---
title: "Analyse a media item's transcript"
method: POST
path: "/v1/media/{id}/transcript/analyze"
tags: ["media"]
---

# Analyse a media item's transcript

`POST /v1/media/{id}/transcript/analyze`

## Request

- Base URL: `https://api.cueframe.ai`
- URL: `https://api.cueframe.ai/v1/media/{id}/transcript/analyze`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.cueframe.ai/api/auth/mcp/authorize`, token URL `https://api.cueframe.ai/api/auth/mcp/token`, scopes: `media:write`)
  - API key in header `PAYMENT-SIGNATURE`

Ask ONE editorial question of a transcript and get the answer as text: `summary` (what is actually said, plus the takeaways), `chapters` (where the subject changes), or `highlights` (the moments that stand alone as a short). PRECISION: timestamps are ABSOLUTE seconds from the start of the media and every one is verified against the transcript before you see it (a fabricated timestamp fails the call rather than shipping) — but they are LINE-START markers roughly 15s apart, so a boundary can sit up to 15s BEFORE the moment you asked for. Treat every span as APPROXIMATE: use it to LOCATE the moment, then refine the in/out against the word timings from GET /v1/media/{id}/context before you cut. Feeding a raw span straight into a clip trim will start it mid-sentence. Pass `window` to analyse one slice; omit it for the whole thing. This is a METERED LLM call, not a read: it bills on measured token spend, and a recording too long to analyse in one pass is rejected with 422 `transcript_too_long` (over ~100k characters, roughly two hours of speech) rather than silently summarising only its first half — narrow it with `window`. 422 `media_no_transcript` means the item has no speech on file (or none inside your window); GET /v1/media/{id}/context shows transcript state, and GET /v1/media/{id}/transcript returns the raw words.

## Path parameters

- `id` string, required

## Request body

- TranscriptAnalyzeRequest: One editorial question about a media item's transcript.
  - `task` 'summary' | 'chapters' | 'highlights', required: What to ask of the transcript. `summary` — prose: the argument plus the concrete takeaways as bullets. `chapters` — one line per subject change, `[h:mm:ss] Title — description`. `highlights` — up to 8 standalone moments, strongest first, one line each as `[h:mm:ss]-[h:mm:ss] Title — why it lands` plus the line that carries it. Every timestamp is ABSOLUTE seconds from the start of the media, including when you pass a `window`, and every one is verified against the transcript before you see it — but they are LINE-START markers about 15s apart, so treat a span as APPROXIMATE (up to 15s early) and refine it against the word timings from GET /v1/media/{id}/context before you cut.
  - `window` object: Analyse only this slice of the transcript. Omit to analyse all of it. Words are windowed exactly as captions.fromTranscript windows them (a word straddling an edge is clamped, a word wholly outside is dropped). Use this when a long recording is rejected with `transcript_too_long`, and to chapter a single segment of a podcast without paying for the whole thing.
    - `startSec` number, required: Window start, SECONDS from the start of the media.
    - `endSec` number, required: Window end, SECONDS from the start of the media.

## Response `200`

The analysis.

- TranscriptAnalyzeResponse: The analysis of one media item's transcript. Metered: this is a real LLM call, billed on measured token spend, so it is not a read to poll.
  - `task` 'summary' | 'chapters' | 'highlights', required: Echo of the requested task — so a batched caller can pair answers to asks.
  - `result` string, required: The answer, as text. `summary` is prose; `chapters` and `highlights` are one record per LINE in the shape documented on the request's `task` field — split on newlines to parse them. Timestamps are absolute seconds from the start of the media and every one is CHECKED against the transcript before the response is returned (a fabricated timestamp fails the call rather than shipping), but they are line-start markers about 15s apart — a boundary can sit up to 15s BEFORE the moment you want. Use them to locate, not to cut: refine against the word timings in GET /v1/media/{id}/context.

## Other responses

- `400`: Bad request — validation_error / invalid_cursor / invalid_query.
- `401`: Missing or invalid Authorization header.
- `402`: Payment required — billing_required or x402 payment_*.
- `403`: Forbidden — permission insufficient.
- `404`: Resource not found.
- `422`: Unprocessable — invalid_composition / media_in_use / session_in_use / render_not_cancellable.
- `429`: Rate limited. Retry-After header present.
- `500`: Internal error.
- `503`: Service unavailable — billing_unavailable / honest-503 for runtime deps.

---

[API](https://skmtc.dev/cueframe/apis/cueframe-api.md) · [All operations](https://skmtc.dev/cueframe/apis/cueframe-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cueframe/apis/cueframe-api/revisions/8f9bfe362b4a?raw)
