Developer API

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.

Back to API console
01
Buy API credits
API credits are isolated from web credits. A job reserves credits before processing starts.
02
Create an API key
Generate the key in the API console. The raw token is shown once, so store it immediately.
03
Upload before processing
Request a signed upload URL first. Then upload your media directly to storage with a PUT request.
04
Poll for completion
Jobs are asynchronous. Poll the job endpoint until the status changes to succeeded or failed.
Jump to what you need
Most users only need the workflow, the stt payload, and the subtitleArea guide.
Before you send the first request
The API is designed for registered users with prepaid API credits. Web credits do not work here.
Authentication

Send Authorization: Bearer <api-key> on every API request.

Billing

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.

Upload flow

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.

Async processing

Webhook callbacks are not available in this version. Poll the job endpoint until the status is final.

Supported endpoints
POST
/api/v1/uploads
Create signed upload URL
Returns a temporary upload URL (1 hour) plus the fileKey. Max size 1GB. URL expires at expiresAt.
POST
/api/v1/jobs
Create async extract or STT job
Requires Idempotency-Key and our fileKey. durationSeconds is optional (estimate). extract requires subtitleArea. Credits reserved after server media probe.
GET
/api/v1/jobs/:id
Poll job status
Returns the latest status and the final result payload when processing has finished.
Job lifecycle
pending
Accepted and queued.
pending
processing
Worker is actively running.
processing
succeeded
Result is available in job.result.segments.
succeeded
failed
Check statusMessage and requestId.
failed

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.

Request template
Replace the example API key with your real secret key.
Base URL
https://www.subextractor.com/api/v1
Authorization header
Authorization: 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.

1. Create a signed upload URL
Send the original file name, content type, and file size in bytes.
POST /api/v1/uploads
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
  }'
Response example
{
  "requestId": "req_123",
  "fileKey": "uploads/<YOUR_USER_ID>/api/abc-demo.mp4",
  "uploadUrl": "https://storage.example.com/...",
  "expiresAt": "2026-04-26T12:00:00.000Z"
}
2. Upload the media file
Use the returned uploadUrl. Keep the fileKey for the next step.
PUT binary file to storage (curl/server)
curl -X PUT "$UPLOAD_URL" \
  -H 'Content-Type: video/mp4' \
  -H 'Content-Length: 12345678' \
  --upload-file ./demo.mp4
PUT constraints
  • Content-Type must equal the contentType used in POST /uploads.
  • Body byte length must equal sizeBytes (presign binds ContentLength).
  • uploadUrl expires in 3600 seconds (expiresAt).
  • Browser clients: set only Content-Type and send the File/Blob body — do not set Content-Length manually.
  • External https://… video URLs are not accepted as job input. Upload first.
3. Create the processing job
Provide a unique Idempotency-Key so retried requests do not create duplicate jobs. Use the real $FILE_KEY returned by step 1 — never a placeholder path.
POST /api/v1/jobs (extract)
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
  }'
POST /api/v1/jobs (stt)
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"
  }'
Required fields
  • jobType: extract or stt
  • fileKey, fileName, fileType, fileSize
  • subtitleArea for extract (required; width/height > 0). Example values assume 1920×1080 — scale for other frame sizes.
Optional fields
  • durationSeconds estimate only — final API charge uses server probe (and startTime/endTime window when set)
  • language when you already know the source language
  • startTime and endTime to process part of the media
  • For stt jobs, omit subtitleArea

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.

subtitleArea visual guide
Example video frame: 1920 × 1080, measured in pixels from the top-left corner.
origin (0, 0)x = 240y = 760width = 1440height = 220subtitleAreafull frame: 1920 × 1080
subtitleArea example
{
  "x": 240,
  "y": 760,
  "width": 1440,
  "height": 220
}
Field meanings
  • x: distance from the left edge of the full video frame
  • y: distance from the top edge of the full video frame
  • width: width of the subtitle region
  • height: height of the subtitle region
Fastest way: use the web UI

Open the normal extractor UI, upload a representative video, switch to subtitle extraction mode, and draw the subtitle box visually.

Then read the exact values

In the configuration summary, the app already shows the selection as x,y (width×height). Copy those integers directly into your API payload.

Coordinate origin

The top-left corner of the frame is (0, 0). Coordinates grow to the right and downward.

Units

Use integer pixel values. Decimal values may be rounded internally by the processing pipeline.

Boundary rule

Keep the full box inside the video frame: x + width ≤ frame width and y + height ≤ frame height.

Practical guidance

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.

When you do not have a perfect sample yet

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.

4. Poll until the job completes
A successful response returns normalized subtitle segments in job.result.segments. Jobs can also end as failed with a statusMessage after create succeeds — always poll to the terminal state.
GET /api/v1/jobs/:id
curl -X GET 'https://www.subextractor.com/api/v1/jobs/job_123' \
  -H 'Authorization: Bearer sub_sk_live_...'
Succeeded response example
{
  "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 }
      ]
    }
  }
}
Minimum runnable bash example
Full flow with a real $FILE_KEY. Requires curl, jq, and ffprobe. Prefer the online playground for browser联调.
end-to-end.sh
#!/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
done
Operational notes
Idempotency

Reuse the same Idempotency-Key only for the exact same job payload. If the payload changes, the API returns 409 idempotency_conflict.

Rate limits

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.

CORS / integration model

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.

Result format

External responses return segments. You do not need to understand the platform's internal job event structure.

Common errors
401 invalid_api_key

Missing Bearer, invalid secret, or revoked key.

403 api_key_revoked_or_forbidden_user

Key owner is banned or anonymous (code name is historical; revoked keys are 401).

402 insufficient_api_credits

Not enough API credits for the server-measured duration (may appear as a failed job after create, once probe completes).

404 job_not_found

Job id does not exist or is not an API job owned by this key's user.

409 idempotency_conflict

The same Idempotency-Key was reused with a different request body.

413 file_too_large

Upload or job file exceeds 1GB (maxSizeBytes in body).

422 invalid_payload

Missing fields, bad fileKey owner prefix, missing extract subtitleArea, or missing Idempotency-Key.

429 rate_limit_exceeded

Slow down and retry shortly. Keep the requestId when you contact support.

Debugging tip

Log HTTP status and requestId / X-Request-Id. If create returns 200 but the job fails later, poll and read statusMessage.