Keys#
Authenticate with a bearer token in the authorization header. Keys start mk_live_ and continue with 48 hexadecimal characters:
authorization: Bearer mk_live_9a4f1c7d0b3e5a628f41d90c7b25e3f814a6d5c2907b3e1f
curl -sS https://anjin-media-api.fly.dev/v1/source-groups \ -H "authorization: Bearer $ANJIN_API_KEY"
That is the only authentication scheme the API accepts. There is no API key header, no query parameter and no session cookie.
Minting a key
Keys are created in the dashboard at app.anjin.media under Settings → API keys, by a member with permission to manage account settings. Give the key a name, choose its scopes, and copy it from the panel that appears. There is no REST endpoint that mints a key - that is deliberate, since a key able to mint keys is a key able to escape its own scopes.
Keys belong to an account, not to a person: a key minted by one member keeps working after that member leaves, until someone revokes it. Revoking cannot be undone and takes effect on the key’s next request; the row stays in the list marked revoked, so an audit still shows what existed.
An unusable key
A missing header, a malformed key, or one that has been revoked all answer the same way - the API never tells an unauthenticated caller which of those it was:
{
"type": "https://anjin.media/errors/invalid_api_key",
"title": "Invalid Api Key",
"status": 401,
"detail": "Invalid API key",
"code": "invalid_api_key"
}Key hygiene
- One key per caller. A separate key per service and per environment means revoking one does not stop the others, and the 12-character prefix in the dashboard tells you which is which.
- Scope down. A reporting job needs
read. A pipeline that renders needsreadandwrite. Almost nothing needsadmin. - Never in a browser. Keys are account-wide and carry no origin restriction. Call the API from your own server, and keep the key out of client bundles, repositories and CI logs.
- Replace, then revoke. Mint the new key, deploy it, confirm it works, then revoke the old one. Revocation takes effect on the next request, so the other order causes an outage.
Scopes#
A key carries any combination of three scopes. Each route requires exactly one of them:
| Scope | Methods | Covers |
|---|---|---|
read | GET | Reads across your media: listing and fetching source groups, transcripts, speakers, cuts, plan versions, brand kits, renders and usage. Listing webhook endpoints is the one GET that is not a read - it needs admin. |
write | POST / PATCH | Creating source groups and completing uploads, creating cuts, editing and revising plans, requesting renders, and naming speakers. |
admin | POST / DELETE | Brand kits, webhook endpoints, and every delete - source groups, cuts and renders. |
A key that resolves but lacks the scope a route requires gets a 403, and the detail names the scope that was missing:
{
"type": "https://anjin.media/errors/insufficient_scope",
"title": "Insufficient Scope",
"status": 403,
"detail": "this API key lacks the required \"write\" scope",
"code": "insufficient_scope"
}The MCP server checks scopes the same way, per tool rather than per route: a read-only key can list source groups and read plans, and render_cut refuses it. See MCP & agents.
Rate limits#
Two limits apply to every authenticated request:
- 60 requests a minute per key.
- 120 requests a minute per account, across every key it holds - so minting more keys does not buy more throughput.
Both are token buckets that refill continuously rather than resetting on a clock edge, so a caller that spaces requests out never sees a limit at all. Every authenticated response - success or failure - carries the budget you have left, which is whichever of the two buckets is lower:
x-ratelimit-remaining: 47
Exceed the per-key limit and the request is refused before it costs an account token, with retry-after in whole seconds:
x-ratelimit-remaining: 0 retry-after: 4
{
"type": "https://anjin.media/errors/rate_limited",
"title": "Rate Limited",
"status": 429,
"detail": "Too many requests - slow down and retry",
"code": "rate_limited"
}Exceed the account limit and the code is different, so you can tell the two apart without parsing prose:
{
"type": "https://anjin.media/errors/account_rate_limited",
"title": "Account Rate Limited",
"status": 429,
"detail": "Too many requests for this account - slow down and retry",
"code": "account_rate_limited"
}Back off on retry-after rather than retrying immediately, and give any request that creates something an idempotency-key header so a retry replays the stored response instead of creating a second cut. Separate ceilings limit how many ingests, plans and renders you can have in flight at once; Errors & limits covers those, the full problem+json code list and idempotency in detail.
With a key in hand, the quickstart runs the whole workflow in seven calls, and Uploading video is the first step of it in full. If you are choosing between the REST API and the MCP server, the API overview and the MCP overview make that call quicker.
