Error Handling

The two error envelopes an agent meets, Content API errors and Agent Platform account errors, what each code means, and when to retry.

After this guide an agent can tell from error.code alone whether to fix the request, retry, top up, or stop. Key kinds are defined in Terms.

Two surfaces, one shape

An agent calls two surfaces, and both answer errors as an error object with a stable code. Branch on error.code, never on the message.

SurfaceRoutesCodesFull reference
Content API/v1/content/**content_*Content API Errors
Agent Platform account layer/v1/platform/agent/*, /v1/platform/health, /v1/platform/lti/*platform_* and a few othersThis page

A Content API error:

JSON
{
  "error": {
    "code": "content_invalid_api_key",
    "message": "Invalid or missing Content Integration API key",
    "status": 401,
    "requestId": "8f9c2a4e-3b1d-4c6e-9a7f-2d5e8b1c0a93"
  }
}

Every Content API error carries requestId. Quote it when you contact support. An Agent Platform error has code, message, and status, and validation errors add details. Some Agent Platform errors also carry error (such as "Unauthorized") and statusCode, which repeat the status; ignore them.

A removed /v1/platform content route answers with a 410 and a link to the migration guide, with any key or none:

JSON
{
  "error": {
    "code": "platform_route_removed",
    "message": "This Agent Platform content route was removed. Use the Content API with a tf_content_ key: https://tutorflow.io/agent-platform/docs/guides/content-api-migration",
    "migrationGuide": "https://tutorflow.io/agent-platform/docs/guides/content-api-migration",
    "status": 410
  }
}

Agent decision table

error.codeStatusRetry?What to do
content_invalid_request400 or 422NoFix the request. error.details names each bad field.
content_invalid_api_key401NoThe key is wrong, revoked, or expired. Use another key.
content_platform_key_not_accepted401NoYou sent a tf_platform_ key to the Content API. Use a tf_content_ key; see Migrating to the Content API.
platform_content_key_too_narrow403NoAn account route got a test key or a key without full access. Use the live key from registration or the dashboard.
content_insufficient_scope, content_classroom_not_allowed403NoThe key may not do this, or not in this classroom. Use defaultClassroomId.
content_not_found404NoThe resource is not there, or not in this classroom. A test key sees only the sandbox.
content_payment_required402After a top-upNot enough credits. For an agent key the error says how to top up; see When credits run out. Retry with the same Idempotency-Key.
content_payment_failed402After it is resolvedA payment failed. Priced calls stay blocked until it is paid.
content_key_budget_exceeded402After error.resetsAtThe key reached its monthly credit limit.
content_rate_limit_exceeded429YesWait Retry-After seconds. See Rate Limiting.
email_verification_required403After verificationA top-up route needs a verified email. Call POST /v1/platform/agent/verify-email/resend and have the owner open the link.
x402_payment_failed402Yes, after a few secondsThe USDC payment has not settled yet.
agent_channel_only403NoAgent top-ups are only for agent accounts. Buy credits in TutorFlow Billing.
platform_invalid_api_key401NoThe key is missing or invalid on an account route.
platform_route_removed410NoYou called a removed Platform content route. Move the call to the Content API; migrationGuide links the migration guide.
platform_rate_limit_exceeded429YesAn account route is over its limit, or this IP made more than 5 register attempts in the past hour. Wait Retry-After seconds. See Rate Limiting.
platform_internal_error, content_internal_error500YesRetry with backoff, starting at 1 second, at most 3 times.

The Content API has more codes, for idempotency conflicts, runs, uploads, and learners. See Error codes.

Common errors

Wrong key for the route

JSON
{
  "error": {
    "code": "content_platform_key_not_accepted",
    "message": "Agent Platform keys (tf_platform_) do not work on the Content API. Use a tf_content_ key: agent register returns one as contentApiKey, and the agent dashboard issues new ones.",
    "status": 401,
    "requestId": "..."
  }
}

See Authentication for which key works where.

Out of credits

JSON
{
  "error": {
    "code": "content_payment_required",
    "message": "The organization does not have enough available AI Credits for this request",
    "billingSessionUrl": "POST /v1/platform/agent/billing/session",
    "emailVerification": {
      "isRequired": true,
      "resendUrl": "POST /v1/platform/agent/verify-email/resend"
    },
    "status": 402,
    "requestId": "..."
  }
}

Where USDC payment is enabled (it is on production; see x402.enabled in GET /v1/platform/.well-known/agent.json), the error also carries x402 with the deposit routes and the $5 to $5,000 limits. The full body is in When credits run out. Nothing was charged. Top up, then retry with the same Idempotency-Key.

With a metered overdraft, this 402 comes only once the overdraft reaches overdraftLimit.

Email verification required

JSON
{
  "error": {
    "code": "email_verification_required",
    "message": "Email verification is required before adding billing or paying with USDC. Check your inbox or call POST /v1/platform/agent/verify-email/resend.",
    "status": 403
  }
}

Only the top-up routes need a verified email. Trial credits work without it.

  1. Call POST /v1/platform/agent/verify-email/resend.
  2. Ask the person who owns the account email to open the link. This is one of the few steps an agent cannot do itself.
  3. Retry the top-up.

A generation run that failed

A generate route can answer 202 and the run can still fail later. Read status and error on GET /v1/content/classrooms/{classroomId}/runs/{runId}, or the *.generation.failed webhook. Steps that fail for a passing reason are retried for you. See Retries and failures.

Retry rules

  • Retry 429 after Retry-After, and 5xx with exponential backoff and jitter.
  • Retry 402 only after the balance changed.
  • Do not retry other 4xx answers unchanged. Fix the request or the key first.
  • Send the same Idempotency-Key on every retry of a create, generate, or render, so a retry is never charged twice.