Error reference
Every Pluto API error is a JSON envelope with a stable, machine-readable code:
{
"error": {
"code": "quota_exceeded",
"message": "Period quota exhausted for current tier",
"fix_hint": "Wait for period reset or upgrade tier. Check /v1/usage for details.",
"docs_url": "https://docs.joinpluto.com/billing"
}
}Fields:
code(always) — snake_case error identifier. Branch on this, not onmessage.message(always) — human-readable description.retry_after(sometimes) — seconds to wait before retrying (rate limits).fix_hint(sometimes) — what to change; written for both humans and agents.docs_url(sometimes) — where to read more.
Status codes
Success: 200 OK, 201 Created, 202 Accepted (async work queued — poll the returned
URL), 204 No Content (deletes).
| Status | Common codes | Meaning + what to do |
|---|---|---|
400 | bad_request, validation_failed | Malformed JSON or a body that fails schema validation. validation_failed messages list each bad field as path: issue, joined by ;. Fix the request; don't retry as-is. |
401 | unauthorized | Missing/malformed Authorization header, invalid key, revoked key, or expired session key. Send Authorization: Bearer sk_live_.... |
402 | quota_exceeded | Period quota (actions / agent runs / workflows) or your tier's dollar spend cap is exhausted. Check GET /v1/usage; wait for the period reset or upgrade. Rarely, this is returned when the usage meter is temporarily unreadable — Pluto fails closed rather than allowing unmetered spend — so a retry after a short wait is reasonable if /v1/usage shows you under quota. |
403 | forbidden | The key's scopes don't cover the operation. Mint a key with the required scope (or the * wildcard). |
404 | not_found | Resource doesn't exist or belongs to another tenant. |
409 | conflict, already_exists, account_not_connected | State conflict — e.g. creating a contact that already exists, or calling a social endpoint before connecting that platform's account. The fix_hint says which. |
410 | claim_expired, claim_consumed | One-time checkout key claims: the key was already retrieved, or the pending claim expired. Contact support with your Stripe session id. |
422 | idempotency_collision | The Idempotency-Key you sent was already used with a different request body. Use a fresh key per logical operation. |
429 | rate_limited | Per-minute rate limit hit. See below. |
500 | server_error | Something broke on Pluto's side. Retry with exponential backoff; contact support if it persists. |
501 | (varies) | Documented capability that isn't live yet — e.g. deploy_mode_not_wired from POST /v1/onpage/apply when asked for its cf_worker/owned_source deploy modes. The message and fix_hint say what to use instead. What you can call is exactly what the API Reference ↗ shows as live. |
503 | stripe_unconfigured, admin_not_configured | A server-side dependency isn't configured. Contact support. |
A code is more stable than its status: a few codes arrive with different statuses depending on
the endpoint — account_not_connected, for example, is returned with 409 (TikTok/YouTube
insights), 400 (Meta insights), or 404 (Instagram DMs). Branch on error.code.
Rate limits (429)
Per-minute rate limits are enforced on specific endpoint groups — currently contacts, sequences, inbox, DMs (Discord/Facebook/Telegram), comment automations, content articles, listing agent runs, and call detail. On those endpoints the limit is per tenant, per endpoint over a 60-second sliding window, with the ceiling set by your tier:
| Tier | Requests / minute |
|---|---|
| Free | 30 |
| Hobby | 120 |
| Pro | 600 |
| Scale | 3,000 |
| Enterprise | Custom |
Endpoints outside those groups do not return 429 today — they are bounded by your period
quotas and spend cap instead (402, below).
A 429 response includes:
Retry-Afterheader — seconds to wait before retrying (alsoretry_afterin the body).X-RateLimit-Remaining: 0andX-RateLimit-Limitheaders.
Correct client behavior: sleep Retry-After seconds, then retry the identical request. Rate
limits are transient — nothing is consumed from your monthly quota by a 429.
Quota exhaustion (402)
Quotas are per billing period (currently the calendar month, UTC). Three separate ceilings can
trigger quota_exceeded:
- Actions — every metered request counts as one.
- Agent runs / workflow runs — counted separately on top of actions.
- Spend cap — a hard dollar ceiling per tier on total metered cost.
{
"error": {
"code": "quota_exceeded",
"message": "Period quota exhausted for current tier",
"fix_hint": "Wait for period reset or upgrade tier. Check /v1/usage for details.",
"docs_url": "https://docs.joinpluto.com/billing"
}
}Unlike 429, a 402 will not clear by waiting a few seconds — check GET /v1/usage to see
which counter is exhausted and when the period resets (period_end).
Retry cheat sheet
| Got | Retry? |
|---|---|
429 | Yes — after Retry-After seconds. |
500 | Yes — exponential backoff. |
502/503/timeouts | Yes — exponential backoff. |
402 | Only after the period resets or an upgrade. |
400, 401, 403, 404, 409, 422 | No — fix the request, key, or state first. |
For write endpoints that accept an Idempotency-Key header, reuse the same key when retrying
so a retried request can never double-apply.