TimedSubs API

API documentation for subtitle automation

Public docs for Studio and Agency private-beta integrations. Learn the auth model, upload flow, job status, export gates, usage, webhooks, and safety boundaries before creating a workspace key.

Public docs, private access

Reading this page does not grant API access. API keys, usage, webhook secrets, signed URLs, Try-it actions, project data, and all API calls remain authenticated, permissioned, and plan-gated.

What is public

Endpoint behavior, examples, auth headers, idempotency, errors, rate limits, webhook signing, and export readiness rules.

What needs sign-in

API key creation, usage records, webhook secrets, project data, workspace settings, and account-specific Try-it actions.

What stays closed

Internal admin routes, provider credentials, ops health, worker control endpoints, raw storage keys, and non-signed file access.

Required headers

Keep keys on your server. Do not put API keys in browser code, mobile apps, client logs, support tickets, screenshots, or public repositories.

Authorization: Bearer <TIMEDSUBS_API_KEY>
Content-Type: application/json
Idempotency-Key: <stable-unique-operation-key>

Reusing an idempotency key with the same body can replay with `X-Idempotency-Replayed: true` and `idempotencyReplayed: true`. Upload-sign replays keep the same file and return a freshly signed upload URL; other endpoints replay the stored response. Reusing a key with a different body returns `409 idempotency_key_conflict`.

curl example

A minimal project creation request with authentication and idempotency.

curl -sS "$BASE_URL/api/v1/projects" \
  -H "Authorization: Bearer $TIMEDSUBS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: project-client-reel-20260602-001" \
  -d '{
    "name": "Client reel subtitles",
    "sourceLanguage": "en",
    "targetLanguages": ["zh"],
    "durationSeconds": 180
  }'

Recommended call order

1POST /api/v1/webhooks
2POST /api/v1/projects
3POST /api/v1/projects/{projectId}/uploads/sign
4PUT {upload.uploadUrl}
5POST /api/v1/projects/{projectId}/uploads/confirm
6POST /api/v1/projects/{projectId}/source-subtitle
7POST /api/v1/projects/{projectId}/jobs
8GET /api/v1/jobs/{jobId}
9GET /api/v1/projects/{projectId}
10GET /api/v1/projects/{projectId}/exports
11GET /api/v1/projects/{projectId}/exports/{format}
12GET /api/v1/usage

Inputs, outputs, and examples

POST

/api/v1/projects

Create a script + audio project, or a subtitle-file translation project with `mode: "translate"`.

Input

{ "name": "Client reel subtitles", "mode": "script_audio", "sourceLanguage": "en", "targetLanguages": ["zh"], "durationSeconds": 180 }

Output

{ "project": { "id": "project-id", "mode": "script_audio", "sourceLanguage": "en" } }

POST

/api/v1/projects/{projectId}/uploads/sign

Request a signed upload URL for audio, video, script, or subtitle assets.

Input

{ "kind": "audio", "filename": "voiceover.wav", "contentType": "audio/wav", "sizeBytes": 8234567 }

Output

{ "upload": { "uploadUrl": "https://signed-upload-url", "objectKey": "workspaces/..." }, "file": { "id": "file-id", "uploaded": false } }

POST

/api/v1/projects/{projectId}/source-subtitle

Parse a confirmed subtitle file into source cues for translation projects; required before a translate job.

Input

{ "filename": "source.srt", "contentType": "application/x-subrip", "text": "1\n00:00:00,000 --> 00:00:02,000\nHello world." }

Output

{ "project": { "id": "project-id", "status": "ready", "sourceLanguage": "en" }, "source": { "cueCount": 1, "durationSeconds": 2 } }

POST

/api/v1/projects/{projectId}/jobs

Accept or enqueue alignment/translation through the background worker path.

Input

{ "type": "align" }

Output

{ "jobId": "job-id", "reused": false, "dispatchState": "enqueued", "enqueued": true }

GET

/api/v1/jobs/{jobId}

Poll job status.

Input

No request body.

Output

{ "job": { "id": "job-id", "status": "processing", "projectId": "project-id" } }

GET

/api/v1/projects/{projectId}/exports

List source and translation exports, including signed download URLs when ready.

Input

No request body.

Output

{ "exports": [{ "format": "srt", "status": "ready", "delivery": { "blocking": false }, "downloadUrl": "https://timedsubs.com/api/v1/..." }, { "format": "srt", "status": "blocked", "delivery": { "blocker": { "category": "timing_issue" } }, "downloadUrl": null }] }

POST

/api/v1/webhooks

Register or update a public HTTPS callback.

Input

{ "url": "https://example.com/timedsubs/webhook", "events": ["job.status", "export.ready"], "enabled": true }

Output

{ "webhook": { "id": "webhook-id" }, "signing": { "algorithm": "HMAC-SHA256" }, "signingSecret": "<shown-once>" }

GET

/api/v1/usage

Read usage and entitlement state for the API key workspace.

Input

No request body.

Output

{ "usage": { "alignmentMinutes": 120, "translationMinutes": 45 }, "entitlements": { "planId": "studio" }, "access": { "accessSource": "workspace_plan", "opsFullAccess": false } }

Webhook signing

TimedSubs sends `timedsubs-webhook-signature` and `timedsubs-webhook-timestamp`. Verify `HMAC-SHA256` over `timestamp.body` using `SHA-256(signingSecret)` as the key.

Rate limits

Authenticated API responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`. Treat `rate_limited` as retry-after work, not a completed job.

Common errors

Expect `api_key_invalid` for revoked keys, `api_scope_forbidden` for missing scopes, source `blocked` or direct `export_blocked_by_qa` for hard source delivery blockers, and translation `export_blocked` when translated subtitle timing is invalid. Only hard timing or export blockers remove download URLs.