Skip to content

Management API

The Management API (/api/v1/*) is the control surface: provisioning, configuration, user/team administration, schema/DDL, secrets, backups, and platform stats. It is not the data plane — application traffic lives under /v1/* (Data-Plane API).

Base URL & auth

https://<host>/api/v1

Every request (except POST /platform/login) needs Authorization: Bearer <token>. It fails closed — no valid credential ⇒ 401. Data-plane keys (anon, service_role) are not valid here.

TypeFormatNotes
Admin tokenliteral ANVILBASE_ADMIN_TOKENroot, constant-time compared, always admin scope. anvilbase start generates one into .env for local stacks; a raw docker compose up deployment sets it (or leaves it unset to disable the bootstrap path and use PATs only)
PATanvilbase_pat_…DB-backed; HMAC-verified via CONTROL_PLANE_SECRET; active + non-expired; carries its own scopes. Mint via POST /platform/tokens or anvilbase token mint
Session JWTcompact JWS12h, issued by POST /platform/login; role→scope mapped; user re-checked active per request

Scopes are enforced per route: admindeployread (unknown routes require admin; the sensitive reads GET /projects/{id}/keys/service-role, GET /admin/audit/export, and GET /admin/users require admin). Under-scoped credentials get a 403 naming the required scope. Full model: Management API → Scopes.

Platform identity

MethodPathPurpose
POST/platform/loginemail+password → 12h session JWT (unauthenticated; per-IP rate limited + 5-failure IP+email lockout)
POST/platform/tokensmint a PAT (`{name, scope
GET/platform/tokenslist PAT metadata (never hashes/plaintext) — admin scope
DELETE/platform/tokens/{id}revoke — admin scope

Conventions

  • Bodies/responses are JSON unless noted. Errors use { "error": { "message": ..., "code": ... } } (Error Codes).
  • Pagination: limit (defaults vary; capped server-side — 100 for most lists, 200 for cache keys) + offset. Some lists wrap as { entries/users, total, limit, offset }.
  • Destructive DDL requires a ?confirm=<name> echo matching the path segment.
  • Mutations write a best-effort audit entry.

OpenAPI

GET /openapi.json

Compile-time OpenAPI 3.x for the control plane (unauthenticated; partial coverage). A per-project, schema-derived spec for your tables is at GET /api/v1/projects/{id}/docs.

Health (unauthenticated)

MethodPathNotes
GET/healthliveness (status, database); carries a top-level version string (the running control-plane CARGO_PKG_VERSION)
GET/health/servicesper-service fan-out (status, warnings, services); also carries a top-level version string

Projects

MethodPathPurpose
POST/projectscreate (provisions DB + bucket + keys; returns jwt_secret + service_role_key once)
GET/projectslist (paginated; ?status= filter)
GET/projects/{id}get one
PATCH/projects/{id}update name/description/settings/quotas
DELETE/projects/{id}soft-delete + deprovision (204)
POST/projects/{id}/suspendset status='suspended' — the data plane then rejects all traffic for the project (403)
POST/projects/{id}/resumeset status='active' — restore data-plane access

Suspend/resume are the only paths that set the suspended state; the data-plane enforcement (in project-context middleware) already rejects any non-active project.

Auth settings & OAuth providers

MethodPathPurpose
GET/projects/{id}/authget settings (secrets masked)
PATCH/projects/{id}/authupdate email_enabled, magic_link_enabled, min_password_length, session_duration_seconds, refresh_token_duration_seconds
PUT/projects/{id}/auth/providersadd/update a provider (github/google/apple/discord/microsoft)
DELETE/projects/{id}/auth/providers/{provider}remove a provider
GET/auth/templatesprovider templates + recommended scopes

Project users

MethodPathPurpose
GET/projects/{id}/userslist (?search= email, paginated)
POST/projects/{id}/userscreate
GET / DELETE/projects/{id}/users/{user_id}get / delete
GET/projects/{id}/users/{user_id}/sessionslist sessions
DELETE/projects/{id}/users/{user_id}/sessionsrevoke all sessions
DELETE/projects/{id}/users/{user_id}/sessions/{token}revoke one
POST/projects/{id}/users/{user_id}/ban · /unbanban / unban

Team invites

MethodPathPurpose
POST/projects/{id}/invitescreate (returns invite_token once)
GET/projects/{id}/inviteslist pending
DELETE/projects/{id}/invites/{invite_id}revoke
POST/invites/acceptaccept ({token}) — token-authorized; binds to the session user (any authenticated platform user; user_id optional, must match the session user)

Roles: owner, admin, editor (default), read_only.

Team members

Post-acceptance member administration (admin scope). A last-owner guard, enforced atomically in the model, refuses to demote or remove the final owner (409).

MethodPathPurpose
GET/projects/{id}/memberslist members
PATCH/projects/{id}/members/{user_id}change a member’s role ({role}) — 404 if not a member, 409 last-owner
DELETE/projects/{id}/members/{user_id}remove a member — 404 if not a member, 409 last-owner

Schema / DDL

MethodPathPurpose
GET/projects/{id}/schema/tableslist tables (columns/PK/FK/indexes/row estimate)
POST/projects/{id}/schema/tablescreate table (RLS on by default)
DELETE/projects/{id}/schema/tables/{table}?confirm={table}drop table
POST/projects/{id}/schema/tables/{table}/columnsadd column
DELETE/projects/{id}/schema/tables/{table}/columns/{col}?confirm={col}drop column
GET/projects/{id}/schema/extensionslist extensions
GET/projects/{id}/docsper-project REST OpenAPI

Database (migrations & reset)

MethodPathPurpose
POST/projects/{id}/db/pushapply ordered migrations (idempotent)
GET/projects/{id}/db/migrationslist applied
POST/projects/{id}/db/resetdrop public tables + optionally re-apply

Raw SQL is a data-plane RPC (rpc/exec_sql, service_role) — see Data-Plane API.

Database branches

Neon-style branching (admin scope). A branch is a fully isolated clone of the parent’s database with its own keys, JWT secret, DB, and bucket — a distinct tenant. The parent is never touched.

MethodPathPurpose
POST/projects/{id}/branchescreate + clone + re-key a branch ({name, from?, schema_only?}; returns the branch’s own anon_key, service_role_key, jwt_secret once)
GET/projects/{id}/brancheslist branches (secret-free)
DELETE/projects/{id}/branches/{slug}drop the branch tenant (DB + bucket); the parent is never affected

name becomes a slug (3–50 chars, lowercase alphanumeric + hyphens). from defaults to "now" (clone the parent’s current state); PITR-sourced branches are deferred. schema_only clones DDL with zero rows.

RLS

MethodPathPurpose
GET/projects/{id}/rls/statusper-table enabled/forced flags
POST/projects/{id}/rls/tables/{table}/enable · /disabletoggle RLS
GET/projects/{id}/rls/tables/{table}/policieslist policies
POST/projects/{id}/rls/tables/{table}/policiescreate policy
DELETE/projects/{id}/rls/tables/{table}/policies/{name}drop policy
POST/projects/{id}/rls/testsimulate (always rolls back)

Secrets vault

MethodPathPurpose
GET/projects/{id}/secretslist (metadata only)
POST/projects/{id}/secretscreate/update (upsert by name) — deploy scope
PUT/projects/{id}/secrets/{name}update an existing secret’s value (404 if absent) — admin scope
POST/projects/{id}/secrets/{name}/rotaterotate to a fresh random value (returned once) — admin scope
DELETE/projects/{id}/secrets/{identifier}delete by UUID or name — admin scope

Values are never returned on list/get/update; rotate returns the new value once.

Secret rotation

MethodPathPurpose
POST/projects/{id}/rotate/jwtnew JWT secret (invalidates all JWTs)
POST/projects/{id}/rotate/api-keysnew anon + service_role keys

Connection pooler

MethodPathPurpose
GET/projects/{id}/poolerreveal the project’s pooled + direct connection strings

Returns { project_id, enabled, mode, pooled, direct, read_only? }. The pooled string (transaction-mode Supavisor) embeds the per-project derived pooler password, so the call is an audit-logged reveal (not part of the secret-redacted project response). When the pooler is unconfigured (SUPAVISOR_PUBLIC_HOST unset), enabled=false and pooled is null; direct (session mode) is always present. When an opt-in read replica is configured (ANVILBASE_READ_REPLICA_HOST set), the response also carries a read_only connection string pointed at the replica for read-heavy queries (writes must still use direct/pooled); the field is omitted entirely when no replica is configured.

Edge functions

MethodPathPurpose
GET / POST/projects/{id}/functionslist / create+deploy
GET / PUT / DELETE/projects/{id}/functions/{name}get / update / delete

verify_jwt defaults to true; code ≤ 10 MB; quota breach ⇒ 429. Invocation is data-plane.

Webhooks

MethodPathPurpose
POST / GET/projects/{id}/webhookscreate (returns secret once) / list
PATCH / DELETE/projects/{id}/webhooks/{wid}update / delete
GET/projects/{id}/webhooks/{wid}/deliveriesdelivery attempts
POST/projects/{id}/webhooks/{wid}/testsynchronous test (SSRF-guarded)
POST/projects/{id}/webhooks/{wid}/attachattach the row-change trigger to a table ({table, events, schema?}) — deploy scope
DELETE/projects/{id}/webhooks/{wid}/attach/{table}detach the trigger (?schema=) — deploy scope
GET/projects/{id}/webhooks/{wid}/attachmentslist attached tables + events

Attach/detach wire the Supabase-style database (row-change) webhook trigger onto a user table; see Guides → Webhooks for the payload shape and security model.

Email (templates & SMTP)

MethodPathPurpose
GET / PUT / DELETE/projects/{id}/email/templates[/{type}]manage templates
GET/projects/email/templates/defaultsbuilt-in defaults
GET / PUT/projects/{id}/email/smtpSMTP config (password write-only)
POST/projects/{id}/email/testsend a test email

Types: magic_link, verify_email, reset_password, invite, welcome.

Cache (Valkey)

MethodPathPurpose
GET/projects/{id}/cache/keyslist (?pattern=, ?limit= ≤ 200)
GET / DELETE/projects/{id}/cache/keys/{key}get / delete
POST/projects/{id}/cache/keysset string (optional ttl)
POST/projects/{id}/cache/flushdelete all project keys
GET/projects/{id}/cache/statskey count

These are the admin/console cache reads (session-gated). The full server-side KV surface (set/incr/scan/…) is a service_role data-plane API — see Data-Plane API → Cache.

Cron (inspector)

Admin/console read-only mirror of the pg_cron data plane — the console holds a session, not a service_role key, so it inspects via these endpoints. Scheduling and unscheduling stay in the service_role data-plane API.

MethodPathPurpose
GET/projects/{id}/cron/jobslist this project’s scheduled jobs (un-namespaced names)
GET/projects/{id}/cron/jobs/{name}/runsrecent run history, newest-first (?limit= ≤ 100, default 20)

Logs

MethodPathPurpose
GET/projects/{id}/logsunified per-project log read (admin/console)

Merges edge_function_logs (function invocations) and proxy_access_logs (data-plane access logs) newest-first, tenant-scoped. Query: ?source=function|access|all (default all), ?limit= (1–500, default 50), ?before= (opaque keyset cursor echoed back as next_before). Entries are telemetry-shape only — method, status, duration, route/function name — never bodies, headers, or keys.

Usage

MethodPathPurpose
GET/projects/{id}/usageper-project hourly usage time-series (admin/console)

Returns { usage: [...] } newest-first — each bucket has bucket_start, requests, bytes_in, bytes_out, function_invocations, storage_bytes. Query: ?from= / ?to= (RFC3339 bounds on bucket_start), ?limit= (1–5000, default 168 ≈ 7 days of hourly buckets).

Backups & PITR

MethodPathPurpose
GET / POST/projects/{id}/backupslist / trigger
GET/projects/{id}/backups/settingsread the scheduled-backup config
PUT/projects/{id}/backups/settingsupsert schedule + retention + verify config ({enabled, interval_minutes, retain_days?, retain_count?, verify_enabled, verify_interval_minutes}) — admin scope
POST/projects/{id}/backups/{backup_id}/restorerestore
GET/projects/{id}/pitr/restore-pointslist restore points
GET/projects/{id}/pitr/restore/previewread-only dry-run for a target (?target_time=<RFC3339>): selected basebackup, WAL window, data-loss cliff, warnings — no writes, safe to repeat
POST/projects/{id}/pitr/restorerequest restore ({target_time}; 202). PITR replays shared WAL, so the eventual restore is instance-wide and the destructive cutover is operator-gated

Setting a backup schedule provisions nothing external — the control-plane backup scheduler reads the config each tick (no pg_cron).

Saved queries & history

MethodPathPurpose
GET / POST/projects/{id}/queries/savedlist / upsert
DELETE/projects/{id}/queries/saved/{query_id}delete
GET / POST/projects/{id}/queries/historylist / record

eIDAS config

MethodPathPurpose
GET / PATCH/projects/{id}/eidasget / replace config

eIDAS wallet flows are data-plane (/v1/eidas/{id}/*).

Platform admin

MethodPathPurpose
GET/admin/statsplatform-wide counts
GET/admin/auditrecent entries (?action=, paginated)
GET/admin/audit/exportbulk export (?format=json|csv, filters)
POST/admin/audit/pruneprune the audit log + auth-event archive on demand (?days=; defaults to ANVILBASE_AUDIT_RETENTION_DAYS)
POST/admin/webhooks/dlq/purgepurge DLQ deliveries on demand (?days=; defaults to ANVILBASE_DLQ_RETENTION_DAYS)
POST/admin/encrypt-settings-secretsone-shot sweep migrating legacy plaintext settings-secrets and the per-project projects credential columns (jwt_secret/previous_jwt_secret/anon_key/service_role_key) to enc:v1: at rest (idempotent; also runs as a non-fatal startup sweep)
POST/admin/platform/upgradedata-safe upgrade runner. ?target= (omit → running version, version-agnostic). Defaults to a side-effect-free DRY-RUN that returns the UpgradePlan (target version, images to pull, pending platform + per-project migrations, per-project snapshot plan). With ?execute=true it snapshots every project FIRST, aborts if any snapshot fails, applies migrations idempotently, and returns the rollback manifest (prior version/images + per-project snapshot ids). See Upgrades.
GET / POST/admin/userslist / create platform users
PATCH / DELETE/admin/users/{id}update / delete

Platform roles: super_admin, project_admin, developer, viewer (can’t delete the last active super_admin).


For request/response bodies and worked curl examples per endpoint, this catalog pairs with the in-repo docs/api/management.md long-form reference and the live GET /openapi.json. See also Data-Plane API and Error Codes.