# Conventions > Authentication, envelopes, pagination, idempotency, optimistic concurrency, and a tour of every route group in the Management API. The Management API is the write side of Myna: schema pushes, drafts, [change sets](/concepts/change-sets), publishing, assets, webhooks, and organization administration. Everything lives under `https://api.myna.sh/v1` and follows a small set of uniform conventions. The complete machine-readable surface is the OpenAPI 3.1 document at [/openapi.json](/openapi.json). ## Authentication Send an API key as a bearer token: ```bash $ curl -H "Authorization: Bearer myna_sk_..." \ https://api.myna.sh/v1/projects/my-site/entries ``` Keys carry explicit [scopes](/platform/scopes); each route requires a specific capability (for example `content:write` to create an entry, `project:admin` to manage keys). A request with a valid key but a missing scope fails with `PERMISSION_DENIED`. Browser sessions from the dashboard work on the same routes, with capabilities derived from the member's role — see [Authentication](/getting-started/authentication). If you already have the CLI authenticated, `myna api` calls any route on this page with the credential it already resolved, and renders the errors below the way the rest of the CLI does: ```bash $ myna api /projects/my-site/entries?collection=posts ``` The SDK has the same escape hatch as `myna.raw(method, path)`. Both are documented under [using the CLI](/cli/overview#the-escape-hatch-myna-api). ## Envelopes Every successful response wraps its payload: ```json { "data": { ... } } ``` List endpoints add a pagination block: ```json { "data": [ ... ], "pagination": { "nextCursor": "eyJrIjoi..." } } ``` ## Pagination Lists use cursor pagination. `limit` is clamped to `1–100` and defaults to `25`; `cursor` is the opaque `nextCursor` from the previous page. `nextCursor` is `null` on the last page. Cursors encode the sort position internally — treat them as opaque strings, and expect `VALIDATION_FAILED` for malformed ones. Pages do not overlap: following `nextCursor` walks every row exactly once. Filters such as `?status=` on entry lists are applied before the page is cut, so a page is full whenever more matching rows exist and `nextCursor` reflects the filtered result rather than the unfiltered one. ```bash $ curl -H "Authorization: Bearer myna_sk_..." \ "https://api.myna.sh/v1/projects/my-site/entries?limit=50&cursor=eyJrIjoi..." ``` ## Idempotency All mutating methods (`POST`, `PATCH`, `PUT`, `DELETE`) accept an `Idempotency-Key` header. The first response with a given key is persisted for 24 hours, keyed by the authenticated actor, the key, and the route; retries with the same key on the same route replay the stored status and body verbatim instead of re-executing. Responses with 5xx status are not stored, so a retry after a server error executes normally. Anonymous requests are never idempotency-tracked. The key is claimed *before* the handler runs, so concurrency is handled rather than raced: if a second request arrives with a key whose first request has not finished, it receives `409` with code `CONFLICT` and a message saying the request is already in flight. Retry once the original completes and you will get the stored response. This is what makes a client-side retry of a request whose response was lost safe — without it, two concurrent retries of `POST /projects/:project/entries` would each create an entry. ```bash $ curl -X POST -H "Authorization: Bearer myna_sk_..." \ -H "Idempotency-Key: 018f3b1e-create-post" \ -H "Content-Type: application/json" \ -d '{"collection":"posts","fields":{"title":"Hello"}}' \ https://api.myna.sh/v1/projects/my-site/entries ``` ## Optimistic concurrency Entry updates accept an `expectedRevisionId`. If it no longer matches the entry's current draft revision, the API responds `409` with code `STALE_REVISION` and a `details` object containing `expectedRevisionId` and `currentRevisionId` — re-read, merge, and retry. This is the primary guard against two agents clobbering each other's edits. ```json { "type": "https://myna.sh/errors/stale-revision", "title": "Stale revision", "status": 409, "code": "STALE_REVISION", "details": { "expectedRevisionId": "rev_a", "currentRevisionId": "rev_b" } } ``` ## Errors All errors are RFC 9457 problem details (`application/problem+json`) with a stable machine-readable `code`, a `requestId`, and — for validation failures — a per-field `errors` array. See [error codes](/management-api/errors) for the full catalog. ## Route groups A tour of the surface. All paths are relative to `https://api.myna.sh/v1`. | Group | Routes | Purpose | | --- | --- | --- | | Meta | `GET /meta` | Public and unauthenticated: the API version, the oldest client release it supports, and the capabilities a client can branch on. See [versioning and compatibility](/platform/compatibility). | | Organizations | `GET/POST /organizations`, `GET/PATCH/DELETE /organizations/:organization`, `POST .../export`, `GET .../members`, `PATCH/DELETE .../members/:user`, `GET/POST .../invitations`, `DELETE .../invitations/:invitation`, `POST /invitations/:token/accept` | Tenancy: orgs, membership roles, invitations, data export. | | Projects | `GET/POST /organizations/:organization/projects`, `GET/PATCH /projects/:project`, `POST /projects/:project/archive`, `GET /projects/:project/export`, `GET /projects/:project/cors-check`, `GET /projects/:project/access`, `GET/POST /projects/:project/views`, `DELETE .../views/:view` | Project lifecycle and settings (slug, origins, public API, locales, publish gates), full JSON export, browser-access diagnosis, [denial diagnosis](/getting-started/authentication), and saved entry views. | | Schema | `GET /projects/:project/collections`, `GET .../collections/:collection`, `GET .../collections/:collection/versions`, `GET .../collections/:collection/translations`, `POST /projects/:project/schema/diff`, `POST /projects/:project/schema/push`, `GET /projects/:project/schema/drift`, `POST /projects/:project/schema/promote` | Code-owned schemas: inspect collections, diff a proposed schema, push immutable versions, report [translation coverage](/schema/defining-schemas), and compare or promote between [environment projects](/schema/deploying). | | Entries | `GET/POST /projects/:project/entries`, `GET/PATCH/DELETE .../entries/:entry`, `POST .../unpublish`, `POST .../restore`, `POST .../duplicate`, `POST /projects/:project/entries/bulk`, `POST /projects/:project/entries/import`, `POST /projects/:project/collections/:collection/reorder`, `GET .../references`, `GET .../revisions`, `GET .../revisions/:revision`, `POST .../revisions/:revision/restore` | Draft content: every write creates a revision on a [change set](/concepts/change-sets). Bulk delete/unpublish, duplication, import with dry-run, editorial ordering, reference backlinks, and full-text search with `?q=`. | | Change sets | `GET/POST /projects/:project/change-sets`, `GET/PATCH .../:changeSet`, `GET .../diff`, `POST .../validate`, `POST .../publish`, `POST .../rebase`, `POST .../close`, `POST/DELETE .../schedule`, `GET/POST .../reviews`, `DELETE .../reviews/:reviewer`, `POST .../approve`, `POST .../request-changes`, `GET/POST .../comments`, `PATCH .../comments/:comment`, `GET .../checks`, `POST .../checks/run`, `POST .../checks/:name` | Group, diff, validate, review, check, schedule, rebase, and atomically publish batches of edits. | | Releases | `GET /projects/:project/releases`, `GET .../releases/:release`, `POST .../releases/:release/revert` | Read what shipped, and generate a reviewable change set that restores the state before a [release](/concepts/releases). | | Checks | `GET/POST /projects/:project/checks`, `DELETE .../checks/:name` | Declare the [external checks](/concepts/change-sets) a project expects, and which of them block publishing. | | Previews | `POST/GET /projects/:project/previews`, `DELETE .../:preview`, `GET /previews/resolve` | Mint and resolve read-only [preview](/concepts/previews) tokens for unpublished content. | | Assets | `POST /projects/:project/assets/uploads`, `POST .../uploads/:upload/complete`, `GET /projects/:project/assets`, `GET/PATCH/DELETE .../assets/:asset`, `POST .../assets/:asset/replace` | Two-step uploads plus asset metadata (alt, caption, tags), identity-preserving replace, and orphan/checksum list filters. | | API keys | `GET/POST /organizations/:organization/api-keys`, `DELETE .../api-keys/:key`, `GET/POST /projects/:project/api-keys`, `DELETE .../api-keys/:key` | Mint and revoke org- or project-scoped keys with explicit [scopes](/platform/scopes). | | Webhooks | `GET/POST /projects/:project/webhooks`, `PATCH/DELETE .../:webhook`, `GET .../:webhook/deliveries`, `POST .../deliveries/:delivery/retry` | Signed event delivery with per-delivery inspection and retry. | | Activity | `GET /projects/:project/activity` | The project audit feed: who changed what, when, with which actor type. | | Billing | `GET /organizations/:organization/billing`, `GET .../usage`, `POST .../billing/checkout`, `POST .../billing/portal` | Plan status, metered usage, and checkout/portal sessions. See [plans](/platform/plans). | ## Invitations `POST /organizations/:organization/invitations` records the invitation and returns it — but never its token. The token only ever exists inside the email Myna sends to the invited address, so the emailed link is the one way to accept. Delivery is a durable job, not part of the request. A provider outage retries with backoff for roughly a day rather than dropping the invitation, and each attempt mints a fresh token: if two emails arrive, only the most recent link resolves. Revoking an invitation before the job runs cancels the send. Accepting is a separate, authenticated call. The recipient signs in, the dashboard `POST`s to `/invitations/:token/accept`, and the API checks that the signed-in account owns the invited address — a mismatch fails with `PERMISSION_DENIED` rather than joining the wrong user. Expired, revoked, and already-accepted tokens are all `NOT_FOUND`, so a used link reveals nothing about whether it ever existed. Invitations expire after seven days, and creation is rate-limited per organization since it sends mail to addresses that never opted in.