This page is mechanism. For the posture - what we hold, what we do not claim, how to report a vulnerability, and the maintained sub-processor list - see the security page and the sub-processors page. Where the two describe the same thing they are meant to agree exactly; this one carries the field names and the numbers.
Where media lives#
Everything you upload and everything made from it sits in four storage buckets. All four are private: created closed to the public internet, so no object in them has an address that resolves without a signature.
| Bucket | Access | What is in it |
|---|---|---|
media-sources | private | The files you upload, byte for byte as they arrived. |
media-derivatives | private | What ingest makes from them: the 1080 mezzanine each render is cut from, the extracted audio track, and the preview proxy. |
media-renders | private | Finished renders and their SRT, ASS, EDL and QC sidecars. |
brand-assets | private | Brand kit fonts, logos and end cards. |
Object keys are prefixed with the owning account, and the storage policies are keyed on that first path segment - so reaching an object is a question about which account you are, not which URL you guessed:
<account_id>/<source_group_id>/<source_file_id>/original.mp4
Signed URLs
Every read of a private object is a short-lived signed URL, minted at the moment you ask rather than stored anywhere:
| What | Lifetime | Notes |
|---|---|---|
render output | 3,600 s | Minted freshly on every call to GET /v1/cuts/:id/renders. Do not store one - fetch the endpoint again when you need it. |
render sidecars | 3,600 s | The same lifetime, on the same read, one URL per sidecar. |
signed upload | one object | Issued once by POST /v1/source-groups for each file you named, and it writes to that one object key and no other. It is a short-lived handle rather than a durable one: upload the bytes and confirm rather than holding it. |
transcription audio | 7,200 s | The one link the platform hands to a third party: a signed URL to the extracted audio, given to the transcription vendor so it can pull the bytes. Two hours, and it points at audio rather than video. |
The account is the boundary
Worth knowing at the call site rather than as posture: on the read, get, patch and list paths, a resource your key does not own answers 404 not_found, never 403. So a 404 from this API means either no such id or not yours, and your own code cannot tell the two apart - which is the intent. Do not write a handler that treats a 404 as proof something was deleted.
The scope of an account, and the fact that per-library permissions are on the roadmap, is stated on the security page and in Working with an archive.
Who processes what#
The vendor list and what each one receives lives on the security page, with the maintained version - role and data category per vendor - on the sub-processors page. What follows is only the part a code-level review asks for: the exact shape of the two calls that leave the platform carrying customer material.
- Transcription. The host called is
api.eu.assemblyai.com. What it is given is a signed URL to the extracted audio track inmedia-derivatives- a WAV derivative rather than the source video - valid for 7,200 seconds, which the vendor fetches out of process. Completion comes back to a separate internal endpoint on this API, authenticated by a shared token; that callback is not part of your webhook surface and carries no customer-facing event. - Planning. The request body is text: your brief and the transcript segment view the plan is chosen from. There is no image or audio content anywhere in the plan path and no media URL is included in it, so a signed link never reaches the planner.
One mechanism detail the security page does not carry: the only response headers this API sets beyond the standard ones are x-ratelimit-remaining, retry-after and idempotency-replayed. There is no request-id header, because nothing in the platform collects request ids - the absence of a monitoring vendor is a posture statement and the security page makes it.
Model training#
We do not train models on your media. There is no Anjin model to train: planning calls Anthropic's API and transcription calls AssemblyAI's, and nothing you upload is used to build anything of ours or shown to another customer.
What those two vendors do with what they receive is set by their own published terms, and the two are not the same - so they are stated separately here rather than averaged into one reassuring sentence. Anthropic's published commercial terms state that Anthropic may not train models on customer content. AssemblyAI's published terms reserve a right to use customer data to develop their services and train their models, with an opt-out that depends on the plan; where our account stands on that opt-out is being confirmed with them, and this section will carry the answer. The security page carries the same statement with links to both documents, and the two pages are updated together.
Nothing is carried between accounts or between one brief and the next. The platform keeps no learned house style: a plan is composed from the transcript and the brief in front of it, and a brand kit applies your fonts, colours, logo and end card at render because you attached it to the cut.
Key handling#
The storage format and the resolve path, which is the half of this a code-level review checks. A key is mk_live_ followed by 24 random bytes rendered as 48 hexadecimal characters. What is persisted is a SHA-256 hash of that whole string, plus its first twelve characters - the fragment the dashboard lists, which is mk_live_ and four hex digits. A database read therefore yields no usable credential, and a lost key is unrecoverable rather than retrievable.
Resolution is a prefix check and then a hash lookup: a token that does not begin mk_live_ is rejected without a database round trip, and anything else is hashed and the row found by that hash. The raw key is never compared against anything stored, because it is not stored. A key that resolves but lacks the scope a route or an MCP tool requires is refused before the handler runs:
{
"type": "https://anjin.media/errors/insufficient_scope",
"title": "Insufficient Scope",
"status": 403,
"detail": "this API key lacks the required \"admin\" scope",
"code": "insufficient_scope"
}Authentication has the rest: minting, the scope map route by route, rotation order and the rate limits.
Webhook signing#
Every outbound delivery carries an anjin-signature header - an HMAC-SHA256 over the timestamp, a full stop and the exact request body, keyed with a 32-byte secret returned once at registration and never again, including from an idempotent replay of the create, which has it redacted from the stored copy. Payloads carry the event name, the account, the ids and a short summary, and never a transcript, media or a signed link.
Webhooks owns the rest: the exact header format, verification with a five-minute tolerance and a constant-time compare, the six events, and delivery behaviour.
Deletion#
Deletion reaches storage, not just the database. It needs the admin scope in every case.
- A source group takes its uploaded originals and every derivative made from them - mezzanine, extracted audio, proxy - and then the record. Bytes already uploaded to a signed URL but never confirmed are purged with it, so an abandoned upload does not linger.
- A cut takes every render it ever produced, across every batch and every aspect, and each render's sidecars with it.
- A render takes the output file and its sidecars, leaving the plan intact so the same version can be rendered again.
- Work in flight refuses. A group that is
ingestingorperceiving, and a render that isqueuedorprocessing, answer409 conflictrather than being pulled out from under a worker. Wait for a terminal state and delete then. - It is not reversible. The bytes are purged, not archived. Cuts that referenced a deleted group keep their plan documents - the record of what was made survives - but there is no footage left to render them from.
There is no lifecycle policy in the platform: no scheduled expiry, no tier a group ages into, and no call that returns something deleted. The one circumstance in which material is removed without a customer asking is the arrears ladder, and the security page is where that commitment is stated.
For the rest of the review: the security page lists what we do not claim and how to report a vulnerability, the sub-processors page is the maintained vendor list, and Authentication and Errors & limits cover the request-level behaviour a penetration test would exercise.
