zenly/Documentation
Search documentation
English
API referenceGetting started

Errors

Every refusal is a problem document: an HTTP status that carries the meaning, and a body that explains the occurrence. Branch on the status.

The body

Errors come back as application/problem+json, the format described by RFC 7807. Three fields are always present.

Response401 Unauthorized
{
  "title": "Unauthorized",
  "status": 401,
  "detail": "invalid or expired API key"
}
statusinteger

The HTTP status, repeated in the body. This is the field to branch on.

titlestring

Short summary of the kind of problem, stable across occurrences of the same status.

detailstring

A sentence about this particular occurrence, meant for a developer reading a log. Do not match on it: the wording can change without the status changing.

type, instance, errorsstring, string, arrayOPTIONAL

The contract declares these three as well. When a request fails validation, errors names each offending field, carrying its location in the request, a message and the value that was rejected.

A validation failure is the one worth handling specially, because it tells you exactly what to change. Here a calendar window was sent as bare dates instead of timestamps:

Response422 Unprocessable Entity
{
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "validation failed",
  "errors": [
    {
      "message": "invalid date/time for format 2006-01-02T15:04:05.999999999Z07:00",
      "location": "query.from",
      "value": "2026-07-27"
    }
  ]
}
There is no error code field

There is no machine-readable code beside the status, so the status is the only thing safe to branch on. Do not match on title or detail.

Statuses

Any endpoint on the API can answer with any of these.

401
Unauthorized

No bearer token, or a token that is unknown, revoked or expired. All four answer identically on purpose, so a caller probing tokens learns nothing about which is which.

402
Payment Required

The organization’s plan does not include the public API. It is available on Pro and above, and the check runs on every request, so a downgrade stops keys that were minted earlier.

403
Forbidden

The organization is not active.

429
Too Many Requests

The key is over its rate limit. Back off and retry.

500
Internal Server Error

Something failed on our side. The body carries a short sentence and never an internal error string, so quote your own request timestamp when reporting one.

What to retry

  • 429 is the one worth retrying automatically. Wait for the window to reset rather than retrying immediately.
  • 500 may be transient, so one retry with a delay is reasonable. Repeated 500s are ours to fix, not yours to work around.
  • 401, 402 and 403 will not resolve by retrying. Something has to change first: the credential, the plan or the organization.

There are no idempotency keys, so retrying a POST that actually succeeded can create a second record. Retry writes deliberately rather than automatically, and check before you repeat one.

One exception to the format

A path that does not exist under /v1 answers 404 as plain text rather than JSON. Treat a non-JSON error body as a wrong URL.