Errors and limits
Dashboard, account and agent requests answer errors with JSON, always in the same shape. Use code, which is stable; message is meant for people and can change.
{ "error": { "code": "key_cap_daily", "message": "a spending limit has been reached", "retryable": true, "retry_after_seconds": 41200,
"remediation": "The key's daily spending limit is used up; wait until it resets at 00:00 UTC or ask the key's owner to raise it.",
"docs_url": "https://pilot.e3d.ai/docs/errors#key_cap_daily" } }retryablesays whether the identical request may succeed later with nobody changing anything.retry_after_secondsappears when the wait is known; it is also theRetry-Afterheader.429responses carry that header.remediationis one sentence a person or a model can act on.docs_urlpoints to the code's entry below (the codes added for agents have their own heading; the older ones are in the table).- A refusal for a spending limit, a rate or capacity can name the specific code in a
reasonfield, next tocode: limit_reached.
Error codes
| Code | Status | Meaning |
|---|---|---|
bad_request | 400 | malformed request line, URL or length headers |
invalid_json | 400 | body is not a strict JSON object |
invalid_request | 400 | a field is missing, unknown or malformed |
invalid_code | 400 | verification, reset or sign-in code not accepted |
password_rejected | 400 | new password fails the policy |
invalid_pack | 400 | pack is not purchasable |
quote_required | 400 | token pricing: a web debate needs a quote id from POST /api/debates/quote |
quote_exceeds_limit | 400 | token pricing: the debate could cost more than the per-debate maximum |
unknown_model_rate | 400 | token pricing: a model on the panel has no price |
terms_required | 403 | the terms must be accepted first (in the dashboard gate) |
terms_changed | 409 | the terms changed; read the current version and accept again |
panel_too_small | 400 | a debate panel needs at least two companies |
panel_too_large | 400 | more companies than the panel limit |
provider_not_allowed | 400 | a company that is not offered |
duplicate_provider | 400 | a company is listed twice |
families_not_distinct | 400 | a debate panel needs companies from at least two vendors |
required_provider_missing | 400 | a company that always takes part was left out of the panel |
bad_signature | 400 | Stripe signature invalid |
invalid_event | 400 | Stripe event malformed |
quote_exceeds_balance | 402 | token pricing: the debate's maximum is more than the balance |
search_unavailable | 503 | token pricing with web search: the run listener refused the search fields (rolled back inside the capability cache); the hold was released in full; ask again for a new quote |
unauthenticated | 401 | no session cookie |
session_expired | 401 | the session has ended; sign in again |
invalid_credentials | 401 (login) / 403 (reauth, change-password) | email or password not accepted |
invite_required | 403 | closed sign-up (PILOT_SIGNUP_MODE=invite): a missing, malformed, unknown, used, revoked, expired or reserved invite code; one answer for all of them |
forbidden_origin | 403 | Origin or fetch metadata not allowed |
csrf_failed | 403 | CSRF token missing or wrong |
reauth_required | 403 | step-up older than 10 minutes |
suspended | 403 | billing suspended: the account balance is negative or a dispute is open |
not_found | 404 | no such route or resource |
invite_invalid | 404 | the team invitation cannot be used by this account |
method_not_allowed | 405 | wrong method for the route |
request_timeout | 408 | body not received in time |
key_limit | 409 | 25 keys already exist |
account_closing | 409 | the account is closing or closed |
request_conflict | 409 | checkout requestId reused for another purchase |
request_in_progress | 409 | identical checkout still running; retry |
liability_outstanding | 409 | cannot close: negative balance |
adjustments_unresolved | 409 | cannot close: open dispute |
holds_open | 409 | cannot close: usage in progress |
credits_remaining | 409 | cannot close without confirming forfeiture |
version_conflict | 409 | the debate panel changed elsewhere; read it again |
panel_unavailable | 409 | the debate panel has fewer than two offered companies from different vendors left |
debate_not_finished | 409 | only a finished debate can be saved as a decision |
decision_limit | 409 | the account has the maximum number of saved decisions |
share_limit | 409 | the account has the maximum number of active share links |
team_limit | 409 | the account owns or belongs to the maximum number of teams |
member_limit | 409 | the team has the maximum number of members |
invite_limit | 409 | the team has the maximum number of pending invitations |
already_member | 409 | the account is already a member of the team |
team_readonly | 409 | the team is read-only (its owner closed the account) |
team_owner_must_resolve | 409 | the owner cannot leave or close while the team has other members |
insufficient_credits | 409 | not enough credits on the account for a debate |
key_inactive | 409 | the key named for attribution is revoked |
quote_changed | 409 | token pricing: the quote is stale or the price rose since it was shown; a fresh quote comes with it |
payload_too_large | 413 | body over the limit |
unsupported_media_type | 415 | body is not application/json |
rate_limited | 429 | limit reached; see Retry-After |
too_many_debates | 429 | two web debates are already in progress |
limit_reached | 429 | a usage limit is reached: a key's spending cap, the account-wide daily ceiling, open requests or rate |
internal_error | 500 | unexpected failure |
receipt_failed | 500 | webhook receipt not durable |
checkout_unavailable | 502 | Stripe Checkout unavailable |
accounts_unavailable | 503 | platform accounts service unavailable |
debates_unavailable | 503 | web debates are not available right now |
handler_timeout | 503 | request took too long |
launch_disabled | 503 | purchases not open yet |
usage_unavailable | 503 | usage analytics unavailable |
retry_later | 503 | webhook processing deferred |
webhook_unavailable | 503 | webhook not configured |
Agent error codes
These are the codes of the agent contract (spending limits, quotes, idempotency, rate and capacity) and of the internal listener. Agent calls use the status shown; the dashboard API keeps the statuses in the table above. Each entry says what to do.
key_revoked
403 · not retryable as is. This key was revoked; ask the key's owner for a new key.
key_unknown
401 · not retryable as is. This key is not recognised; check that the whole key was copied.
key_cap_per_run
403 · not retryable as is. This run could cost more than the key's per-run limit; ask a shorter or smaller debate, or ask the key's owner to raise the limit.
key_cap_daily
429 · retry after 00:00 UTC. The key's daily spending limit is used up; wait until it resets at 00:00 UTC or ask the key's owner to raise it.
key_cap_total
403 · not retryable as is. The key's lifetime spending limit is used up; ask the key's owner to raise it or issue a new key.
key_cap_period
429 · retryable. The key's monthly spending limit is used up; wait for the new month or ask the key's owner to raise it.
account_cap_daily
429 · retry after 00:00 UTC. The account's daily spending ceiling is reached; wait until it resets at 00:00 UTC.
insufficient_credits
402 · not retryable as is. The account does not have enough credits for this run's maximum; ask a human to add credits.
quote_required
400 · not retryable as is. Get a quote first and pass its quote_id.
quote_expired
410 · not retryable as is. The quote is unknown or older than 10 minutes; get a new quote.
quote_mismatch
409 · not retryable as is. The run does not match its quote (see the mismatch list); get a new quote for exactly this run.
quote_used
409 · not retryable as is. This quote already belongs to another run; get a new quote.
quote_changed
409 · not retryable as is. The price changed after the quote; get a new quote and check the new maximum.
quote_exceeds_limit
400 · not retryable as is. This debate could cost more than the per-debate maximum; use fewer models or a shorter length.
idempotency_key_invalid
400 · not retryable as is. Send an idempotency_key of 8 to 128 letters, digits or the characters . _ : -
idempotency_conflict
409 · not retryable as is. This idempotency_key was already used for a different request; use a new key for a different request.
in_progress
409 · retry after about 5 seconds. The first attempt with this key is still running; wait, then repeat the same call to get its result.
rate_limited
429 · retry after the next minute starts. Too many requests; slow down and retry after the wait.
concurrency_limit
429 · retry after about 15 seconds. Too many debates are already running for this key or account; wait for one to finish.
panel_unavailable
409 · not retryable as is. The requested models are not available on this account's panel; list the providers and choose from them.
question_too_large
413 · not retryable as is. The question is over the size limit; shorten it.
kill_switch
503 · retry after about 300 seconds. New debates are paused by the operator; retry later.
service_capacity
503 · retry after 00:00 UTC. The service's daily capacity is reached; retry after it resets at 00:00 UTC.
pricing_paused
503 · not retryable as is. Debates for this connection are paused for a pricing check; contact support.
too_many_failed_runs
429 · retry after 00:00 UTC. Too many debates did not finish today; retry after 00:00 UTC.
terms_required
403 · not retryable as is. The account owner must accept the current terms in the dashboard.
account_suspended
403 · not retryable as is. Billing is suspended for this account; the owner must resolve it in the dashboard.
account_closing
409 · not retryable as is. The account is closing or closed.
invalid_request
400 · not retryable as is. A field is missing, unknown or malformed (see the message); fix the request.
unsupported_version
400 · not retryable as is. Send a supported version header.
unauthorized
401 · not retryable as is. The service token is wrong; this is a configuration error, not for end users.
not_found
404 · not retryable as is. No such route or resource.
service_unavailable
503 · retry after about 10 seconds. Billing is temporarily unavailable; retry shortly with the same idempotency_key.
internal_error
500 · retry after about 10 seconds. Unexpected failure; retry once with the same idempotency_key.
proxy_headers_rejected
400 · not retryable as is. Remove proxy headers from requests to this listener.
forbidden
403 · not retryable as is. This listener or route is not available to you.
too_many_failed_attempts
429 · retry after about 60 seconds. Too many failed authentication attempts; wait a minute.
unhealthy
503 · retry after about 10 seconds. The service is not healthy; retry shortly.
MCP endpoint responses
The MCP endpoint at https://pilot.e3d.ai/mcp answers with plain HTTP statuses before a tool runs.
| Status | Meaning |
|---|---|
| 401 | The key is missing, wrong, revoked or expired. |
| 403 | The request came from a browser origin that is not allowed. |
| 405 | Only POST (and DELETE to end a session) are supported; there is no event stream. |
| 406 | The client must accept application/json. |
| 415 | The body must be application/json. |
| 429 | Too many requests; wait for Retry-After. |
| 503 | The server is busy; retry after Retry-After. |
A refusal for credits, limits or suspension arrives as a tool result that explains why; see Credits and billing.
Limits
| Scope | Limit |
|---|---|
| Sign-in | 10 per minute per address, plus a wait of 1, 2, 4 ... up to 60 seconds after consecutive failures for one email |
| Sign-up | 5 per hour per address |
| Forgot password and resend code | 5 per hour per address and 3 per hour per email |
| Email me a code | 10 starts per hour per address and 3 per hour per email; 20 attempts per hour per address, and 5 attempts per email per hour across all codes |
| Verification and reset codes | 5 attempts per code (sign-in codes are valid 10 minutes) |
| Password confirmation | 5 per 10 minutes per session |
| Dashboard reads | 120 per minute per session |
| Dashboard changes | 30 per minute per account |
| Keys | 25 per account |
| Sessions | 10 per account |
| Request body | 64 KiB |
Spending limits reset at 00:00 UTC. Each key can have a cap per day, month or in total (a default daily cap applies when you set none), and an account-wide daily ceiling covers everything the account spends. Reaching either answers limit_reached; running out of credits answers insufficient_credits. The usage page shows what was charged to the website and to each key.