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
{
"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:
curl -X POST https://jettson.dev/api/v1/agents \
-H "Authorization: Bearer $JETTSON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"task": "..."}'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.