API Documentation

REST API to fetch YouTube video transcripts with asynchronous processing, prepaid credit billing and outbound webhooks signed with HMAC-SHA256.

Interactive spec: Swagger UI at /api/v1/docs · OpenAPI JSON


Getting Started

1. Create an API key

Sign in and open /account/api-keys to generate a key. The sk_live_… token is shown only once — store it immediately.

Scopes:

  • read — read-only (list, fetch, balance, deliveries).
  • read-write — create/retry/delete transcripts and purchases.
  • admin — create new keys, configure webhook.

2. Make sure you have credits

Each transcript costs credits (defined per package). Buy at /account/billing or via POST /api/v1/credits/purchase. Current balance:

curl https://<host>/api/v1/credits/balance \
  -H "Authorization: Bearer sk_live_…"

3. Request a transcript

curl -X POST https://<host>/api/v1/transcripts \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "videos": [{ "url": "https://www.youtube.com/watch?v=…",
                 "requested_language": "en" }]
  }'

A 201 response returns transcript_ids. Status follows todo → doing → done (or error). Poll:

curl https://<host>/api/v1/transcripts/<id> \
  -H "Authorization: Bearer sk_live_…"

Webhooks (HMAC signature + retries)

Instead of polling, configure an outbound webhook to be notified whenever a transcript changes state.

Configure

curl -X PATCH https://<host>/api/v1/account/webhook \
  -H "Authorization: Bearer sk_live_…_admin" \
  -H "Content-Type: application/json" \
  -d '{ "webhook_url": "https://my-app.com/hooks/transcript" }'

The response includes webhook_secretshown only once. Store it to validate signatures. DELETE on the same endpoint removes the configuration.

Verify the signature

Every POST includes the header X-Webhook-Signature: sha256=<hex> computed over the raw body with your webhook_secret.

// Node 18+ / Bun
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody: string, headerSig: string, secret: string) {
  const expected = "sha256=" + createHmac("sha256", secret)
    .update(rawBody).digest("hex");
  const a = Buffer.from(headerSig);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

Always use timingSafeEqual (or equivalent) — direct string comparison leaks timing.

Retries

Failures (timeout or HTTP ≥ 400) enter a retry loop with exponential backoff, up to webhook_max_attempts attempts (configured globally, default 5). History at GET /api/v1/account/webhook/deliveries.


Rate Limits

Limits are applied per API key. Headers on every response:

HeaderMeaning
X-RateLimit-LimitLimit for the current window
X-RateLimit-RemainingRemaining requests
X-RateLimit-ResetEpoch (s) when the window resets
Retry-After(On 429) seconds until you may retry

429 response:

{ "error": { "code": "RATE_LIMITED", "message": "Too many requests" } }

Recommended strategy: exponential backoff honouring Retry-After.


Error codes

All errors follow the envelope:

{ "error": { "code": "INVALID_REQUEST", "message": "…", "details": {} } }
HTTPerror.codeWhen
400INVALID_REQUESTInvalid body / query
400INVALID_VIDEO_URLUnrecognised video URL
400INVALID_WEBHOOK_URLInvalid webhook URL (https)
401UNAUTHORIZEDMissing/invalid/revoked API key
402INSUFFICIENT_CREDITSInsufficient balance
403FORBIDDENKey scope does not allow the action
404TRANSCRIPT_NOT_FOUNDResource missing or not yours
409INVALID_STATUS_TRANSITIONe.g. retry on non-error transcript
409TRANSCRIPT_LOCKEDCannot delete while processing
429RATE_LIMITEDSee X-RateLimit-* headers
500INTERNAL_ERRORInternal error (report with x-request-id)

Next steps