Skip to main content

Errors & Rate Limits

Error envelope

Errors are returned as a JSON body alongside a non-2xx HTTP status code:

{
"exception": "frappe.exceptions.ValidationError",
"exc_type": "ValidationError",
"_server_messages": "[\"{\\\"message\\\": \\\"Priority is a required field\\\", \\\"indicator\\\": \\\"red\\\"}\"]"
}

_server_messages is a JSON-encoded array of individual, user-facing messages — parse it if you want to surface field-level validation errors to your own users.

HTTP status codes

StatusMeaning
200 OKRequest succeeded.
202 AcceptedDelete succeeded.
400 Bad RequestMalformed request (e.g. invalid JSON in filters).
401 UnauthorizedMissing or invalid credentials.
403 ForbiddenAuthenticated, but the user lacks permission for this doctype/record, or the method isn't whitelisted.
404 Not FoundDoctype or document does not exist.
409 ConflictDocument was modified by someone else since you last fetched it (timestamp mismatch).
417 Expectation FailedA whitelisted method raised an application-level exception.
429 Too Many RequestsRate limit exceeded — see below.
5xxServer-side error. Safe to retry with backoff.

Conflict handling (optimistic locking)

Every document carries a modified timestamp. If you fetch a document, someone else updates it, and you then PUT your (now stale) copy, the request returns 409 Conflict. Re-fetch the document, re-apply your change, and retry.

Rate limits

API requests are rate-limited per API key to protect the shared infrastructure. Current limits are returned on every response via headers:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUnix timestamp when the window resets.

When you exceed the limit, you'll receive 429 Too Many Requests with a Retry-After header (seconds to wait before retrying). Implement exponential backoff for 429 and 5xx responses rather than retrying immediately.

Idempotency

GET and DELETE requests are naturally idempotent. For POST (create), there is no built-in idempotency key — if you need to safely retry a create request, check for an existing record with your own unique business key before creating a new one, or design your integration to be reconciliation- based rather than relying on exactly-once creation.