Spaces:
Sleeping
Sleeping
File size: 7,095 Bytes
b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f b2d53f4 255731f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | # 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.
|