Errors
All errors follow a standard format:
{
"error": {
"type": "not_found",
"message": "Agent not found",
"code": 404,
"request_id": "req_abc123",
"doc_url": "https://m8tes.ai/docs/api-errors#error-types"
}
}doc_url links to the docs page for that error type. Include request_id when you contact support.
Branch on error_code, never message text. Older clients can read error.details.error_code; Python exposes it as the exception's .code or .error_code.
Error Types
The four you'll hit while building:
Catch the exception you can recover from:
from m8tes import M8tes, NotFoundError, AuthenticationError, BillingError, RateLimitError
client = M8tes()
try:
agent = client.agents.get(999)
except NotFoundError:
print("Agent does not exist")
except AuthenticationError:
print("Check your API key")
except BillingError as e:
print(e.code, e.details) # machine-readable recovery context
except RateLimitError:
print("Still rate limited after SDK retries. Try again later.")The SDK retries 429 and 500+ responses with exponential backoff for idempotent methods (GET, PUT, DELETE), and for the run-creating POSTs, which carry an idempotency key so a retry returns the original run instead of starting a second one. Any other POST or PATCH is never retried, to prevent duplicate side effects.
All error types and `error.details` conventions
- Errors that carry a machine-readable app code (e.g.
feature_disabled,from_template_conflict,RUN_LIMIT_REACHED) put it inerror.error_code(mirrored aterror.details.error_codefor older clients), with any recovery context (missing,connect_urls,rejected_fields, …) as sibling keys undererror.details. The Python SDK surfaces it as the exception's.code(and.error_codesince 4.8.0). 403 TEAMS_MODEL_LOCKED: a Teams account set a model other thandeepseek-v4-1-flashon an agent, task, or run. Omitmodel(or use that one), connect your own provider, or ask about Enterprise for every model.- Query params are strict: an unknown query parameter is rejected with
422(error.details.error_code = "unknown_query_parameter", with a did-you-mean hint). Analytics params (utm_*,gclid,fbclid,_) are ignored. Unknown fields in request bodies are silently ignored.
Billing Error Codes
For 402, read error.error_code and error.details (BillingError.code and .details in Python):
from m8tes import BillingError
try:
run = client.runs.create_and_wait(agent_id=mate.id, message="...")
except BillingError as e:
if e.code == "RUN_LIMIT_REACHED" and e.details.get("overage_available"):
client.billing.set_overage(enabled=True, monthly_cap_cents=5000) # keep running
elif e.code == "TOKEN_BALANCE_DEPLETED":
checkout_url = client.billing.topup(amount_cents=5000) # add $50 of credit
print("Top up to continue:", checkout_url)
else:
print(e.code, e.details)All billing codes, plus the two 429 guards
Two more guards return 429 rate_limit_error (not 402):
END_USER_RATE_LIMITED: an end-user burst past yourper_end_user_rate_per_minutesetting.detailscarryend_user_id,limit_per_minute,retry_after, plus aRetry-Afterheader. Configure all three sub-caps viaclient.settings.update(...); see Users.SANDBOX_CONCURRENCY_LIMIT: too many runs in flight at once.details.scopesays which ceiling:account(your own burst;detailscarry yourconcurrent_runsandlimit) orplatform(overall capacity, no counts). Wait for in-flight runs to finish and retry after theRetry-Afterheader. Need more concurrency? Contact support to raise your account cap.
Run-Level Failures
The exceptions above cover problems reaching the API. A run can also fail upstream: an expired Claude credential, an exhausted quota, a model rate limit. The HTTP request succeeds (no exception), but the run ends with status "failed", the message in run.output, and a machine-readable class in run.error_code. Always check it before trusting output:
run = client.runs.create_and_wait(agent_id=mate.id, message="...")
if run.error_code:
print(f"run failed upstream: {run.error_code}: {run.output}")
else:
print(run.output)Run `error_code` reference
Transient codes are retry-safe; credential codes need re-auth first.
Interrupted task runs continue their saved session, preserving the run ID and conversation.
A non-null next_retry_at means automatic recovery is queued; keep polling the run or
rejoin its stream when its status returns to running. Recovery checks every minute
starting three minutes after server startup, waits for any platform update to finish,
and uses the shared three-attempt limit. A newer run of the task, cancellation, or
unavailable funding can stop recovery; next_retry_at then clears and manual recovery
is available. Guarded coding runs cannot resume their discarded sandbox session and
require manual recovery. Scheduled failures without a saved session retain their existing fresh
retry policy, which refuses to replay work after external writes.
Cancel pending recovery with POST /api/v2/runs/{run_id}/cancel or
client.runs.cancel(run_id). A queued failed run becomes cancelled and its
next_retry_at clears, preventing either recovery worker from starting it.
For oauth_revoked, subscription_quota_exhausted, model_unavailable, and an
own-subscription rate_limited failure, manual recovery is the exception to model
pinning: retrying a task or replying to a chat resolves the account's current
connected/preferred provider instead of replaying the inaccessible concrete model.
Connect or select another provider first to switch; a chat reply resends only that
failed follow-up on the same run. Other failure retries keep the original model.
Account gates include EMAIL_VERIFICATION_REQUIRED, ACCOUNT_SUSPENDED, ACCOUNT_DELETED, and API_WALLET_REQUIRED. Restart a blocked one-off run after resolving the gate.
Next: Limits · Going Live