API v1 · Errors
Error codes
Every Scripe API error returns a stable machine-readable code you can
switch on. The rest of the body is human-readable and may evolve. The
docs_url field on every error envelope deep-links to the entry for
that code on this page; most codes also have a dedicated page with
causes and remediation.
{
"error": {
"code": "key_revoked",
"message": "This API key has been revoked.",
"request_id": "req_01J9Z…",
"docs_url": "https://docs.scripe.io/api/v1/errors#key_revoked"
}
}See Conventions § Errors for the envelope structure. The same codes are returned by the MCP tool error envelope, so one parser handles both surfaces. OAuth endpoints use the same envelope on their JSON legs and RFC 6749 query parameters on the redirect leg — see OAuth § Errors.
How to handle errors in your client
- Switch on
code, notstatusormessage. Codes are stable; wording is not. HTTP status is a coarse classifier. - Always log
request_id. It's the only thing that lets us correlate your incident with our server-side trace. - Distinguish 401 vs 403 in your alerting. A 401 is a credentials issue (re-mint, rotate). A 403 is a permission issue (grant more scopes, or upgrade the plan). Wiring them to the same on-call channel is a common debugging dead-end.
- Respect 429. The
Retry-Afterheader is the smallest safe sleep value. Treat 429 as transient, never as a permanent failure. - Surface
docs_url. When showing an error to a developer, link straight to the relevant page.
400 — malformed requests
invalid_request
The request payload or a query parameter is malformed, or (on MCP) a
confirmation token is invalid/expired — the details.reason field
distinguishes confirmation_invalid / confirmation_expired.
Details →
bad_cursor
The pagination cursor is invalid or no longer applies (usually because a filter changed mid-loop). Restart the loop. Details →
bad_pagination
limit is not a positive integer, or another pagination parameter is
invalid. A limit above the endpoint's maximum is clamped, not rejected.
Details →
version_unsupported
Scripe-Api-Version names a version this build doesn't serve. The
message lists the accepted versions.
Details →
ssrf_blocked
A webhook endpoint URL resolves to a private or restricted IP. Details →
401 — fix your authentication
unauthenticated
No Authorization header, or no recognisable Bearer token.
Details →
invalid_token
The token format is invalid, or the token no longer maps to a live principal. Details →
key_revoked
The API key was revoked. Mint a new one. Details →
key_expired
The API key passed its expiresAt. Mint a new one.
Details →
402 — AI budget exhausted
usage_limit_exceeded
The workspace's weekly AI usage limit is exhausted (the primary
budget). details carries scope, percentage, and reset time.
Details →
spend_cap_exceeded
The workspace's daily API spend cap is reached (the abuse valve). Resets 00:00 UTC. Details →
403 — permissions
scope_missing
The token lacks the scope this endpoint requires. Look the endpoint up in the scope catalogue rather than parsing the message. Details →
plan_not_eligible
The workspace's plan does not include the product feature this specific capability uses. API access itself is on every plan. Details →
workspace_mismatch
The token cannot access the requested workspace. Details →
forbidden_project
The principal cannot access the requested project (assignment-based reach). Details →
admin_required
The action needs workspace-admin authority, not just the scope. Details →
404 / 405
not_found
The resource does not exist OR is not visible to this workspace — the API never distinguishes the two. Details →
method_not_allowed
The route does not accept this HTTP method. The Allow header lists
what it does accept. Details →
409 — state conflicts
conflict
The request conflicts with the resource's current state (dead LinkedIn connection on a scheduling write, publish already in flight, synced document refusing deletion). Details →
idempotency_key_conflict
The same Idempotency-Key was reused with a different request body
inside the 24-hour window. Details →
not_cancellable
The job is already running, done, failed, or cancelled — only QUEUED
jobs can be cancelled. Details →
endpoint_disabled
Reserved for a disabled webhook endpoint; no route returns it today. Details →
413 / 422
payload_too_large
The request body exceeded the per-endpoint size cap. Details →
unprocessable
Syntactically valid but semantically rejected (bad enum, past timestamp, cross-project reference). Details →
429
rate_limited
A rate-limit bucket is exhausted (or, on agent surfaces, the
publish budget). Honour Retry-After. Details →
5xx — Scripe-side
internal_error
Unexpected server-side failure. Retry with backoff; check status.scripe.io. Details →
service_unavailable
A downstream subsystem a request depends on (S3, LinkedIn, Clerk) is
unavailable. Not emitted by GET /v1/health, which reports degradation
in its own body instead. Details →
session_capacity
Reserved for MCP session-capacity exhaustion; no route returns it today (the server evicts the oldest session instead). Details →
OAuth codes
Returned by the OAuth endpoints (envelope on JSON legs, query parameters on the redirect leg — see OAuth § Errors):
invalid_grant
Code expired/used, PKCE mismatch, or refresh token invalid/revoked. Details →
invalid_client
Client authentication failed — unknown client_id.
Details →
invalid_client_metadata
The DCR registration body is invalid (e.g. a non-none auth method).
Details →
invalid_redirect_uri
The redirect URI is not registered for this client or malformed. Details →
unsupported_grant_type
Only authorization_code and refresh_token are supported.
Details →
unsupported_response_type
Only response_type=code is supported.
Details →
invalid_scope
A requested scope is outside the closed list, or a refresh tried to widen scope. Details →
client_suspended
The OAuth client was suspended by Scripe. Details →
refresh_token_reuse
Rotation reuse detected — the token family was revoked. Details →
workspace_unavailable
The targeted workspace is not accessible to the consenting user. Details →
consent_required
The consent is missing, expired, or revoked — re-authorize. Details →
access_denied
The user cancelled the consent screen. Details →