BETA
Skip to content

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

StatusMeaningBody
400 Bad RequestMalformed JSON or a missing/invalid required top-level field{ "error": "..." }
401 UnauthorizedAPI key missing, malformed, expired, or revoked{ "error": "Invalid or missing API key" }
403 ForbiddenValid key without the route's required scope, or a cross-team resource access{ "error": "Insufficient scope" }
404 Not FoundResource does not exist, or belongs to another team (never confirmed){ "error": "Not found" }
422 Unprocessable EntityChangeset validation failed, or a service policy rejected the request{ "error": "Validation failed", "details": { ... } }
429 Too Many RequestsRate-limit bucket exhausted, or a plan usage limit reached{ "error": "Rate limit exceeded", "retry_after": 23 }
500 Internal Server ErrorUnexpected 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 code for policy errors, not on the human-readable error.
  • Show field-level messages by walking the top-level details object on 422 responses.
  • Retry 429 and 5xx with exponential backoff, honouring the retry-after header on 429.
  • Do not retry 400, 401, 403, or 422 — fix the request first.

Built by Krafter Studio