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.

  • 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, 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_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.