# Results

> Read transcripts as JSON, SRT, WebVTT or plain text, and scenes with captions, objects, palettes and shot types. What "not ready" and "unavailable" mean.

Source: https://www.framequery.com/docs/results (updated 2026-10-13)

Each billable stage has its own endpoint. Results are readable as soon as that stage's status is `completed`, whatever the other stage is doing.

| Endpoint | Returns |
|---|---|
| `GET /jobs/{jobId}/transcript` | The transcript as JSON, or `?format=srt`, `vtt` or `txt` as a file |
| `GET /jobs/{jobId}/scenes` | Detected scenes with captions |
| `GET /jobs/{jobId}/audioTracks` | Per-track transcripts of a multi-track job (stored form) |

## Transcript

```bash
curl "https://api.framequery.com/v1/api/jobs/$JOB_ID/transcript" \
  -H "Authorization: Bearer $FRAMEQUERY_API_KEY"
```

```json
{
  "data": {
    "jobId": "0b5e6c1e-6f43-4a0e-9d4c-2f7f3c1b9a10",
    "language": "en",
    "speakers": ["Speaker 1", "Speaker 2"],
    "text": "Morning. Did you see the forecast for tomorrow? Rain all day, apparently.",
    "segments": [
      { "startTime": 40.2, "endTime": 41.0, "text": "Morning.", "speaker": "Speaker 1" },
      { "startTime": 41.6, "endTime": 44.1, "text": "Did you see the forecast for tomorrow?", "speaker": "Speaker 2" },
      { "startTime": 44.5, "endTime": 46.3, "text": "Rain all day, apparently.", "speaker": "Speaker 1" }
    ]
  },
  "requestId": "5d7e1f0a3b9c4e28"
}
```

| Field | Notes |
|---|---|
| `language` | Detected language code, for example `en` |
| `speakers` | Every speaker label used by a segment, `"Speaker 1"`, `"Speaker 2"` and so on |
| `text` | The whole transcript: segment texts joined by single spaces |
| `segments[].startTime`, `endTime` | Seconds (float) from the start of the media |
| `segments[].text` | The words spoken in the segment |
| `segments[].speaker` | One entry of `speakers`, or `null` when diarisation could not attribute the segment |
| `segments[].speakerTag` | Deprecated numeric form of `speaker` (`"Speaker 2"` is `2`). Use `speaker` |
| `tracks`, `trackIndex`, `trackName` | Multi-track jobs; see below |

Speaker labels are consistent within a job, not across jobs: `Speaker 1` in one job is not the same person as `Speaker 1` in another. Word-level timestamps are not part of the beta API.

### SRT, WebVTT and plain text

The server renders caption files, so every client gets identical output. They are sent as downloads (`Content-Disposition: attachment`).

```bash
curl "https://api.framequery.com/v1/api/jobs/$JOB_ID/transcript?format=srt" \
  -H "Authorization: Bearer $FRAMEQUERY_API_KEY" -o interview.srt
```

```text
1
00:00:40,200 --> 00:00:41,000
Speaker 1: Morning.

2
00:00:41,600 --> 00:00:44,100
Speaker 2: Did you see the forecast for tomorrow?
```

```text
WEBVTT

00:00:40.200 --> 00:00:41.000
<v Speaker 1>Morning.

00:00:41.600 --> 00:00:44.100
<v Speaker 2>Did you see the forecast for tomorrow?
```

```text
Speaker 1: Morning.
Speaker 2: Did you see the forecast for tomorrow?
```

Segments without a speaker have no label. On a multi-track job the file formats need `track`; without it the request fails with `400 INVALID_REQUEST` and `param: "track"`.

### Multi-track transcripts

For a job with several audio tracks (see [Multi-track audio](https://www.framequery.com/docs/jobs#audio-tracks)):

- Without `track`, the JSON response adds `tracks[]`. Each entry has `trackIndex`, `trackName` (your `label`), `status`, `language`, `speakers`, `text`, `segments` and, when that track failed, `errorCode`.
- `?track=1` returns that one track in the top-level shape, with `trackIndex` and `trackName` set. A track that failed returns `404 RESULTS_UNAVAILABLE` with `reason: "track_failed"` and its `trackIndex`.

## Scenes

```bash
curl "https://api.framequery.com/v1/api/jobs/$JOB_ID/scenes" \
  -H "Authorization: Bearer $FRAMEQUERY_API_KEY"
```

```json
{
  "data": {
    "jobId": "0b5e6c1e-6f43-4a0e-9d4c-2f7f3c1b9a10",
    "scenes": [
      {
        "index": 0,
        "startTime": 0.0,
        "endTime": 6.4,
        "description": "Aerial view of a coastal town at sunrise with fishing boats moored in the harbour.",
        "objects": ["town", "harbour", "fishing-boat", "sea", "hills"],
        "colorPalette": ["#f2b56b", "#2f5d7c", "#c9d6df", "#8a4f3d"],
        "shotType": "extreme-wide",
        "shotAngle": "aerial"
      },
      {
        "index": 1,
        "startTime": 6.4,
        "endTime": 11.9,
        "description": "A fisherman in a yellow jacket coils rope on the deck of a small boat.",
        "objects": ["fisherman", "yellow-jacket", "rope", "boat-deck"],
        "colorPalette": ["#e3c13b", "#3e4a52", "#a7b3ba", "#6b4f3a"],
        "shotType": "medium",
        "shotAngle": "high-angle"
      }
    ]
  },
  "requestId": "a2c4e6f8b0d14f39"
}
```

| Field | Notes |
|---|---|
| `index` | Position in the list; scenes are ordered by `startTime` |
| `startTime`, `endTime` | Seconds (float) |
| `description` | One sentence describing the subject, action, setting and key visual details |
| `objects` | Up to about 10 visible objects as short lowercase labels (`"red-car"`). Labels, not bounding boxes |
| `colorPalette` | Dominant colours of the frame as hex strings, most dominant first |
| `shotType` | One of `extreme-wide`, `wide`, `full`, `medium-wide`, `medium`, `medium-close-up`, `close-up`, `extreme-close-up`, `over-the-shoulder`, `two-shot`, `group-shot`, `pov`, `insert`, `cutaway`, `reaction`, `aerial`, `silhouette`, `static-graphic` |
| `shotAngle` | One of `eye-level`, `low-angle`, `high-angle`, `dutch`, `aerial` |
| `startTs`, `endTs` | Deprecated aliases of `startTime` / `endTime` for SDK 0.1. They will be removed |

A video without scene cuts returns an empty `scenes` array. Treat unknown `shotType` or `shotAngle` values as plain strings: the vocabulary can grow.

## Not ready versus unavailable

The results endpoints tell you whether to wait or to stop:

| Response | Meaning | What to do |
|---|---|---|
| `200` | The stage completed | Use the data |
| `409` [`RESULTS_NOT_READY`](https://www.framequery.com/docs/errors#RESULTS_NOT_READY) | The stage is `pending` or `running` and may still complete. The body adds `state`, `stage`, `stageStatus` | Retry after `Retry-After` (15 s), or wait for the webhook |
| `404` [`RESULTS_UNAVAILABLE`](https://www.framequery.com/docs/errors#RESULTS_UNAVAILABLE) | The output does not exist and never will. The body adds `state`, `stage`, `stageStatus`, `reason`, and `errorCode` or `trackIndex` when relevant | Stop polling. Read `reason` |
| `404` [`NOT_FOUND`](https://www.framequery.com/docs/errors#NOT_FOUND) | No such job (or track) for this key | Check the id |

```json
{
  "error": "This job has no results for the requested stage and none will be produced.",
  "code": "RESULTS_UNAVAILABLE",
  "docUrl": "https://framequery.com/docs/errors#RESULTS_UNAVAILABLE",
  "state": "completed",
  "stage": "vision",
  "stageStatus": "skipped",
  "reason": "audio_only_input",
  "requestId": "f0e1d2c3b4a54968"
}
```

| `reason` | Why |
|---|---|
| `processing_mode` | The job's `processingMode` excludes this stage |
| `audio_only_input` | The input has no video, so there is no vision stage |
| `no_audio` | The input has no audio stream, so there is no transcript |
| `stage_skipped` | The stage was skipped (cause not recorded) |
| `stage_failed` | The stage failed; `errorCode` says why |
| `track_failed` | The requested audio track failed |
| `job_cancelled` | The job was cancelled before this stage ran |
| `job_expired` | The job expired before this stage ran |
| `job_failed` | Another stage failed before this one started |

## Embedded results and audio-track endpoints

`GET /jobs/{jobId}` embeds results under `processedData` (`scenes`, `transcript`, `speakers`, `language`, `length`) unless you pass `include=none`. The embedded transcript uses the stored entry form, which older clients read. New code should use the endpoints above.

`GET /jobs/{jobId}/audioTracks` and `GET /jobs/{jobId}/audioTracks/{trackIndex}` return each track's stored transcript (`trackIndex`, `trackName`, `status`, `transcript`, `speakers`, `language`, `errorCode`). The transcript endpoint's `tracks[]` and `?track=` cover the same data in the documented segment shape.
