Errors are always JSON, with the same shape everywhere:
{ "error": { "code": "forbidden", "message": "Only an owner can refund a live payment. This is an agent key: ask a person to do it in the console.", "details": { "reason": "owner_only", "next_step": "Tell the user: the account owner does this in the console ▸ the project ▸ Payments ▸ the payment ▸ Refund (or with an owner key). Retrying with this key will not work." } } }codeis stable: branch on it, never onmessage.details.reasonrefines a code (owner_only,payouts_not_ready,daily_spend…).details.next_step, when present, is what to tell the person. Agents pass it on instead of retrying.- Every response has an
X-Request-Id; quote it to support. - AI routes add OpenAI's
typeandparamso OpenAI SDKs read them.
Codes
| Code | Status | Meaning | Next step |
|---|---|---|---|
invalid_request | 400 | The request is malformed or a field is wrong. details names the field (issues[].path, param, problems) | Fix the field; don't retry unchanged |
content_filtered | 400 | An AI content filter refused the request | Change the input |
context_length_exceeded | 400 | The AI input is longer than the model's window | Shorten it or use a larger model |
unauthorized | 401 | Missing, unknown or revoked key; an app key on an account route | Check the key and its kind |
forbidden | 403 | Not allowed for this key. With reason: owner_only: a person must do it | Pass details.next_step to the owner |
no_account | 403 | Signed in to the console with no account yet | Create an account or link a key in the console |
not_found | 404 | No such object on this account or in this environment | Check the id and env |
conflict | 409 | The object's state doesn't allow it (details.reason says why), or a concurrent request with the same Idempotency-Key is still running | Read the object again; act on reason |
domain_taken | 409 | The slug or domain belongs to someone else | Pick another |
dependency_not_ready | 409 | A resource this needs is missing or still provisioning | Add it or wait, then retry |
insufficient_balance | 409 | The project's balance can't cover a refund | Wait for more sales, or refund less |
payment_required | 402 | The account is out of credits for a paid call (details.reason: insufficient_balance, or past_due for deploys after a week at zero) | Pass details.next_step and top_up_url to the person; don't retry until credits are added |
quota_exceeded | 413 | A stored quota is full (replay) | Delete or wait for retention |
payload_too_large | 413 | The body or upload is over its cap | Send less |
idempotency_mismatch | 422 | The same Idempotency-Key was used with a different body | Use a new key for a new request |
rate_limited | 429 | A limit was reached; details says which and resets_at | Wait (Retry-After or resets_at); don't loop |
client_outdated | 426 | The Vibiz CLI that called is older than the API supports; details.minimum | Update it: npm install -g @vibiz/cli@latest, then retry |
internal | 500 | Our bug | Retry once; then contact support with X-Request-Id |
provider_failed | 502 | The work was refused downstream (a refund refused; your own AI key rejected) | Read message; retrying rarely helps |
provider_unavailable | 503 | The service is unavailable right now, or the call timed out (details.reason) | Retry later with backoff |
model_unavailable | 503 | No route can run the model you asked for right now. Vibiz never swaps in another model | Retry after details.retry_after_seconds, or pick another model |
not_configured | 503 | This deployment doesn't offer that capability now | Use another model or capability |
Retries and idempotency
Any POST accepts Idempotency-Key. The first successful JSON response is stored for 24 hours and replayed for a retry with the same body, so a retried payment checkout, email or AI call can't happen twice. A different body under the same key is idempotency_mismatch; a concurrent request with the same key while the first is running is conflict. Streams and audio aren't replayed. Server errors aren't stored, so they can be retried.
Retry 429 (after the reset), 502/503 and network errors with backoff. Never retry 400, 401, 403 or 409 unchanged.
In the MCP server
Tool errors are isError: true results whose text is the same JSON. A failed build waited on by vibiz_deploy comes back as deployment_failed.