Automate subtitle generation from your scripts, CI pipelines, or backend. The API exposes the same endpoints the web app uses: paste a URL (or upload a file), get SRT/VTT/ASS files back. Pay-per-use with the same credits as your account.
1. Create an API key in Account → API keys. The full key (sk_...) is shown only once — store it safely.
2. Create a job from a YouTube URL and translate it to Italian:
curl -X POST https://api.subpix.app/api/v1/jobs \
-H "Authorization: Bearer sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "type": "url", "url": "https://youtube.com/watch?v=VIDEO_ID" },
"source_language": "auto",
"target_languages": ["it"],
"output_formats": ["srt", "vtt"]
}'3. The response contains a job_id. Poll it until status is done, then download the files from outputs.
Every request needs your API key in the Authorization header:
Authorization: Bearer sk_YOUR_KEY
Keys are tied to your account and spend your credits. You can have up to 10 active keys and revoke any of them at any time from the Account page. Only a hash of the key is stored on our side — if you lose it, create a new one.
POST /jobs — the source object accepts three types:
// Single video URL (YouTube or Vimeo)
{ "type": "url", "url": "https://youtube.com/watch?v=..." }
// Playlist or channel (one job per video, max 30)
{ "type": "playlist", "url": "https://youtube.com/playlist?list=...",
"video_urls": ["https://youtube.com/watch?v=aaa", "..."] }
// Direct file upload (use POST /uploads/presigned first)
{ "type": "upload", "upload_id": "uuid-from-upload-confirm" }Other fields:
source_language — ISO code or "auto" (detected from the audio)target_languages — up to 5 ISO codes, e.g. ["it", "es"]. See GET /languagesoutput_formats — ["srt"], ["vtt"] or bothfilename_format — "title" (default) or "video_id"Response 201:
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"estimated_credits": 15,
"created_at": "2026-06-12T10:00:00Z"
}GET /jobs/:id — poll every few seconds while status is queued | downloading | transcribing | translating | packaging. When it's done, the response includes download URLs:
{
"id": "...",
"status": "done",
"credits_used": 15,
"files_expire_at": "2026-06-13T10:00:00Z",
"outputs": [
{
"language": "it",
"language_name": "Italian",
"files": [
{ "format": "srt", "download_url": "https://...", "expires_at": "..." },
{ "format": "vtt", "download_url": "https://...", "expires_at": "..." }
]
}
]
}Files expire 24 hours after completion. Download them promptly or re-request the job detail for fresh presigned URLs while they exist. Prefer webhooks over tight polling when possible.
Resolve a playlist or channel URL first with GET /video/info?url=... — it returns the video list with durations. Then submit the videos you want (max 30 per batch) as a playlist job:
curl "https://api.subpix.app/api/v1/video/info?url=https://youtube.com/playlist?list=XXX" \
-H "Authorization: Bearer sk_YOUR_KEY"
# -> { "type": "playlist", "videos": [ { "url": "...", "duration_seconds": 332, ... } ] }The playlist response from POST /jobs includes playlist_id and job_count. Once jobs complete, download everything in one go: GET /jobs/playlist/:playlist_id/download returns a ZIP.
Tip: calling /video/info first also enables the precise upfront credit check (durations are cached server-side for 12 hours).
Instead of polling, configure a webhook in Account → Webhook. We POST job.completed and job.failed events:
{
"event": "job.completed",
"created_at": "2026-06-12T10:00:00+00:00",
"data": {
"job_id": "...", "status": "done", "video_title": "...",
"duration_seconds": 872, "target_languages": ["it"],
"credits_used": 15, "playlist_id": null
}
}Each request is signed: the X-Subpix-Signature header contains sha256=<hex>, the HMAC-SHA256 of the raw body with your webhook secret. Verify it like this (Python):
import hmac, hashlib
def verify(body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(header.removeprefix("sha256="), expected)Delivery is best-effort with a 10s timeout and no retries — keep polling as a fallback for critical flows. The secret is shown once when you save the URL; saving again regenerates it.
1 credit = 1 minute of source audio × target language, rounded up per video. Credits are only charged when a job completes successfully — failed jobs cost nothing. Check your balance with GET /credits.
Common error responses:
401 — missing/invalid/revoked API key402 — insufficient credits (the message says how many are needed)404 — video not found or private429 — too many active jobs (max 5; playlist batches are exempt) or rate limit (20 job creations/min)Errors are JSON: { "detail": "..." }.
sk_ key. Need help? Contact support