OverviewAuthenticationScopesEndpointsVideo previewErrors & limitsPluginsBuild a platform
Documentation

Errors & limits

Stable machine codes, an honest retry column, and what the rate-limit headers actually mean.

Every failure has the same shape:

{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not carry the render.write scope.",
    "docs_url": "https://developer.vidmoat.com/developer/docs/scopes"
  }
}
Branch on code, never on message. The code is part of the contract; the message is written for a human reading a log line and gets reworded whenever a clearer sentence turns up. New codes can appear in v1 — treat an unrecognised one as a generic failure of its HTTP class rather than crashing.

Codes

HTTPCodeMeaningRetry?
400invalid_requestMalformed body, unknown field, or a value out of range.No — fix the request.
400invalid_commandA command failed schema validation. The whole batch was rolled back; none of it applied.No. Check GET /v1/schema/commands.
400prompt_refusedA generation prompt hit the safety filter. The credit was not charged.No — reword it.
401unauthenticatedMissing, malformed, revoked or expired bearer token.No, until you fix the credential.
403insufficient_scopeThe credential is valid but lacks a required scope. The response names which one.No — mint a key with that scope.
403app_suspendedThis app’s kill switch is on. Independent of your account standing.No — check the contact address on the app.
403account_restrictedThe owning account is suspended or under review.No.
402insufficient_creditsNot enough credits for this action. Nothing ran and nothing was charged.After topping up.
402plan_requiredThe owner’s plan does not include this feature — the scope was necessary but not sufficient.No — upgrade.
402quota_exceededMonthly export or project allowance is used up.After the monthly reset.
404not_foundNo such resource, or it belongs to somebody else. The two are deliberately indistinguishable.No.
409conflictThe resource changed underneath you, or a render is already queued for it.After re-reading the resource.
413too_largeUpload exceeds the plan’s file-size cap.No.
429rate_limitedToo many requests in the window. Rejected requests do not consume quota.Yes — after Retry-After.
500internal_errorOur fault. Nothing is implied about whether the work happened.Once, with backoff, then re-read state.
503unavailableA dependency (queue, provider, database) is down.Yes — with backoff.
402 is two different problems. insufficient_credits means top up; plan_requiredmeans the feature is not on the plan at all and no amount of credit will unlock it. Distinguishing them is why they are separate codes rather than one “payment required”.
404 hides 403. A resource you do not own returns not_found, not forbidden— otherwise the API would confirm the existence of other people’s projects to anybody who guessed an id.

Rate limits

A sliding window per credential. Rejected requests do not consume quota, so a client that hits the wall and backs off recovers cleanly rather than digging itself deeper.

HeaderMeaning
X-RateLimit-LimitRequests allowed in the window.
X-RateLimit-RemainingHow many are left.
X-RateLimit-ResetUnix seconds when the window rolls over.
Retry-AfterOn a 429 only. Seconds to wait — honour this rather than guessing.
Per-minute ceilings by plan are on the settings page. Generation actions are additionally bounded by your credit balance, and renders by a per-plan concurrency limit — which means a burst of render requests can succeed at the HTTP layer and then queue, so poll rather than assuming a 200 meant “started”.
What the limiter does when it cannot reach Redis: it lets you through.Failing open on a limiter outage is a deliberate choice — the alternative is a Redis blip taking down every customer’s integration. Do not design around it. A generous window is not a guarantee, and the credit meter does not fail open for automated callers.
Versioning. Additive changes ship into v1 — new fields, new endpoints, new enum values. Ignore unknown fields; a client that rejects them will break on a routine release. Breaking changes get a /v2, and v1 is supported for at least 12 months after one exists. Send Vidmoat-Version: 2026-08-01 to pin date-based behaviour within v1.