Error shape
A /v1 error returns a JSON body with an error object carrying a stable type, a human-readable
message, and the request_id for that call:
Handled /v1 responses also carry that id as an X-Request-Id header, and Cue echoes an inbound
X-Request-Id if you send one, so you can supply your own.
An unhandled 500 carries neither. The header is stamped by middleware that runs after the
handler returns, and an unhandled exception never gets that far, so a 500 comes back bare with
no error body and no request id. That is the one failure you would most want to report. When
you hit one, send us the timestamp, the endpoint, and your account id instead. Responses outside
/v1, including /mcp, carry no X-Request-Id at all.
Status codes
An oversized or non-video source fails later, not on the request
POST /v1/videos and POST /v1/match with a video_url validate the URL shape and then return
202 immediately. The fetch happens in a worker, so size and content-type are checked long after
your request succeeded. There is no 413 or 415 on the request.
Poll GET /v1/videos/{video_id} for the outcome:
reason is prefixed with a machine-branchable token you can switch on: source_too_large past
600 MB, source_not_a_video, source_url_rejected, source_not_found, source_error for any
other problem reaching or reading the source (an empty body, a transfer that was truncated
mid-download), or processing_error for anything on our side. Treat an unrecognised token as
processing_error.
A source counts as a video when its Content-Type contains video, or its URL path ends .mp4,
.mov, .webm or .m4v.
Rate limit
Each key is limited to 60 requests per minute on a rolling 60-second window. Buckets are per
key, so two keys is two budgets. Need more?
Get in touch and we will raise it.
Exceeding the limit gets you a 429 telling you how long to wait:
Any /v1 response that reached the rate check also carries X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset, so you can pace yourself before you hit the wall.
A response refused earlier than that check, such as a 401 on a bad key or a 402 from the card
or credit gate, carries none of them.
What an Idempotency-Key does, endpoint by endpoint
The header does two different things depending on where you send it, and only one of them is what
most people expect.
On POST /v1/beats it is a true replay. A key is honoured for 24 hours, scoped to your account.
Resending the same request with the same key returns the original job_id with
idempotent_replay: true, and nothing runs or bills a second time. Reusing that key for a
different request is a 422 rather than a silent alias. The fingerprint ignores the URL’s query
string, so re-signing a presigned link between attempts is fine.
On POST /v1/match, /v1/align, /v1/loop and /v1/underscore it prevents a second charge
only. Nothing short-circuits the handler, so the match, alignment or loop is recomputed in full.
Use it to make a retry safe for your bill, not to save the work.
On a cold /v1/align, /v1/loop or /v1/match, the one that returns 202 with a job_id, the
header does not reach the billing path at all. Those jobs are metered under a server-generated
key when they complete. Resending while the first job is still running starts a second job and
bills a second time at the higher cold rate, whatever key you send. If a cold call times out on
you, poll the job URL you already have before you resend the request.