Appearance
Errors
Every Krafter API uses conventional HTTP status codes and a flat JSON body whose top-level error field is always a human-readable string. Some shapes add sibling fields (details, retry_after, code) depending on which layer rejected the request. Branch on the HTTP status first, then on the body's top-level fields.
json
// The baseline shape — every error response has at least this
{ "error": "Not found" }Audit API exception
The Audit service wraps responses in a richer { data, meta, error } envelope, so its errors look like { "error": { "code": ..., "message": ... } } instead of the flat shape below. Its :strict_rate_limit 429 is the one exception that falls back to the platform shape. See Authentication and the Audit service docs for details.
Status codes
| Status | Meaning | Body |
|---|---|---|
400 Bad Request | Malformed JSON or a missing/invalid required top-level field | { "error": "..." } |
401 Unauthorized | API key missing, malformed, expired, or revoked | { "error": "Invalid or missing API key" } |
403 Forbidden | Valid key without the route's required scope, or a cross-team resource access | { "error": "Insufficient scope" } |
404 Not Found | Resource does not exist, or belongs to another team (never confirmed) | { "error": "Not found" } |
422 Unprocessable Entity | Changeset validation failed, or a service policy rejected the request | { "error": "Validation failed", "details": { ... } } |
429 Too Many Requests | Rate-limit bucket exhausted, or a plan usage limit reached | { "error": "Rate limit exceeded", "retry_after": 23 } |
500 Internal Server Error | Unexpected server error | { "error": "..." } |
Error shapes
Authentication — flat string
Returned by the API-key plug. The body has exactly one key, error — no code, no details.
json
// 401 — missing, malformed, expired, or revoked key
{ "error": "Invalid or missing API key" }json
// 403 — valid key, but it does not hold the route's required scope
{ "error": "Insufficient scope" }See Authentication for the key format, scopes, and the per-request lifecycle.
Not found — flat string
json
// 404
{ "error": "Not found" }A resource that belongs to another team returns 404, not 403 — the API never confirms the existence of resources outside your team.
Validation — string + top-level details
Returned when an Ecto changeset fails. details is a sibling of error at the top level (not nested inside it), mapping each field to a list of messages.
json
// 422
{
"error": "Validation failed",
"details": {
"name": ["can't be blank"],
"url": ["must be a valid http or https URL"]
}
}For batch endpoints, a failing item reports its position:
json
// 422
{
"error": "Validation failed at index 2",
"details": { "to": ["can't be blank"] }
}Atom-derived errors (e.g. :not_found, :limit_exceeded) carry only error — the details map appears only for changeset-derived validation failures.
Rate limit — string + retry_after
Returned when a rate-limit bucket is exhausted. The same value is also set as the retry-after HTTP header.
http
HTTP/1.1 429 Too Many Requests
content-type: application/json
retry-after: 23
x-ratelimit-limit: 100
x-ratelimit-remaining: 0
x-ratelimit-reset: 1714128360
{ "error": "Rate limit exceeded", "retry_after": 23 }Plan-level usage limits surface as a 429 with a different message and no retry_after:
json
// 429
{ "error": "Monthly usage limit exceeded. Upgrade your plan." }See Rate limits for the buckets, headers, and window semantics.
Policy — string + machine code
Some services reject a request because of a service-specific rule (stream, sandbox, override, …). These add a stable, machine-readable code alongside the human-readable error. Branch on code, not on error — the message wording may change.
json
{
"error": "API key is not allowed to send on stream 'broadcast'",
"code": "stream_not_allowed"
}The code enum is documented per service. See Mail → Error handling for the canonical example.
Service-specific errors
Each service documents the status codes and any extra fields unique to it:
Best practices
- Check the HTTP status before parsing the body — different statuses emit different shapes.
- Branch on
codefor policy errors, not on the human-readableerror. - Show field-level messages by walking the top-level
detailsobject on422responses. - Retry
429and5xxwith exponential backoff, honouring theretry-afterheader on429. - Do not retry
400,401,403, or422— fix the request first.