ArchiveAI-TTS / docs /api.md
billingsmoore's picture
Rewrite docs for terser, more austere style (no content changes)
255731f
|
Raw History Blame Contribute Delete
7.1 kB
# REST API
Async job API alongside the Gradio UI, for callers that need synthesis drafts without a human opening the browser β€” a CMS, or the [Orchestrator](../../Orchestrator/docs/api.md).
- **Base URL (DO)**: `https://archiveai-tts.64-225-26-230.nip.io`
- **Auth**: `X-API-Token: <API_TOKEN>` on everything except `/api/ping`. `/api/drain` requires an operator bearer token.
- **Style**: submit returns immediately with a `job_id`; poll for the result.
Routes are defined in [`server/api_jobs.py`](../server/api_jobs.py), the source of truth if this drifts.
## Auth
`/api/*` is carved out of the droplet's Google OAuth gate so a non-browser caller can reach it, making `X-API-Token` the entire perimeter for these paths (see [the droplet security model](../../Orchestrator/docs/security.md#4-the-api-carve-out)). The check fails closed: an unset `API_TOKEN` gives `503`, never an open API. Comparison is constant-time.
| Status | Meaning |
|---|---|
| `401` | Missing or wrong `X-API-Token`. |
| `503` | `API_TOKEN` isn't configured β€” a deployment problem, not a caller problem. |
## Routes
| Method | Path | Auth | Purpose |
|---|---|---|---|
| `GET` | `/api/ping` | none | Static `{"status":"ok"}`. |
| `POST` | `/api/jobs` | token | Submit a turn-generation + synthesis job. |
| `GET` | `/api/jobs/{job_id}` | token | Poll status/result. |
| `GET` | `/api/jobs?status=` | token | List jobs, optionally filtered. |
| `GET` | `/api/projects` | token | List saved projects. Poll to detect UI edits. |
| `GET` | `/api/projects/{project_id}` | token | One project's full saved state. |
| `GET` | `/api/status` | token | `{active_jobs, draining}`. |
| `POST` | `/api/drain` | operator | Stop accepting new jobs before a deploy. |
---
## `POST /api/jobs`
`multipart/form-data`. Runs the same pipeline Turn Review's UI runs on upload: parse captions into cues β†’ get a speaker-turn plan β†’ build one segment per turn β†’ synthesise audio for every non-mantra turn.
### Input
| Field | Required | Notes |
|---|---|---|
| `caption_file` | yes | The translated captions (`.vtt`/`.srt`). |
| `turns_file` | optional | A pre-computed `turns.json`. Supplying this skips the Gemini turn-plan call: cheaper, deterministic, and the way to re-run synthesis on an approved plan. |
| `synthesize_audio` | optional (default `true`) | `false` produces the turn plan and segments without calling ElevenLabs. Use to review splits first. |
### Synthesis parameters
All optional. Same knobs the UI exposes.
| Field | Default | Notes |
|---|---|---|
| `model_label` | `Multilingual v2` | ElevenLabs model, by label (see `tts/core/turn_audio.py`'s `MODELS`). |
| `speed` | `1.0` | |
| `comma_pause` | `0.3` | Seconds inserted at commas. |
| `period_pause` | `0.5` | Seconds inserted at sentence ends. |
| `stability` | `0.75` | |
| `similarity_boost` | `0.75` | |
| `style` | `0.05` | |
### Keys, naming, callbacks
| Field | Notes |
|---|---|
| `gemini_api_key` | Per-job override for turn-plan generation. Sanitised before storage. |
| `elevenlabs_api_key` | Per-job override for synthesis. Sanitised before storage. |
| `label` | Project name; defaults to `untitled`. |
| `project_id` | Write into an existing project. Must match `^[A-Za-z0-9_-]+$`. |
| `webhook_url` | POSTed to on completion. See [Webhooks](#webhooks). |
Returns `202` with `{"job_id": "...", "status": "queued"}`.
```bash
# Turn plan only β€” no ElevenLabs spend, for reviewing splits first
curl -X POST https://archiveai-tts.64-225-26-230.nip.io/api/jobs \
-H "X-API-Token: $API_TOKEN" \
-F 'caption_file=@captions.vtt' \
-F 'synthesize_audio=false' \
-F 'label=GR_0150-A'
# Synthesise from an already-approved turn plan (skips Gemini)
curl -X POST https://archiveai-tts.64-225-26-230.nip.io/api/jobs \
-H "X-API-Token: $API_TOKEN" \
-F 'caption_file=@captions.vtt' -F 'turns_file=@turns.json' \
-F 'project_id=gr-0150-a-3f2a'
```
`synthesize_audio=true` calls Gemini once for the turn plan and ElevenLabs once per non-mantra segment. On a full teaching, cost is material. Prefer `synthesize_audio=false` first, review the plan, then re-submit with `turns_file`.
## `GET /api/jobs/{job_id}`
Same shape as the sibling apps: `status` of `queued` | `running` | `done` | `failed`, plus `params` (secrets masked), `progress`, `webhook_delivery`, and on success a `result` carrying the `project_id`.
A `404` is permanent. Job state is in-memory, purged an hour after last update, and lost on restart. Resubmit rather than retry. The project is durable β€” `result.project_id` references it, and it includes the generated audio.
## `GET /api/projects` β€” the edit-detection endpoint
`id`, `name`, `updated_at` per saved project. `updated_at` bumps on every UI edit (`app.py`'s `_autosave_project`); polling this is how a caller detects human edits. There is no push notification out of this app, by design. The Orchestrator polls this every 60s and exposes a merged feed at `/api/changes`.
## `GET /api/projects/{project_id}`
The full saved project: settings, segments, per-turn text, timestamps, rationale, and references to each turn's generated audio. `project_id` is validated against `^[A-Za-z0-9_-]+$`; anything else is `422`. That validation matters more here than in TranslationUI: this app's local-file backend builds real filesystem paths from the value.
## `GET /api/status` and `POST /api/drain`
`/api/status` returns `{"active_jobs": n, "draining": bool}`.
```bash
curl -X POST https://archiveai-tts.64-225-26-230.nip.io/api/drain \
-H "Authorization: Bearer $HF_TOKEN"
```
The bearer value is whatever `HF_TOKEN` is set to on this app, reusing an existing operator value rather than adding a new secret. Draining is in-memory and clears on restart.
## Concurrency: the UI is not on the same queue
Unlike TranslationUI and ArchiveAI-STT, this app's UI synthesis handlers are not wrapped through the job store's semaphore. The UI has three separate Generate/Generate All call sites plus a background sweeper thread; wrapping all of them was out of scope when the API was added.
Consequences:
- API jobs are serialised against each other by `MAX_CONCURRENT_JOBS`.
- API jobs and UI-triggered synthesis can run concurrently, competing for the same ElevenLabs rate limit.
- `active_jobs` in `/api/status` undercounts real work: a human clicking Generate All in the UI does not show up there.
`/api/drain` before a deploy stops API submissions but does not stop a UI batch already running. Confirm the UI is not in use before deploying.
## Webhooks
Supply `webhook_url` and a JSON POST β€” same body as `GET /api/jobs/{job_id}` β€” is sent on `done` or `failed`, with the outcome in `webhook_delivery`.
The destination is re-resolved and IP-pinned immediately before connecting, not merely validated at submission. Synthesis jobs are long-running, so the gap between submission and delivery is exactly the DNS-rebinding window this closes. Private, loopback, and link-local destinations are rejected. A blocked delivery marks `webhook_delivery: "failed"`; the job still completes.