Already using the Anthropic or OpenAI SDK? Ship your agent to production in one call.

See the quickstart

Errors

The error envelope, every error code Jettson emits, and how to react.

Every Jettson API error comes back in a stable envelope. Parse the code field to branch on category — message is for humans, code is for code.

Error envelope

json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many spawn requests. Try again in 12s.",
    "details": {
      "plan": "free",
      "window": "minute",
      "limitPerMinute": 5,
      "limitPerHour": 30
    }
  }
}

| Field | Always present | | --- | --- | | error.code | Yes — stable string identifier | | error.message | Yes — human-readable, can change | | error.details | Optional — structured context for UI rendering |

Some errors also include a Retry-After HTTP header (always for rate_limited).

Code reference

Authentication

| Code | HTTP | Meaning | | --- | --- | --- | | invalid_api_key | 401 | Missing, malformed, or revoked key | | missing_id_token | 401 | Only on Console endpoints — missing Firebase ID token | | invalid_id_token | 401 | Firebase token expired or signature invalid |

Validation

| Code | HTTP | Meaning | | --- | --- | --- | | invalid_body | 400 | Body wasn't JSON or wasn't an object | | invalid_task | 400 | Agent spawn — missing or oversized task | | invalid_key | 400 | Memory write — missing/oversized key | | invalid_value | 400 | Memory write — wrong type or over 5000 chars | | invalid_query | 400 | Bad query string parameters | | invalid_plan | 400 | Checkout — unknown plan tier | | invalid_price_id | 400 | Checkout — unknown Stripe price |

Quotas (402 — Payment Required)

| Code | Meaning | Recommended action | | --- | --- | --- | | monthly_quota_exceeded | Plan's agent-hour budget used | Upgrade or wait for the first of the month | | memory_quota_exceeded | Memory pool full | POST /api/v1/memory/dedupe first; otherwise upgrade |

Rate / concurrency (429 — Too Many Requests)

| Code | Meaning | Recommended action | | --- | --- | --- | | rate_limited | Per-key spawn rate hit | Honor Retry-After header; back off | | concurrent_limit_reached | Plan's concurrent agents at cap | Wait for one to finish, or stop one |

Resources

| Code | HTTP | Meaning | | --- | --- | --- | | agent_not_found | 404 | Wrong agent ID, or it belongs to a different account | | not_found | 404 | Memory not found by (key, namespace) | | user_not_found | 404 | Internal billing routes when the Firebase user doc is missing |

Provider

| Code | HTTP | Meaning | | --- | --- | --- | | temporarily_unavailable | 503 | Container provider transient failure — retry with backoff | | billing_unavailable | 502 | Stripe transient failure during checkout / portal mint | | mind_unavailable | 503 | Internal reasoning proxy errored — retry with backoff |

Generic

| Code | HTTP | Meaning | | --- | --- | --- | | internal_error | 500 | Unexpected — log it, retry once with backoff, then bail |

Retry guidance

| Code | Retry? | How | | --- | --- | --- | | rate_limited | Yes | Wait Retry-After seconds, then retry. Don't ignore the header. | | temporarily_unavailable / mind_unavailable | Yes | Exponential backoff, 1s → 2s → 4s, max 3 tries. | | internal_error | Once | If the second call also fails, surface the error — don't loop. | | concurrent_limit_reached | Yes (delayed) | Poll until concurrent count drops, then retry. Or upgrade. | | monthly_quota_exceeded | No | Reset is on the 1st of next month. | | invalid_* | No | Fix the request before retrying. | | agent_not_found / not_found | No | The resource doesn't exist. |

Example

A spawn that hits the concurrent limit:

bash
curl -X POST https://jettson.dev/api/v1/agents \
  -H "Authorization: Bearer $JETTSON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task": "..."}'
text
HTTP/2 429
Content-Type: application/json

{
  "error": {
    "code": "concurrent_limit_reached",
    "message": "You've reached your concurrent agent limit (3). Stop an existing agent or upgrade your plan.",
    "details": {
      "plan": "pro",
      "currentConcurrent": 3,
      "maxConcurrent": 3
    }
  }
}

The right move here is to either stop a running agent (DELETE /api/v1/agents/{id}) or wait for one to finish, then retry.