Spaces:
Sleeping
Download docs/api.md from GarchenArchive/ArchiveAI-TTS: direct link, hf CLI and curl.
- Browser
- Download file 7.1 kB
-
https://huggingface.co/spaces/GarchenArchive/ArchiveAI-TTS/resolve/main/docs/api.md
- Command line
-
hf download hf://spaces/GarchenArchive/ArchiveAI-TTS/docs/api.md
-
curl -L -o api.md https://huggingface.co/spaces/GarchenArchive/ArchiveAI-TTS/resolve/main/docs/api.md
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.
- Base URL (DO):
https://archiveai-tts.64-225-26-230.nip.io - Auth:
X-API-Token: <API_TOKEN>on everything except/api/ping./api/drainrequires an operator bearer token. - Style: submit returns immediately with a
job_id; poll for the result.
Routes are defined in 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). 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. |
Returns 202 with {"job_id": "...", "status": "queued"}.
# 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}.
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_jobsin/api/statusundercounts 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.