# Subtitles agent guide

Shared service that times spoken audio against its known script and returns subtitle cues.
Used by Insight Tour and Puppet Sprites. Base URL: `https://subtitles.jessald.fairystack.com`.

## Authentication and billing

Send your own BrightWrapper API key as `X-API-Key`. The service forwards it to BrightWrapper's
fal route (ElevenLabs Scribe v2 speech-to-text), so the call is priced and recorded against you.
Agents in a Cyberpsychos session also send `X-FairyStack-Session`; it is forwarded. Jobs are
readable only with the key that created them. The service stores no key.

## Start a job

`POST /api/subtitles` (JSON, at most 25 MB of audio)

| Field | Meaning |
|---|---|
| `audio_base64` | The audio. Required |
| `mime_type` | Default `audio/mpeg`; also wav, mp4, aac, ogg, webm, flac |
| `text` | The exact script of a single-line clip, or |
| `lines` | `[{text, speaker?}]` in spoken order for a multi-line take |
| `spoken_as` | `{written: said}` for coined names the voice says differently (`{"Da5id": "David"}`) |
| `chars` | Phrase length limit, 12–84 (default 30) |
| `tail_ms` | How long a line's last phrase stays after its last word (default 900, capped at the next line) |
| `diarize` | Defaults to true when lines have more than one speaker |
| `project_name` | BrightWrapper project attribution (default `subtitles`) |

Returns `202` with `{id, status, step, poll_url, deadline_at}`, or `200` with the result when
the same audio and options were already timed (`cached: true`, no charge).

## Read a job

`GET /api/subtitles/<id>` with the same key. `status` is `queued`, `running`
(`step`: `transcribing`, `aligning`), `completed`, `failed` or `timed_out` (300 s deadline;
a job whose worker stops reporting for 90 s becomes `failed`). Poll every 2 s with a
per-request timeout. Failures carry `error` and the failed `step`; an alignment failure names
the lines it could not find in the audio.

A completed job has `result`:

- `lines`: `[{line, start_ms, end_ms}]`, each script line's span in the audio.
- `words`: `[{id, word, start_ms, end_ms}]`, the transcribed words (normalised).
- `cues`: `[{line, start_ms, end_ms, text, speaker?}]`, one short phrase per cue. Phrases split
  at punctuation once at least 14 characters, or before `chars`. Each cue starts when its first
  matched word is heard and ends when the next begins.
- `vtt`, `srt`: the same cues as WebVTT (speakers as `<v Name>`) and SRT (`Name: text`).
- `transcript`, `notes`: what was heard, and how unmatched lines were timed.

Cue text is the script, not the transcript, with `[audio tags]` removed.

## Rendering

The service returns timing only. Each app draws the cues in its own picture. The house style is
bold white text with a black outline and no box, centred near the bottom, one phrase at a time.
