CaptionPass
AlphaYou're using an early release of CaptionPass. Features and limits may change; report bugs or ideas using the contact email in the footer or on Pricing.

Home · Docs · Dashboard

CaptionPass API v1

Programmatic caption QA for Pilot accounts (same limits as planned Pro: up to 99,999 successful conversions per UTC day via this API). Create API keys under Dashboard → Account. Only HTTP 200 responses count toward the daily cap; failed attempts do not decrement remainingApiRunsToday.

Authentication

Send your secret key on every request (TLS required in production). Do not put API keys in query strings, logs, or client-side bundles served to browsers.

Authorization: Bearer cp_live_...

Creating and revoking keys uses the logged-in web session at Dashboard → Account (/api/account/api-keys with cookies). That flow is separate from the Bearer contract below, which is intended for server-to-server integrations.

POST /api/v1/process

This endpoint accepts POST only. There is no GET health check on /api/v1/process.

Content-Type: multipart/form-data (browser or library-generated boundary).

Form fields

Limits and rate control

Presets

The preset field selects platform rules and output format. Default is youtube.

presetOutputLabel / intent
youtubesrtYouTubeSRT output, conservative line length and reading speed; strips styling tags YouTube ignores.
tiktoksrtTikTok / ShortsSRT optimized for vertical shorts: shorter lines, faster pace, single-line cues preferred.
html5vttHTML5 / WebVTT playerWebVTT output with required header, normalized timestamps, and conservative styling.
genericsrtGeneric safeThe safest possible output: SRT, plain text only, broad reading-speed defaults.
lmsttmlLMS / TTML (IMSC1-friendly)TTML 1.0 / IMSC1-friendly XML output for LMS players and accessibility pipelines.
developer-jsonjsonDeveloper JSON (IR)Stable JSON timeline (CaptionPass IR v1) for round-tripping, automation, and future API use.

Base URL (production site or CloudFront origin):

https://www.captionpass.com/api/v1/process

curl

curl -sS -X POST "https://www.captionpass.com/api/v1/process" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@./captions.srt" \
  -F "preset=youtube"

HTTPie

http --timeout 120 --form POST "https://www.captionpass.com/api/v1/process" \
  "Authorization: Bearer YOUR_API_KEY" \
  file@./captions.srt \
  preset=youtube

Node.js (fetch)

import fs from "node:fs";

const apiKey = process.env.CAPTIONPASS_API_KEY;
const form = new FormData();
form.set("file", new Blob([fs.readFileSync("captions.srt", "utf8")], { type: "text/plain" }), "captions.srt");
form.set("preset", "youtube");

const res = await fetch("https://www.captionpass.com/api/v1/process", {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}` },
  body: form,
});
const body = await res.json();
console.log(res.status, body);

Python

import os, requests

url = "https://www.captionpass.com/api/v1/process"
headers = {"Authorization": f"Bearer {os.environ['CAPTIONPASS_API_KEY']}"}
files = {"file": open("captions.srt", "rb")}
data = {"preset": "youtube"}
r = requests.post(url, headers=headers, files=files, data=data, timeout=120)
print(r.status_code, r.json())

Success response (200)

Content-Type: application/json. Typical headers: Cache-Control: no-store, X-Content-Type-Options: nosniff.

The JSON body merges the pipeline result (report + output from the caption engine) with a quota field:

FieldTypeDescription
reportobjectinputFormat, outputFormat, preset, diagnostics[], fixes[], qaScore (0–100), cueCount, totalDurationMs.
outputstringRendered caption text in report.outputFormat (e.g. SRT, VTT, TTML, JSON depending on preset).
remainingApiRunsTodaynumberSuccessful v1 calls remaining this UTC calendar day after this response (0 when you are on the last allowed run).

Diagnostics and fixes

Each entry in report.diagnostics includes code, severity (error, warn, or info), message, and optionally line or cueIndex. report.fixes entries include code, message, optional cueIndex, and optional before / after snippets for traceability.

Common diagnostic code values include:

codeTypical meaning
empty_inputParser: file was empty.
no_cuesParser: no valid cues found.
invalid_timestampParser: malformed time range.
missing_webvtt_headerVTT: WEBVTT missing on line 1.
overlapping_cuesRule: two cues overlap in time.
reading_speed_highRule: CPS above preset maximum for a cue.
too_many_linesRule: cue exceeds max lines per cue.
strip_stylingRule: markup removed per preset.

Example (truncated)

{
  "report": {
    "inputFormat": "srt",
    "outputFormat": "srt",
    "preset": "youtube",
    "diagnostics": [
      { "code": "overlapping_cues", "severity": "warn", "message": "…", "cueIndex": 1 }
    ],
    "fixes": [
      {
        "code": "wrap_long_line",
        "message": "Wrapped cue 0 to 42 chars per line.",
        "cueIndex": 0,
        "before": "…",
        "after": "…"
      }
    ],
    "qaScore": 92,
    "cueCount": 12,
    "totalDurationMs": 145000
  },
  "output": "1\n00:00:01,000 --> 00:00:04,000\n…",
  "remainingApiRunsToday": 99887
}

Error responses (v1)

All v1 errors use this JSON shape (human-readable message may vary slightly; rely on code for branching):

{ "error": { "code": "unauthorized", "message": "…" } }
HTTPerror.codeWhen
401unauthorizedMissing/invalid Bearer token, or unknown/revoked API key.
403forbiddenKey resolved but account is not allowed API access (entitlement).
429rate_limitedPer-key burst limit (too many requests in a short window).
429quota_exceededDaily successful conversion cap reached for your account (UTC day).
400bad_requestMalformed multipart, missing file, bad preset, empty file, decode error, etc.
400payload_too_largeDeclared Content-Length exceeds the allowed request ceiling (before body read).
413payload_too_largeEngine refused input as too large (e.g. cue count cap).
422processing_failedValidation/processing error; message explains the failure.
503unavailableServer misconfiguration (API data store not available).

Example error bodies

401 unauthorized

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or revoked API key."
  }
}

429 rate_limited

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Slow down and try again shortly."
  }
}

429 quota_exceeded

{
  "error": {
    "code": "quota_exceeded",
    "message": "Daily API conversion limit reached. Try again tomorrow."
  }
}

422 processing_failed

{
  "error": {
    "code": "processing_failed",
    "message": "Could not parse cues: …"
  }
}

Public web converter (comparison)

POST /api/process (browser, no API key) returns the same success shape: report, output, and remainingFreeRunsToday instead of remainingApiRunsToday. Many errors from that route use a flat JSON body like { "error": "string message" } (not the nested error.code object). Prefer /api/v1/process for integrations.

Versioning

Paths are versioned (/api/v1/...). Non-breaking additions may land without a new version; incompatible changes will ship under /api/v2 with advance notice when possible.