Cut.ProDocs
Guides

Errors and limits

The one error shape, what to do on each status, and how to handle a 429

Every error from this API, on every route and every status, has the same body. Learn to read one and you have read them all.

402 response
{
  "code": "INSUFFICIENT_CREDITS",
  "extra": { "credits_needed": 121, "current_balance": 40 }
}
  • code is a stable, uppercase identifier from a closed set. This is what your switch decides on.
  • extra only shows up when there is something to act on: what was missing, which field failed, which ids were rejected.
  • There is no ready-made message. The server does not know the language of the person using your product, and prose cannot be handled in code. You write the sentence.

States are lowercase, errors are uppercase. status says where something is (queued, processing, completed, failed); code and error_code say what went wrong (VIDEO_TOO_LONG, DOWNLOAD_NO_AUDIO).

What to do on each status

StatusWhat it meansWhat to do
400The request body is wrongFix the payload. Sending it again unchanged will not help
401Key missing, revoked or expiredCheck the X-Api-Key header
402Not enough creditsTop up, or read extra.credits_needed
403The video or the account is off limitsNothing to retry
404Does not exist in this workspaceCheck the id and the workspace
409It exists, but its state refuses the operationWait and try again
410It existed and expiredSubmit or render again
422Valid link, unusable videoTry another source
429Rate limitedWait for Retry-After
5xxOur faultRetry with backoff

The full list of code values per route is in the reference, response by response.

Handling errors in your code

The pattern that covers everything: read the code, let the status decide whether it is worth retrying.

const RETRIABLE = new Set([429, 500, 502, 503, 504]);

async function call(path, init = {}, attempt = 1) {
	const response = await fetch(`https://api.cut.pro/api/v1${path}`, {
		...init,
		headers: { "X-Api-Key": process.env.CUTPRO_API_KEY, "Content-Type": "application/json" },
	});

	if (response.ok) return response.json();

	const body = await response.json();

	if (RETRIABLE.has(response.status) && attempt <= 5) {
		// The server says how long to wait on a 429. On 5xx, exponential backoff.
		const retryAfter = Number(response.headers.get("retry-after"));
		const wait = retryAfter > 0 ? retryAfter : 2 ** attempt;
		await new Promise((resolve) => setTimeout(resolve, wait * 1000));
		return call(path, init, attempt + 1);
	}

	throw Object.assign(new Error(body.code), { code: body.code, extra: body.extra, status: response.status });
}

Then your product turns the code into the language of whoever is looking:

switch (error.code) {
	case "INSUFFICIENT_CREDITS":
		show(`You need ${error.extra.credits_needed - error.extra.current_balance} more credits.`);
		break;
	case "VIDEO_TOO_LONG":
		show(`That video is ${error.extra.duration}s and your plan takes up to ${error.extra.max_duration}s.`);
		break;
	case "LIVE_STREAM":
		show("The stream is still on air. Try again once it becomes a video.");
		break;
	default:
		show("We could not process that right now. Try again in a moment.");
}

Rate limits

600 requests per minute, per API key, in a fixed 60-second window. Every response tells you where you stand:

HeaderWhat it carries
X-RateLimit-LimitThe ceiling (600)
X-RateLimit-RemainingCalls still available in this window
X-RateLimit-ResetUnix seconds for when the window resets

Over the ceiling you get 429 with { "code": "RATE_LIMIT_EXCEEDED" } and a Retry-After header in seconds. Honour it: retrying immediately only burns the next window.

POST /clips/info has its own, tighter limit of 10 per minute, because every call reaches out to the source platform for metadata. Analyze once and keep the id.

The limit is per key, not per IP: several of your processes sharing a key share the same budget, and two keys behind the same IP never get in each other's way.

Does polling fit in that?

Comfortably. Polling one submission every 10 seconds costs 6 requests per minute, so you can keep dozens of jobs in flight. What blows the limit is a tight loop with no wait.

On this page