# Management API

> The portal's own HTTP API — everything the CLI and UI do.

[HTML version](https://snoozestack.com/docs/management-api/) · [Docs index](https://snoozestack.com/llms.txt)

Everything the CLI and portal UI do goes through the portal's own HTTP API — there is no private back channel. Anything you can click, you can script.

This page is for your own tooling. If the caller is an **AI client**, use the [MCP endpoint](https://snoozestack.com/docs/mcp.md) instead: it's these same operations exposed as MCP tools — with argument schemas, a project-scoped token you can rotate, and the docs served alongside them — rather than endpoints a model has to guess its way around.

**Why use it**

This is the **control plane**, and it's a different axis from the project API your app talks to. `/api/auth`, `/api/functions/v1`, `/api/storage/v1` and friends are what an app calls at runtime — none of them reach a table directly, a [function](https://snoozestack.com/docs/functions.md) has to sit in front; the management API instead creates and configures *the projects themselves*. Keep the two straight and the security model falls out naturally: app code holds a public project key and can never provision infrastructure, while a management token can provision anything and therefore never belongs in an app.

Both of a project's keys sit on the app axis, the *service* key included. It exists so your own server can call this project's API — enqueueing to a [queue](https://snoozestack.com/docs/queues.md), say — and nothing more; it is not an admin credential, and no key of any kind reads a table. Anything administrative, reading rows with `snoozestack data` included, is this API with your login or a project token from `snoozestack projects tokens create`.

Reach for it whenever a human clicking the portal is the bottleneck — when project setup has to be reproducible, or when the thing that needs to act isn't a person.

- **Provision a project per customer** — A B2B product that gives every tenant an isolated database: create the project, publish its schema, and read back the keys, all from your signup flow, instead of clicking through the portal for each new account.
- **Ephemeral environments in CI** — Spin up a throwaway project for a pull request, run the test suite against a real database and real functions rather than mocks, and delete it when the branch merges. (For most CI, `snoozestack dev` against the checkout is even simpler — no hosted project needed at all.)
- **Infrastructure as code** — Keep secrets, scheduled functions, and DNS records in a script under version control, so a rebuilt project is identical to the one it replaced.
- **Internal admin tooling** — Build your own support console — look up a user, ban an account, re-run a schedule — without giving support staff portal logins.

### Authenticating

Sign the CLI in once — `snoozestack user login` opens your browser, and works for an account that signs in with Google as well as one with a password — then `snoozestack user token` prints the bearer token it stored in `~/.snoozestack/config.json`. Treat it like a root credential: it can read every key and delete every project in the organization.

```bash title="terminal"
snoozestack user login          # approve the code in the browser tab it opens
TOKEN=$(snoozestack user token)

curl -s https://snoozestack.com/api/projects -H "Authorization: Bearer $TOKEN"
```

A machine with no browser and no CLI can exchange an email and password for the same token at `/api/auth/cli-token` (`POST` `{"email", "password"}`) — for a password account only. For anything unattended, prefer a [project token](https://snoozestack.com/docs/cli.md) (`snoozestack projects tokens create`): it is scoped to one project and revocable from anywhere.

A worked example — provision a project, wait for it, apply a schema, and print the keys an app would need:

```bash title="provision.sh"
set -euo pipefail
API=https://snoozestack.com/api
AUTH="Authorization: Bearer $TOKEN"

# 1. Create the project — the response is the project row, incl. its keys.
REF=$(curl -s -X POST "$API/projects" \
  -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"name": "tenant-acme"}' | jq -r .slug)

# 2. Wait for the project's runtime to come up: runtime/state is 404 until
#    the starter publish that creation kicks off has a container answering.
until curl -sf "$API/projects/$REF/runtime/state" -H "$AUTH" >/dev/null
do sleep 3; done

# 3. Apply the schema: the schema.json that "snoozestack schema build" writes
#    from schema.mjs, published as the project's new schema version.
curl -s -X POST "$API/projects/$REF/schema" \
  -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{"schemaJson": $(jq -Rs . < snoozestack/schema.json)}"

# 4. Hand the app its credentials. There is no "url" field — a project is
#    always served at https://<slug>.snzzz.com.
ANON=$(curl -s "$API/projects/$REF" -H "$AUTH" | jq -r .anon_key)
echo "SNOOZESTACK_URL=https://$REF.snzzz.com/api"
echo "SNOOZESTACK_ANON_KEY=$ANON"
```

### Projects & schema

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/projects` | GET, POST | List / create projects |
| `/api/projects/<ref>` | GET, DELETE | Project detail (incl. keys) / delete |
| `/api/projects/<ref>/pause` | POST | Stop the project: runtime removed, API and sites answer paused (owner only) |
| `/api/projects/<ref>/resume` | POST | Start it again, if the plan has a free slot |
| `/api/projects/<ref>/schema` | GET, POST | The project's current schema.json / upload a new one (what snoozestack publish sends) |
| `/api/projects/<ref>/runtime/state` | GET | What the running runtime holds: tables, functions, queues, sites — the console's read path |
| `/api/projects/<ref>/runtime/table/<name>` | GET | One runtime table's rows, paged |
| `/api/projects/<ref>/schema/live` | GET | The schema production's database records as applied — what publish plans a release's drops against ({ schemaJson }, null before production has any) |
| `/api/projects/<ref>/schema/drop-impact` | GET | ?drop=<table> / ?drop=<table>.<column>, repeated: rows per table and non-null values per column in production — what publish shows before a release drops data |
| `/api/projects/<ref>/schema/rebuild-impact` | GET | ?checks=<json array>: rows each rebuilt table keeps, values under a changed type, and the rows that would stop a rebuild (NULLs under a new NOT NULL, duplicates under a new unique key, orphans under a new foreign key) — counted in production before publish uploads anything |
| `/api/projects/<ref>/releases` | GET, POST | Releases / record one from a package manifest and deploy it to production (what snoozestack publish does last) |
| `/api/projects/<ref>/releases/<n>` | GET | One release in full: manifest, checksums, schema plan, drops, diff, deployments |
| `/api/projects/<ref>/releases/<n>/deploy` | POST | Deploy a recorded release again (publish --release) — a rollback when it is older than the live one |
| `/api/projects/<ref>/deployments` | GET | Every publish and rollback, with actor, evidence and outcome |
| `/api/projects/<ref>/preview` | GET, POST, DELETE | The signed-in account's own preview: its name and status / deploy a package manifest to it (what snoozestack preview does last) / clean it up |
| `/api/projects/<ref>/preview/outbox` | GET | Mail your preview's runtime tried to send — captured, never delivered |
| `/api/projects/<ref>/previews` | GET | Everyone's previews of the project, with owner, URL, state and expiry |
| `/api/projects/<ref>/previews/<name>` | GET, DELETE | One preview's status (probing it if still building) / clean it up now |
| `/api/projects/<ref>/previews/<name>/outbox` | GET | Mail the preview's runtime tried to send — captured, never delivered |

### Functions, schedules & secrets

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/projects/<ref>/runtime/publish` | POST | Upload the whole snoozestack/ tree as a runtime version: dark for the live namespace (a release names it, its deploy activates it), live for a preview namespace |
| `/api/projects/<ref>/functions` | GET | The per-function store's listing, kept for the console's MCP page; what is live is runtime/state |
| `/api/projects/<ref>/functions/<name>` | GET, PUT, DELETE | Read source / redeploy (merges files, preserves auth + MCP flags) / delete |
| `/api/projects/<ref>/schedules` | GET, POST, PATCH, DELETE | Cron schedules for functions — list / create / update / delete |
| `/api/projects/<ref>/schedules/run` | POST | Run a schedule immediately |
| `/api/projects/<ref>/secrets` | GET, POST, DELETE | Function secrets (applied live) |

### Auth

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/projects/<ref>/auth/users` | GET, PATCH, DELETE | List users / ban-unban / delete — no POST: accounts exist only because someone signed up |
| `/api/projects/<ref>/auth/users/send` | POST | Send the sign-in link to an existing user: { type: "recovery", email } — it doubles as password reset. "invite" and "confirmation" answer 409: application identity has no such flows |
| `/api/projects/<ref>/auth/config` | GET, PUT | Site URL + additional redirect URLs |
| `/api/projects/<ref>/auth/providers` | POST, DELETE | Enable / disable Google and Apple sign-in |
| `/api/projects/<ref>/auth/providers/apple-secret` | POST | Sign an Apple client secret from the stored .p8 — returns it, saves nothing |
| `/api/projects/<ref>/auth/emails` | GET, PUT | Email template subjects and HTML bodies |
| `/api/projects/<ref>/auth/reset-config` | GET, PUT | Where the hosted password page hands off to instead of rendering its form |
| `/api/projects/<ref>/auth/reset-page` | GET, PUT | Hosted password page HTML |

### Storage & queues

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/projects/<ref>/storage` | GET, POST, DELETE | Browse / upload / delete objects |
| `/api/projects/<ref>/storage/buckets` | GET, POST, PATCH, DELETE | List / create / toggle public-private / delete buckets |
| `/api/projects/<ref>/storage/sign` | GET | Mint a signed URL for a private object |
| `/api/projects/<ref>/queues` | GET, POST, DELETE | List (with message counts) / create / delete queues |
| `/api/projects/<ref>/queues/<name>/messages` | POST | Enqueue a message |
| `/api/projects/<ref>/queues/triggers` | GET, POST, PATCH, DELETE | Queue triggers — list / create / update (functions, enabled) / delete |
| `/api/projects/<ref>/queues/webhooks` | GET, POST, DELETE | Queue webhooks (public inbound URL → queue) — list / create / delete |

### Web & domains

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/projects/<ref>/sites` | GET | List published static sites |
| `/api/projects/<ref>/sites/<site>` | GET, POST, DELETE | Site manifest (paths + ETags) / upload a file / prune paths or delete the site |
| `/api/projects/<ref>/sites/<site>/publish` | POST | Turn staged uploads into a new, previewable version — not live until promoted |
| `/api/projects/<ref>/sites/<site>/promote` | POST | Make a version live and flush the edge cache — also how to roll back |
| `/api/projects/<ref>/sites/<site>/versions` | GET | Version history for a site |
| `/api/projects/<ref>/domains` | GET, POST, DELETE | List / connect / disconnect a domain |
| `/api/projects/<ref>/domains/<domain>/records` | GET, POST, DELETE | List / add / delete DNS records |
| `/api/projects/<ref>/distributions` | GET, POST, DELETE | Attach / detach a custom domain to a site or the project API |

### Monitoring

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/projects/<ref>/services/logs?service=…` | GET | The runtime's log (service=runtime; auth, storage and functions are aliases for it), or the portal-side queue and website activity (queues, sites) |
| `/api/projects/<ref>/audit` | GET | The project's change history — console, CLI, MCP and service tokens — newest first, filterable by area and actor |
| `/api/projects/<ref>/observability[?section=…]` | GET | Resources overview, or per-product detail (database, auth, storage, functions, queues, sites) |
| `/api/projects/<ref>/signals?range=…` | GET | Signals overview aggregates |
| `/api/projects/<ref>/signals/events` | GET | Raw event feed, newest first, paginated |
| `/api/projects/<ref>/signals/users` | GET | User profiles derived from events |
| `/api/projects/<ref>/signals/meta` | GET | Event names / property keys / top property values in a range |

### Organization

| Endpoint | Methods | Purpose |
| --- | --- | --- |
| `/api/org` | GET, PATCH | Organization detail / rename |
| `/api/org/members` | GET, POST, DELETE | Admin accounts — list / invite / remove |
| `/api/org/audit` | GET | The organization-wide change history |
| `/api/projects/<ref>/mcp-tokens` | GET, POST | MCP access tokens — list / create (DELETE on /<id>, POST /<id>/rotate) |
| `/api/projects/<ref>/service-tokens` | GET, POST | Project tokens for CI — list / create (DELETE on /<id>, POST /<id>/rotate) |

Responses are JSON; failures carry a non-2xx status and an `{ "error": "…" }` body. See the [CLI reference](https://snoozestack.com/docs/cli.md) for the commands that wrap the most common of these calls.
