Errors and rate limits
Every error response uses one envelope, whatever the endpoint:
{
"success": false,
"error": {
"type": "rate_limited",
"message": "Too many requests, slow down"
}
}
Branch on error.type, not on the message text; messages can change, types will not.
Error types
invalid_input(400, and 405 for a wrong HTTP method): the request body, query or method is wrong. Fix the request against the API reference; the message says which field failed. Also returned byPOST /posts/schedulewhen a platform's post metadata is missing on the video.unauthorized(401): missing, malformed or revoked API key. Check theAuthorization: Bearer fl_live_...(orX-API-Key) header; with the CLI, re-runfaceless loginor fixFACELESS_API_KEY.insufficient_credits(402): the team cannot pay for a credit-costing operation (POST /videos, series episodes). Nothing was charged or created. Top up at https://faceless.so/billing; do not retry until the balance changes.forbidden_scope(403): the key is valid but lacks a required scope. Create a key with the needed scopes in team settings at https://faceless.so/team;GET /meshows the scopes a key has.usage_limit_reached(403): the team's plan caps its number of video projects and the cap is hit. Upgrading the plan (or deleting old projects) fixes it; changing keys does not.not_found(404): the resource id does not exist or belongs to another team. Also returned for malformed object ids.conflict(409): the request clashes with current state, most commonly cancelling a post that is no longer scheduled, or reusing anIdempotency-Keywith a different request body. Do not blind-retry; re-read the resource state first.rate_limited(429): too many requests for this key. Wait for the number of seconds in theRetry-Afterheader, then retry. Default limit 300 per 60s, with tighter per-operation limits on creation and publishing endpoints (see credits and limits).internal_error(500 and other 5xx): something failed on our side. Retry with exponential backoff; if it persists, contact support with the request path and time.
HTTP status to type mapping
- 400:
invalid_input - 401:
unauthorized - 402:
insufficient_credits - 403:
forbidden_scopeorusage_limit_reached - 404:
not_found - 405:
invalid_input(method not allowed on this endpoint) - 409:
conflict - 429:
rate_limited - 5xx:
internal_error
Idempotency and the 409 conflict
Mutating operations marked idempotent in the API reference (POST /videos, POST /videos/captions, POST /videos/{id}/render, POST /series, POST /series/{id}/generate, POST /posts, POST /posts/schedule, POST /assets) accept an Idempotency-Key header (any unique string, for example a UUID). Successful responses are stored for 24 hours; retrying with the same key and the same body replays the stored response instead of re-running the operation, which makes timeouts safe to retry and prevents double credit charges. The CLI generates a key automatically for every mutating command; MCP tools accept an idempotencyKey argument with the same semantics.
Reusing a key with a different body is treated as a bug in your client and returns 409:
{
"success": false,
"error": {
"type": "conflict",
"message": "This Idempotency-Key was already used with a different request body"
}
}
Generate a fresh key per logical operation, and reuse it only for retries of exactly that operation.
Async failures are not HTTP errors
A video whose generation fails reports it through polling, not through an error status: GET /videos/{id}/status returns 200 with status: "failed" and the reasons in errorMessages. Likewise a render ends as status: "error" on GET /renders/{id}. Poll to terminal states and branch on the data, not just on HTTP codes.