# REST API

The API is a Cloudflare Worker. All application endpoints are under `/v1` and require
authentication. Values are always **ciphertext** — the API neither encrypts nor decrypts.

## Authentication

Send one of:

- `Authorization: Bearer <jwt>` — a Stytch B2B session JWT (web sessions).
- `Authorization: Bearer skt_…` — a service token. Its stored `role` is `member`
  (runtime credential) or `admin` (passes admin-gated routes, for headless
  provisioning). `owner` is never issued to a token.
- `x-seekrit-dev-user: <email>` — local dev only, when the worker runs with `AUTH_MODE=dev`.

Errors use `{ "error": { "code": "...", "message": "..." } }` with a matching HTTP status
(`400`, `401`, `403`, `404`, `409`, `500`). Organizations you don't belong to return `404`.

## Health

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/health` | Liveness check (no auth). |

## Identity

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/me` | The current user and their organizations. |
| `GET` | `/v1/me/keys` | Your public key and passphrase-encrypted private key. |
| `PUT` | `/v1/me/keys` | Upload your keys (one-time key setup). |

## Organizations

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs` | List organizations you can access. |
| `POST` | `/v1/orgs` | Create an organization (creator becomes owner). |
| `GET` | `/v1/orgs/:orgId` | Get one organization and your role. |
| `GET` | `/v1/orgs/:orgId/members` | List members (includes public keys, for granting). |

## Applications & environments

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/apps` | List applications. |
| `POST` | `/v1/orgs/:orgId/apps` | Create an application. *(admin)* |
| `GET` | `/v1/orgs/:orgId/apps/:appId` | Get an application and its environments. |
| `DELETE` | `/v1/orgs/:orgId/apps/:appId` | Delete an application. *(admin)* |
| `GET` | `/v1/orgs/:orgId/apps/:appId/envs` | List environments. |
| `POST` | `/v1/orgs/:orgId/apps/:appId/envs` | Create an environment (body includes the wrapped DEK). *(admin)* |
| `GET` | `/v1/orgs/:orgId/envs/:envId` | Get an environment. |
| `DELETE` | `/v1/orgs/:orgId/envs/:envId` | Delete an environment. *(admin)* |

An environment is owned by **either** an application or a group; `applicationId`
and `groupId` are both present on the row, with the unused one `null`.

## Groups

A group is a reusable, org-scoped secret bag. Its environments (matched by slug)
reuse the same secret and key-grant endpoints above.

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/groups` | List groups. |
| `POST` | `/v1/orgs/:orgId/groups` | Create a group. *(admin)* |
| `GET` | `/v1/orgs/:orgId/groups/:groupId` | Get a group and its environments. |
| `DELETE` | `/v1/orgs/:orgId/groups/:groupId` | Delete a group. *(admin)* |
| `GET` | `/v1/orgs/:orgId/groups/:groupId/envs` | List a group's environments. |
| `POST` | `/v1/orgs/:orgId/groups/:groupId/envs` | Create a group environment (body includes the wrapped DEK). *(admin)* |

### Composition

Which groups an application environment pulls in, and their precedence.

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/envs/:envId/groups` | List composed groups (slug, name, position). |
| `POST` | `/v1/orgs/:orgId/envs/:envId/groups` | Compose a group `{ groupId, position? }`. *(admin)* |
| `DELETE` | `/v1/orgs/:orgId/envs/:envId/groups/:groupId` | Remove a composed group. *(admin)* |

## Resolve

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/resolve` | The full layered environment for the calling principal. |

For a service token, the org + app + environment come from the token itself. For
a user session pass `?env=<envId>`. Repeat `?with=<groupSlug>:<envSlug>` to
resolve specific groups at a different slug. Returns `{ scope, layers }`, where
`layers` are ordered lowest-precedence first (composed groups → the app
environment) and each carries its `environmentId`, secret ciphertexts, and the
DEK wrapped to the caller. The client decrypts and merges; the server sees only
ciphertext.

> **Note:** Resolve is edge-cached per principal, so a fleet of identical containers or CI jobs sharing one service token is served from cache without re-querying the database. Responses reflect secret writes, key changes, and composition changes within seconds (a write invalidates the cache; a short TTL is the backstop). Because the cache key includes the calling principal, one caller never receives another's wrapped DEK. Successful resolves are metered for usage/billing but are not written to the audit log; a *denied* resolve is audited (`env.resolve_denied`).

## Secrets

Values are opaque ciphertext blobs produced by the client.

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/envs/:envId/secrets` | List secrets (with ciphertext). |
| `PUT` | `/v1/orgs/:orgId/envs/:envId/secrets/:name` | Create or update a secret (appends a version). |
| `DELETE` | `/v1/orgs/:orgId/envs/:envId/secrets/:name` | Delete a secret. |

## Key grants

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/envs/:envId/key` | The calling principal's own wrapped DEK. |
| `GET` | `/v1/orgs/:orgId/envs/:envId/keys` | List all grants for the environment. *(admin)* |
| `POST` | `/v1/orgs/:orgId/envs/:envId/keys` | Grant a wrapped DEK to a principal. *(admin)* |
| `DELETE` | `/v1/orgs/:orgId/envs/:envId/keys/:grantId` | Revoke a grant. *(admin)* |

## Service tokens

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/tokens` | List service tokens. *(admin)* |
| `POST` | `/v1/orgs/:orgId/tokens` | Register a token (client sends hash + public key; `role` defaults to `member`, pass `admin` for a provisioning token; optional `environmentId` binds it to an app environment). *(admin)* |
| `DELETE` | `/v1/orgs/:orgId/tokens/:tokenId` | Revoke a token. *(admin)* |

A service token may read or write secrets only for environments it holds a key
grant for — its bound environment and the group slices composed into it. It
cannot address other environments in the org.

## Audit

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/orgs/:orgId/audit` | Paginated audit entries. *(admin)* Query: `cursor`, `limit`, `action`, `resourceType`. |

> **Note:** Token creation is client-driven: the client generates the keypair and token string locally and submits only the token id, a SHA-256 hash, and the public key. The server never sees the token secret.
