Errors
Error codes returned by the REST API and the equivalent JSON-RPC error codes on MCP.
The REST API returns a consistent error response shape:
{
"error": "machine_readable_code",
"message": "Optional human-readable detail"
}Some errors include additional fields for client-side handling, such as quota errors, rate limits, and scope violations. These are documented inline below.
Standard codes
400 invalid_body
Request body failed Zod schema validation. Inspect the message field when present.
400 invalid_id
Path UUID failed parsing.
401 missing_bearer_token
No Authorization: Bearer … header was sent.
401 invalid_token
The token is unknown, hash-mismatched, revoked, or expired. Mint a new one at /settings/integrations.
402 quota_exceeded
The user's monthly transcription quota is exhausted. The response includes the tier and cap so the client can render an upgrade prompt:
{
"error": "quota_exceeded",
"resource": "transcription_minutes",
"tier": "tier_10",
"cap_per_month": 90000,
"message": "..."
}403 capability_denied
The token lacks the required capability (typically write). The response includes the required and held capabilities:
{
"error": "capability_denied",
"required": "write",
"have": ["read"]
}403 forbidden_scope
The operation requires a wider scope than the token carries:
POST /api/v1/folders: requires a whole-library tokenDELETE /api/v1/folders/{id}: requires a whole-library tokenPOST /api/v1/items/{id}/folders: returned to single-folder tokens because there is nowhere else to add toPOST /api/v1/items/{id}/folders: returned to multi-folder tokens when the target folder is not in scope. Includes anout_of_scopearray:
{
"error": "forbidden_scope",
"message": "Target folder(s) are not in this token's scope.",
"out_of_scope": ["aaa-bbb-ccc-…"]
}404 not_found
The resource does not exist, is not owned by the caller, or is outside the token's scope. The 404 deliberately does not distinguish these cases. Distinguishing would leak the existence of resources outside scope.
404 folder_not_found
POST /api/v1/items/{id}/folders returns this when one or more target folders do not exist or are not owned by the caller. Includes a missing array of the offending UUIDs:
{
"error": "folder_not_found",
"missing": ["aaa-bbb-…"]
}409 name_taken
POST /api/v1/folders returns this when a folder with the given name already exists (case-insensitive per user).
413 file_too_large
The upload exceeded the size cap. The body includes maxBytes:
{
"error": "file_too_large",
"maxBytes": 52428800
}415 unsupported_media_type
The file MIME is not in the allowlist, or magic-byte verification detected that the bytes do not match the declared MIME (rename attack defense).
422 size_mismatch, 422 object_missing, 422 extraction_failed
Upload-finalize errors. Something went wrong between signed-URL issue and finalize:
size_mismatch: the uploaded size differs from the declared sizeobject_missing: the path the client claimed to PUT to has no objectextraction_failed: text extraction failed (for example, a corrupt PDF)
429 rate_limited
The per-token rate limit was exceeded. The response includes retry_after_sec:
{
"error": "rate_limited",
"retry_after_sec": 600
}Per-endpoint caps:
| Endpoint family | Cap |
|---|---|
| Read endpoints (GET) | 1000 / hr |
| Ingest, item-folder membership add/remove | 60 / hr |
| Item delete, folder create | 60 / hr |
| Folder delete, item bulk delete | 30 / hr |
MCP add_to_knowledge | 60 / hr |
| Per-token monthly write cap | 200 (default) |
500 internal_error
An unexpected server error occurred. Operator alerts fire. The caller should retry with backoff. If the error persists for more than five minutes, file a bug at support@graniite.co and include the x-vercel-id from the response header.
MCP error mapping
MCP uses JSON-RPC 2.0 error codes. The mapping to the REST shape is:
| REST | JSON-RPC |
|---|---|
400 invalid_body | -32602 InvalidParams |
401 missing_bearer_token | -32600 InvalidRequest (transport-level) |
401 invalid_token | -32600 |
403 capability_denied | Custom -32605 |
404 not_found | Custom -32603 returned as text with isError: true |
429 rate_limited | Custom -32605 with retry_after_sec |
500 internal_error | -32603 InternalError |
Most MCP clients surface the error text from content[0].text to the agent, which can then react in its own conversation flow.
Tips for retry logic
401: never retry. Mint a new token.402: never retry. Show the user an upgrade prompt.403: never retry. The scope or capability needs to change.404: retry once if you suspect an eventual-consistency race between a create and an immediate read. Otherwise treat as terminal.409: pick a different name and retry once.429: sleepretry_after_sec(or back off 60s if absent), then retry. Cap retries at 3.500: exponential backoff (1s, 2s, 4s, 8s), maximum 3 retries.