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.
Ad discovery errors
Discovery has its own shared team and provider budgets in addition to API-key rate limits. It never consumes generation credits. Retry-After and error.retryAfterSeconds give the delay for quota failures.
ads_daily_limit(429): the team's daily upstream allowance is exhausted; retry after the reset. Cached repeats and saved references remain available.ads_search_expired(409): the cursor or save receipt expired, belongs to another team, or no longer matches the search filters. Run the search again.ads_saved_limit(409): remove a workspace reference before saving another.ads_unavailable,ads_not_configured,ads_name_search_unavailable(503): the requested discovery capability is disabled or awaiting configuration. Name-search availability does not affect keyword or Page-ID searches.ads_auth_expired,ads_access_denied(503): the service's Meta credential needs renewal or approval. Saved references remain available.ads_cache_unavailable(503): upstream limits cannot be enforced. Fresh searches pause; saved listing and removal remain available.ads_upstream_error,ads_upstream_timeout(502): Meta failed or timed out. Retry later.
See competitor ad discovery for coverage, caching, limits, and saving semantics. These errors are distinct from a successful search with no matching ads.
Default 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.