Nothing on this page is a separate product or a different endpoint. An archive is what you have once you have loaded enough recordings, and the API for one recording is the API for a thousand. What changes at that scale is which questions you ask first: what is in the library, which groups a brief should draw on, who is speaking, and what all of it costs to keep.
A library of groups#
GET/v1/source-groups
Every group on the account, newest first, keyset-paginated at 50 rows by default and 100 at most. This is the index of the library:
curl -sS "https://anjin-media-api.fly.dev/v1/source-groups?limit=100" \ -H "authorization: Bearer $ANJIN_API_KEY"
{
"data": [
{
"id": "9f4b1d2e-6c3a-4d51-8f0b-2a7c5e91d340",
"name": "Q3 product webinar",
"status": "ready",
"duration_ms": 3541200,
"created_at": "2026-08-16T09:12:44.610Z",
"camera_count": 1,
"total_duration_s": 3541.2
},
{
"id": "1c7e5a94-3b28-4f60-9d15-8a2f6c30e7b1",
"name": "Customer panel - March",
"status": "ready",
"duration_ms": 5218900,
"created_at": "2026-03-04T15:40:02.118Z",
"camera_count": 3,
"total_duration_s": 5218.9
}
],
"next_cursor": "MjAyNi0wMy0wNFQxNTo0MDowMi4xMThafDFjN2U1YTk0LTNiMjgtNGY2MC05ZDE1LThhMmY2YzMwZTdiMQ"
}The fields worth reading before you brief against anything:
| Field | Type | Description |
|---|---|---|
name | string | Whatever you called the group at creation. It is the only human label the library has, so name groups the way you would name a folder you expect to search in two years. |
status | string | Only a ready group can be used in a cut. Filter on this before you brief against anything. |
camera_count | integer | How many files in the group carry the camera role. Anything above 1 is a multicam group, which the planner may cut between. |
total_duration_s | number | null | The duration of the group's reference angle, to one decimal - not the sum of its files. For a three-camera group of a 90-minute panel this reads about 5,400, not 16,200. It is null until the reference file has been probed. |
duration_ms | integer | null | The same figure in milliseconds, filled in once the group is ready. |
Rows carry no file list. For the files, their roles and their individual statuses, fetch the group: GET /v1/source-groups/:id, documented on Uploading video.
Cuts across groups#
A cut takes one to four source groups. That is the mechanism behind every claim about composing from an archive: the planner reads all four transcripts as one body of material and selects passages from wherever the argument is best made.
{
"prompt": "Ninety seconds on how our answer to the data residency question changed between the March panel and the Q3 webinar. Open on the older answer, then the newer one, then the number.",
"source_group_ids": [
"1c7e5a94-3b28-4f60-9d15-8a2f6c30e7b1",
"9f4b1d2e-6c3a-4d51-8f0b-2a7c5e91d340"
],
"aspects": ["16:9"],
"settings": { "target_duration_s": 90, "chronology": "strict" }
}Every group named has to be owned by your account - 404 not_found otherwise - and every one has to be ready, or the call is 422 group_not_ready with the offending ids in the detail. Four is a hard ceiling, not a soft one; a fifth id is 422 invalid_request.
In the plan that comes back, every entry names the group it came from, so a multi-recording edit stays legible after the fact:
{
"n": 3,
"role": "body",
"source_group": "9f4b1d2e-6c3a-4d51-8f0b-2a7c5e91d340",
"camera": "A",
"source_start_ms": 1426400,
"source_end_ms": 1458900,
"text": "It sits in the region you pick, and it never leaves it.",
"speaker": "A",
"speaker_name": "Priya Raman",
"tightening": [],
"pinned": false
}source_group plus the two timecodes is the whole provenance story: given the plan and the library, anyone can find the frames the edit used. Composition results documents the plan document field by field.
Multicam groups#
A group holds one to eight files, and how many of them may carry the camera role is a plan entitlement: one on Creator and Pro, two on Studio, four on Enterprise. Files with the camera role become angles, lettered from A in the order they were given; files with the audio role are separate recordings of the same event. The planner may cut between angles within a group, and each plan entry names the camera it used.
{
"id": "1c7e5a94-3b28-4f60-9d15-8a2f6c30e7b1",
"name": "Customer panel - March",
"status": "perceiving",
"asr_status": "complete",
"vad_status": "complete",
"sync_status": "pending",
"camera_count": 3,
"source_files": [
{ "id": "…", "role": "camera", "label": "wide", "status": "ready" },
{ "id": "…", "role": "camera", "label": "left", "status": "ready" },
{ "id": "…", "role": "camera", "label": "right", "status": "ready" },
{ "id": "…", "role": "audio", "label": "board mix", "status": "ready" }
]
}Perception reports three stages separately, and the third one only matters here:
asr_status- transcription and diarization.vad_status- speech and silence detection.sync_status- alignment across angles.
Multicam ingest is entitlement-gated: where billing enforcement is configured on, creating a group with more camera files than the plan allows is refused with 402 multicam_not_entitled. The camera count is fixed at creation - completing an upload later never re-checks it. Every camera file's minutes count towards the source-minute allowance: a 60-minute two-camera group is 120 source minutes.
Speakers#
GET/v1/source-groups/:id/speakers
Diarization labels every passage with who said it - A, B, and so on. This endpoint turns those labels into something you can act on:
GROUP_ID=1c7e5a94-3b28-4f60-9d15-8a2f6c30e7b1 curl -sS "https://anjin-media-api.fly.dev/v1/source-groups/$GROUP_ID/speakers" \ -H "authorization: Bearer $ANJIN_API_KEY"
{
"speakers": [
{
"label": "A",
"display_name": "Priya Raman",
"talk_time_ms": 1842300,
"segment_count": 214,
"sample_quotes": [
"It sits in the region you pick, and it never leaves it, and that is the whole answer to the question people are really asking.",
"We moved the whole archive in nine months, and the part that took longest was not the engineering."
]
},
{
"label": "B",
"display_name": null,
"talk_time_ms": 604800,
"segment_count": 88,
"sample_quotes": [
"So the question we get every single time is: where does our footage actually sit?",
"Let me push back on that, because our procurement team asks it differently."
]
}
]
}talk_time_msandsegment_count- how much each person actually said. On a panel this is the fastest way to find the person you are looking for.sample_quotes- the two longest things each label said, trimmed to 200 characters. Enough to identify someone without opening the footage.display_name-nulluntil you name them.
PATCH/v1/source-groups/:id/speakers
Naming is a map of label to name, 1 to 60 characters each. It needs the write scope and it is worth doing on anything you expect to cut from more than once:
curl -sS -X PATCH \
"https://anjin-media-api.fly.dev/v1/source-groups/$GROUP_ID/speakers" \
-H "authorization: Bearer $ANJIN_API_KEY" \
-H "content-type: application/json" \
-d '{ "names": { "A": "Priya Raman", "B": "Tom Alderton" } }'The response is the full speaker list again, with the names in place. From then on the planner sees those names in the transcript it reads, and every plan entry for that speaker carries speaker_name - so a brief can say “open on Priya” and mean it.
A label the group does not have is refused rather than created, so a typo cannot leave an orphan name behind:
{
"type": "https://anjin.media/errors/unknown_label",
"title": "Unknown Label",
"status": 422,
"detail": "unknown speaker label(s) for this group: C",
"code": "unknown_label"
}Labels are per group. The same person in three recordings is three labels and needs naming three times - diarization does not identify people across groups, it separates voices within one.
Searching transcripts#
Every ready group has a word-timed, speaker-labelled transcript, and it is readable in four formats: segments (the default, and what the planner reads), words, srt and vtt. That is what makes an archive queryable - you can find out what is in a recording without watching it.
Two calls are usually enough. First, what is in the library:
# Every group in the library that can be used in a cut curl -sS "https://anjin-media-api.fly.dev/v1/source-groups?limit=100" \ -H "authorization: Bearer $ANJIN_API_KEY" \ | jq -r '.data[] | select(.status == "ready") | "\(.id) \(.name)"'
Then, what a particular group says about something:
GROUP_ID=1c7e5a94-3b28-4f60-9d15-8a2f6c30e7b1
# Every passage in that group mentioning the phrase, with its timecode
curl -sS \
"https://anjin-media-api.fly.dev/v1/source-groups/$GROUP_ID/transcript?format=segments" \
-H "authorization: Bearer $ANJIN_API_KEY" \
| jq -r '.segments[]
| select(.text | test("data residency"; "i"))
| "\(.start_ms)ms \(.speaker) \(.text)"'The start_ms on each hit is a real source timecode, so a match is directly usable: it tells you the group is worth briefing against, and it tells a person where to look. Run the second call across the ids from the first and you have an archive search - over your own storage, with no index to maintain.
Asking before transcription has finished returns 409 not_ready with the stage in the detail rather than an empty result. Uploading video documents the formats in full.
Storage and usage#
GET/v1/usage
A library that is kept costs something to keep, and the meter for that is stored_gb_months. It is reported alongside six other totals:
curl -sS \ "https://anjin-media-api.fly.dev/v1/usage?from=2026-08-01T00:00:00Z&to=2026-09-01T00:00:00Z" \ -H "authorization: Bearer $ANJIN_API_KEY"
{
"camera_hours_ingested": 41.6,
"render_angle_hours": 12.4,
"plan_transcript_hours": 38.2,
"plan_count": 27,
"output_minutes_rendered": 63.5,
"egress_gb": 74.8,
"stored_gb_months": 218.4,
"minutes_plan": "plan_enterprise",
"minutes_remaining": null,
"low_minutes": false
}The last three fields in that response are not meters. minutes_plan, minutes_remaining and low_minutes are the additive usage envelope appended to billable responses across the API - informational, minutes_remaining is null on a custom Enterprise allowance, and all three are absent when the allowance cannot be read, so do not branch on them. A legacy credit account gets credits_remaining and low_credits instead. Composition results describes the pair. The seven above them are the usage totals:
| Field | Unit | What it counts |
|---|---|---|
camera_hours_ingested | hours | Footage taken in, counted per camera file rather than per group - the meter the source-minute allowance reads, in hours. |
render_angle_hours | hours | Angle-hours assembled by the renderer - the meter renders are measured on. |
plan_transcript_hours | hours | Transcript hours the planner read, summed over every plan version it wrote. |
plan_count | count | How many plan versions were committed in the window. |
output_minutes_rendered | minutes | Finished output length. Reported for context rather than metered on. |
egress_gb | GB | Bytes served out of storage - downloads, and the audio handed to transcription. |
stored_gb_months | GB-months | The library's own footprint: a point-in-time measurement of every object your account holds, recorded once per account per month across sources, derivatives and renders. |
from and to are ISO-8601 and the window is half-open - from inclusive, to exclusive. That is what lets consecutive months be added up without double-counting their shared boundary. Omit both for everything the account has ever done.
The response carries no prices. These are the quantities; what they cost is on pricing, and the deliberate separation means a figure here can never disagree with a figure there.
Deleting#
DELETE/v1/source-groups/:id
Deletion needs the admin scope and it reaches storage: the uploaded originals, the mezzanines, the extracted audio and the preview proxies are purged, and then the record. It is not reversible and there is no archive tier to move something into instead.
- A group that is
ingestingorperceivingrefuses with409 conflict- a worker is holding those files. Wait forreadyorfailed. - Cuts that drew on the group keep their plan documents, so the record of what was made survives. There is no footage left to render them from.
- Deleting a cut takes its renders and their sidecars with it; deleting one render leaves the plan untouched, so the same version can be rendered again.
Nothing expires on a timer. What you load stays until you delete it - there is no retention schedule and no lifecycle policy in the platform. Security & data handling covers where the bytes live in the meantime.
From here: Creating a composition is the full contract for briefing against the groups you picked, MCP & agents puts the same list, read and search calls in an agent's hands, and the archive overview makes the case for loading one in the first place.
