Errors

All errors follow a standard format:

JSON
{
  "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.

FieldUse it for
error.codeHTTP status, such as 402
error.error_codeRecovery logic, such as TOKEN_BALANCE_DEPLETED
error.detailsRecovery URLs, limits, and other context
error.messageHuman-readable explanation

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:

StatusTypeSDK ExceptionDescription
401authentication_errorAuthenticationErrorInvalid or missing API key
402billing_errorBillingErrorRun or cost limit reached, or balance depleted
422validation_errorValidationErrorInvalid request parameters
429rate_limit_errorRateLimitErrorToo many requests

Catch the exception you can recover from:

Python
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
StatusTypeSDK ExceptionDescription
400invalid_request_errorValidationErrorInvalid request
403permission_errorPermissionDeniedErrorInsufficient permissions
404not_foundNotFoundErrorResource not found
405method_not_allowedAPIErrorHTTP method not supported on this path
409conflict_errorConflictErrorResource conflict (duplicate, not active)
500+api_errorAPIErrorServer error (automatically retried for GET/PUT/DELETE)
  • Errors that carry a machine-readable app code (e.g. feature_disabled, from_template_conflict, RUN_LIMIT_REACHED) put it in error.error_code (mirrored at error.details.error_code for older clients), with any recovery context (missing, connect_urls, rejected_fields, …) as sibling keys under error.details. The Python SDK surfaces it as the exception's .code (and .error_code since 4.8.0).
  • 403 TEAMS_MODEL_LOCKED: a Teams account set a model other than deepseek-v4-1-flash on an agent, task, or run. Omit model (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):

codeMeaningKey details fields
RUN_LIMIT_REACHEDMonthly included runs exhaustedruns_used, runs_limit, period_end, overage_available (whether overage can actually be enabled for your account; false without an active subscription to bill it to), free_path_available (false when already on Hobby — model-plan connect is not recovery past the allotment)
STARTER_LANE_MODEL_REQUIREDWallet still on the $1 API test credit — only deepseek-v4-1-flash is allowed until you top up or connect a providerallowed_model, requested_model, topup_url, connect_model_url
TOKEN_BALANCE_DEPLETEDPrepaid token balance exhausted (API/developer billing). Top up, or connect a model subscription for personal developmentbalance_micros, balance_usd, topup_url, connect_model_url (legacy: connect_claude_url)
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
codeMeaningKey details fields
OVERAGE_CAP_REACHEDUsage overage spend cap hitoverage_used_cents, overage_cap_cents, period_end, overage_settings_url (raise the cap or upgrade)
TRIAL_EXPIREDTime-boxed trial has endedtrial_ends_at, upgrade_url
COST_LIMIT_REACHEDFair-usage cost backstop hitruns_used, runs_limit, period_end
SUBSCRIPTION_BLOCKEDPayment issue (past_due / unpaid)subscription_status
FUNDING_REQUIREDNo usable model plan or paid plan. When free_path_available is true, connect a model plan to continue for free; otherwise recovery is paid-only.plan, free_path_available, connect_model_url, upgrade_url, demo_url, demo_grants_access
END_USER_RUN_LIMIT_REACHEDOne end-user hit your per-end-user run sub-cap (other end-users unaffected)end_user_id, runs_used, runs_limit, period_end
END_USER_COST_LIMIT_REACHEDOne end-user hit your per-end-user cost sub-capend_user_id, cost_used_cents, cost_limit_cents, period_end

Two more guards return 429 rate_limit_error (not 402):

  • END_USER_RATE_LIMITED: an end-user burst past your per_end_user_rate_per_minute setting. details carry end_user_id, limit_per_minute, retry_after, plus a Retry-After header. Configure all three sub-caps via client.settings.update(...); see Users.
  • SANDBOX_CONCURRENCY_LIMIT: too many runs in flight at once. details.scope says which ceiling: account (your own burst; details carry your concurrent_runs and limit) or platform (overall capacity, no counts). Wait for in-flight runs to finish and retry after the Retry-After header. 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:

Python
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.

codeMeaningRun statusRetry safe?
oauth_revokedThe connected model-plan credential was revoked or expiredfailedAfter reconnecting OAuth
subscription_quota_exhaustedThe connected Claude, OpenAI/Codex, xAI/Grok, or Gemini plan hit its usage quotafailedAfter the provider quota window resets
model_unavailableThe requested model is not available on the connected provider planfailedAfter switching to a matching model or connecting that provider
api_credit_balanceThe upstream provider key ran out of creditfailedAfter the balance is restored
rate_limitedUpstream model rate limit hit mid-run (auto-retried on scheduled runs)failedYes
overloadedThe upstream provider was overloaded or down (5xx/overload, auto-retried on scheduled runs)failedYes
sandbox_boot_timeoutThe sandbox never started; nothing executedfailedYes (auto-retried on scheduled runs)
sandbox_quota_exhaustedThe execution platform was at capacity, so no sandbox could be created; nothing executedfailedNot immediately. The same limit will refuse a retry; wait, then retry
incomplete_streamThe stream died before finishing; the agent may have partially executedfailedReview output first
snapshot_version_mismatchThe sandbox image runs an older agent runtime than the platform expects, so the run was stopped before executing anythingfailedNo. A retry hits the same image; we have to update it (reported automatically)
internal_timeoutAn internal service timed out while the run was starting or executingfailedYes
service_unreachableA service the run depends on was unreachablefailedYes
sandbox_unavailableThe sandbox could not be provisioned, or was lost mid-runfailedYes (auto-retried on scheduled runs when nothing had executed yet)
storage_errorA temporary storage error interrupted the runfailedYes
deploy_interruptedA platform deploy interrupted the active process; task runs (including scheduled runs) with a saved session resume in placefailed, then running if recovery claims itAutomatic for eligible task runs; otherwise retry
orphaned_on_restartThe run was interrupted before it finished — by a restart, or by a worker that was killed; found at startup, or within minutes by the ownership leasefailed, then running if recovery claims itAutomatic for eligible task runs; otherwise retry
stuck_running_no_activityThe run stopped producing activity and its process could not recover itfailedYes; review partial output first
subagent_stopped_at_completionDelegated work was still running when the parent turn endedfailedResumes automatically: silently in the run viewer, and server-side for scheduled and triggered runs; no action needed
error_max_turnsThe run used every conversation turn its max_turns budget allowed. A new message or Continue re-arms the budget on that run; a retry starts a child without the parent's max_turns. The Platform run viewer auto-Continues oncefailedContinue / reply (preferred); retry creates a child with a fresh/default budget. Raise max_turns if the work genuinely needs more in one request
internal_errorUnclassified platform error; reported automatically on our sidefailedYes, then contact support
unknownUnclassified upstream error; the message is in run.outputvariesReview output 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.

Block before executionRecorded result
Billing or account gateFailed run with the gate's code; nothing executed
Gate has no codeADMISSION_BLOCKED
Recurring schedule: FUNDING_REQUIRED, MODEL_CONNECTION_REQUIRED, or CLAUDE_CONNECTION_REQUIREDSkip without a failed run; try the next occurrence
Three consecutive skips aboveSchedule pauses (auto_disabled); one email sent; re-arms when the gate clears

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

Was this page helpful?