Graniite Docs

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 token
  • DELETE /api/v1/folders/{id}: requires a whole-library token
  • POST /api/v1/items/{id}/folders: returned to single-folder tokens because there is nowhere else to add to
  • POST /api/v1/items/{id}/folders: returned to multi-folder tokens when the target folder is not in scope. Includes an out_of_scope array:
{
  "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 size
  • object_missing: the path the client claimed to PUT to has no object
  • extraction_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 familyCap
Read endpoints (GET)1000 / hr
Ingest, item-folder membership add/remove60 / hr
Item delete, folder create60 / hr
Folder delete, item bulk delete30 / hr
MCP add_to_knowledge60 / hr
Per-token monthly write cap200 (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:

RESTJSON-RPC
400 invalid_body-32602 InvalidParams
401 missing_bearer_token-32600 InvalidRequest (transport-level)
401 invalid_token-32600
403 capability_deniedCustom -32605
404 not_foundCustom -32603 returned as text with isError: true
429 rate_limitedCustom -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: sleep retry_after_sec (or back off 60s if absent), then retry. Cap retries at 3.
  • 500: exponential backoff (1s, 2s, 4s, 8s), maximum 3 retries.

On this page