API Quickstart
Buy API credits, create an API key, upload media to a signed URL, start an async job, and poll the result. The current API supports extract and stt jobs.
stt payload, and the subtitleArea guide.Send Authorization: Bearer <api-key> on every API request.
API credits are charged from the server-probed media duration after upload: max(1, ceil(billableSeconds / 60)). Optional client durationSeconds is only an estimate. Web product billing is unchanged.
Max file size is 1GB. Request a signed upload URL, then PUT the file. Content-Type and body byte length must match the values sent to POST /uploads. External video URLs are not supported.
Webhook callbacks are not available in this version. Poll the job endpoint until the status is final.
/api/v1/uploads/api/v1/jobs/api/v1/jobs/:idjob.result.segments.statusMessage and requestId.Authentication and base URL
All requests use the same base URL and Bearer authentication. Every successful and failed response includes both the X-Request-Id response header and a matching requestId field in the JSON body.
https://www.subextractor.com/api/v1Authorization: Bearer sub_sk_live_...End-to-end workflow
The typical integration is request upload URL → upload file → create job → poll for result. The examples below follow that order.
curl -X POST 'https://www.subextractor.com/api/v1/uploads' \
-H 'Authorization: Bearer sub_sk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"fileName": "demo.mp4",
"contentType": "video/mp4",
"sizeBytes": 12345678
}'{
"requestId": "req_123",
"fileKey": "uploads/<YOUR_USER_ID>/api/abc-demo.mp4",
"uploadUrl": "https://storage.example.com/...",
"expiresAt": "2026-04-26T12:00:00.000Z"
}uploadUrl. Keep the fileKey for the next step.curl -X PUT "$UPLOAD_URL" \
-H 'Content-Type: video/mp4' \
-H 'Content-Length: 12345678' \
--upload-file ./demo.mp4Content-Typemust equal thecontentTypeused inPOST /uploads.- Body byte length must equal
sizeBytes(presign bindsContentLength). uploadUrlexpires in 3600 seconds (expiresAt).- Browser clients: set only
Content-Typeand send theFile/Blobbody — do not setContent-Lengthmanually. - External
https://…video URLs are not accepted as job input. Upload first.
Idempotency-Key so retried requests do not create duplicate jobs. Use the real $FILE_KEY returned by step 1 — never a placeholder path.curl -X POST 'https://www.subextractor.com/api/v1/jobs' \
-H 'Authorization: Bearer sub_sk_live_...' \
-H 'Idempotency-Key: 8d9d3b2f-f2f1-4a74-a8e8-55d7f7d4e6e3' \
-H 'Content-Type: application/json' \
-d '{
"jobType": "extract",
"fileKey": "$FILE_KEY",
"fileName": "demo.mp4",
"fileType": "video/mp4",
"fileSize": 12345678,
"durationSeconds": 141,
"subtitleArea": { "x": 240, "y": 760, "width": 1440, "height": 220 },
"language": "en",
"startTime": 0,
"endTime": 120
}'curl -X POST 'https://www.subextractor.com/api/v1/jobs' \
-H 'Authorization: Bearer sub_sk_live_...' \
-H 'Idempotency-Key: 2d8d5d4e-5a35-41e0-b5e4-e14fbaf0b5e9' \
-H 'Content-Type: application/json' \
-d '{
"jobType": "stt",
"fileKey": "$FILE_KEY",
"fileName": "demo.mp4",
"fileType": "video/mp4",
"fileSize": 12345678,
"durationSeconds": 141,
"language": "en"
}'jobType:extractorsttfileKey,fileName,fileType,fileSizesubtitleAreaforextract(required; width/height > 0). Example values assume 1920×1080 — scale for other frame sizes.
durationSecondsestimate only — final API charge uses server probe (andstartTime/endTimewindow when set)languagewhen you already know the source languagestartTimeandendTimeto process part of the media- For
sttjobs, omitsubtitleArea
How to set subtitleArea
subtitleArea defines the subtitle region of interest for extract jobs. The values use the original video's pixel coordinate system. Send raw pixel numbers, not normalized values between 0 and 1.
{
"x": 240,
"y": 760,
"width": 1440,
"height": 220
}x: distance from the left edge of the full video framey: distance from the top edge of the full video framewidth: width of the subtitle regionheight: height of the subtitle region
Open the normal extractor UI, upload a representative video, switch to subtitle extraction mode, and draw the subtitle box visually.
In the configuration summary, the app already shows the selection as x,y (width×height). Copy those integers directly into your API payload.
The top-left corner of the frame is (0, 0). Coordinates grow to the right and downward.
Use integer pixel values. Decimal values may be rounded internally by the processing pipeline.
Keep the full box inside the video frame: x + width ≤ frame width and y + height ≤ frame height.
For standard burned-in subtitles, start with a wide lower-third box near the bottom of the frame. Make the box slightly taller than the visible text so OCR does not clip descenders or dual-line subtitles.
Start with a lower-third box that covers about 80% to 90% of the frame width and about 10% to 15% of the frame height near the bottom. Then tighten the box after you inspect the first OCR result.
job.result.segments. Jobs can also end as failed with a statusMessage after create succeeds — always poll to the terminal state.curl -X GET 'https://www.subextractor.com/api/v1/jobs/job_123' \
-H 'Authorization: Bearer sub_sk_live_...'{
"requestId": "req_123",
"job": {
"id": "job_123",
"jobType": "extract",
"status": "succeeded",
"progress": 1,
"statusMessage": null,
"createdAt": "2026-04-26T12:00:00.000Z",
"updatedAt": "2026-04-26T12:03:00.000Z",
"result": {
"segments": [
{ "text": "Hello", "start": 0.1, "end": 1.7 }
]
}
}
}$FILE_KEY. Requires curl, jq, and ffprobe. Prefer the online playground for browser联调.#!/usr/bin/env bash
set -euo pipefail
API_KEY="sub_sk_live_..."
BASE="https://www.subextractor.com/api/v1"
FILE="./demo.mp4"
CONTENT_TYPE="video/mp4"
SIZE_BYTES=$(wc -c < "$FILE" | tr -d ' ')
# durationSeconds is optional (estimate only). Server probes media and bills after upload.
DURATION=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$FILE" || echo 60)
UPLOAD_JSON=$(curl -sS -X POST "$BASE/uploads" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"fileName\":\"$(basename "$FILE")\",\"contentType\":\"$CONTENT_TYPE\",\"sizeBytes\":$SIZE_BYTES}")
FILE_KEY=$(echo "$UPLOAD_JSON" | jq -r .fileKey)
UPLOAD_URL=$(echo "$UPLOAD_JSON" | jq -r .uploadUrl)
curl -sS -X PUT "$UPLOAD_URL" \
-H "Content-Type: $CONTENT_TYPE" \
-H "Content-Length: $SIZE_BYTES" \
--upload-file "$FILE" >/dev/null
JOB_JSON=$(curl -sS -X POST "$BASE/jobs" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d "{\"jobType\":\"extract\",\"fileKey\":\"$FILE_KEY\",\"fileName\":\"$(basename "$FILE")\",\"fileType\":\"$CONTENT_TYPE\",\"fileSize\":$SIZE_BYTES,\"durationSeconds\":$DURATION,\"subtitleArea\":{\"x\":240,\"y\":760,\"width\":1440,\"height\":220}}")
JOB_ID=$(echo "$JOB_JSON" | jq -r .job.id)
while true; do
STATUS_JSON=$(curl -sS -H "Authorization: Bearer $API_KEY" "$BASE/jobs/$JOB_ID")
STATUS=$(echo "$STATUS_JSON" | jq -r .job.status)
echo "status=$STATUS"
if [[ "$STATUS" == "succeeded" || "$STATUS" == "failed" ]]; then
echo "$STATUS_JSON" | jq .
break
fi
sleep 2
doneReuse the same Idempotency-Key only for the exact same job payload. If the payload changes, the API returns 409 idempotency_conflict.
Current limits are 30 upload requests per minute, 20 job creation requests per minute, and 120 job status requests per minute for the same API key.
Recommended: server-side or curl. Browser calls to /api/v1 and storage PUT work from allowlisted app origins (same-origin playground). Third-party browser sites are not supported. Storage CORS is per-region allowlist — not open to arbitrary customer domains.
External responses return segments. You do not need to understand the platform's internal job event structure.
401 invalid_api_keyMissing Bearer, invalid secret, or revoked key.
403 api_key_revoked_or_forbidden_userKey owner is banned or anonymous (code name is historical; revoked keys are 401).
402 insufficient_api_creditsNot enough API credits for the server-measured duration (may appear as a failed job after create, once probe completes).
404 job_not_foundJob id does not exist or is not an API job owned by this key's user.
409 idempotency_conflictThe same Idempotency-Key was reused with a different request body.
413 file_too_largeUpload or job file exceeds 1GB (maxSizeBytes in body).
422 invalid_payloadMissing fields, bad fileKey owner prefix, missing extract subtitleArea, or missing Idempotency-Key.
429 rate_limit_exceededSlow down and retry shortly. Keep the requestId when you contact support.
Log HTTP status and requestId / X-Request-Id. If create returns 200 but the job fails later, poll and read statusMessage.