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",
"hint": "Wait for the seconds in the Retry-After header, then retry. Limits are documented at https://viraloop.io/developers/docs/credits-and-limits.md."
}
}
Branch on error.type, not on the message text; messages can change, types will not. hint is one fixed sentence per type describing the recovery step, meant for agents that have to act without a human reading these docs. It is informational: never parse it, and never branch on it.
Paths that match no endpoint return the same envelope with type: "not_found" and a 404, so one parser covers the whole API surface.
A 401 also carries a WWW-Authenticate header pointing at the protected-resource metadata (RFC 9728), which lists every scope the API supports.
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.unauthorized(401): missing, malformed or revoked API key. Check theAuthorization: Bearer vl_live_...(orX-API-Key) header; with the CLI, re-runviraloop loginor fixVIRALOOP_API_KEY.insufficient_credits(402): the team does not have enough credits for a credit-costing operation. The two credit-costing endpoints arePOST /influencers/{id}/videos(20 credits) and the optional preview inPOST /influencers(10 credits, skipped instead of failing when out of credits). Top up at https://viraloop.io/settings/billing; do not retry until the balance changes. Failed generations refund their credits automatically.forbidden_scope(403): the key is valid but lacks a required scope. Create a key with the needed scopes at https://viraloop.io/settings/developers;GET /meshows the scopes a key has.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. The three common cases: cancelling a post that is no longerscheduled, publishing a suggestion that is already scheduled, and 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. Limits: 300 per 60s default, 10 per 300s onPOST /generations, 60 per 300s onPOST /posts(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_scope - 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 (including POST /generations, POST /posts, POST /influencers and POST /influencers/{id}/videos) 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.