Errors
Authenticated endpoints answer every failure, including a failure on our side, with one envelope. Branch your code on error.type; use error.code for the exact reason.
The envelope
{
"error": {
"type": "invalid_request_error",
"code": "MISSING_PARAMETER",
"message": "`since` query parameter is required.",
"details": { "parameter": "since" },
"doc_url": "https://developer.tendralhealth.com/docs/errors#MISSING_PARAMETER",
"request_id": "req_018f5a2e7c127c8da123cafed00dfeed"
}
}messageis for people and your logs. Do not parse it; it can change.detailsis present when there is more to say, such as the parameter at fault.doc_urllinks to this page, to the entry for that code.request_idmatches theX-Request-Idheader. Quote it when you write to us.
Types
| Type | HTTP | Retry? |
|---|---|---|
authentication_error | 401 | No |
permission_error | 403 | No |
invalid_request_error | 400, 404 | No |
idempotency_error | 409 | No |
rate_limit_error | 429 | Yes, after Retry-After |
api_error | 500 | Yes, with backoff |
New codes can be added to an existing type. Code that branches on type keeps working when they are.
Codes
UNAUTHORIZED
The bearer key is missing, malformed, expired or revoked.
| HTTP | Type | Retry? |
|---|---|---|
| 401 | authentication_error | No |
What to do: Send Authorization: Bearer with a current key. If the key was rotated or revoked, use its replacement.
INSUFFICIENT_SCOPE
The key is valid but does not carry the scope this endpoint needs.
| HTTP | Type | Retry? |
|---|---|---|
| 403 | permission_error | No |
What to do: error.details.required_scope names the scope. Ask for a key that carries it.
INVALID_TOKEN
A path token is not in the expected format.
| HTTP | Type | Retry? |
|---|---|---|
| 400 | invalid_request_error | No |
What to do: Check the token against the pattern the reference gives for that path parameter.
INVALID_CAMPAIGN_ID
A campaign id is not in the prefixed cmp_ form.
| HTTP | Type | Retry? |
|---|---|---|
| 400 | invalid_request_error | No |
What to do: Pass the prefixed id exactly as the API returned it.
TOKEN_NOT_FOUND
The token is well formed, but no record exists for it.
| HTTP | Type | Retry? |
|---|---|---|
| 404 | invalid_request_error | No |
What to do: Check that the record has been published to you. A record that was withdrawn is not found.
MISSING_PARAMETER
A required query parameter is missing.
| HTTP | Type | Retry? |
|---|---|---|
| 400 | invalid_request_error | No |
What to do: error.details.parameter names it. Add it and send the request again.
INVALID_PARAMETER
A parameter is present but has the wrong type, format or value.
| HTTP | Type | Retry? |
|---|---|---|
| 400 | invalid_request_error | No |
What to do: error.details.parameter names the parameter. Common causes: a timestamp or date that is not ISO 8601, a limit outside 1–500, a cursor that is not one the API returned.
INVALID_EXPAND
A value in expand[] is not allowed on this endpoint.
| HTTP | Type | Retry? |
|---|---|---|
| 400 | invalid_request_error | No |
What to do: error.details.allowed lists the values this endpoint accepts.
INVALID_TYPE_FILTER
A value in type or types[] is not one of the event types this endpoint lists.
| HTTP | Type | Retry? |
|---|---|---|
| 400 | invalid_request_error | No |
What to do: error.details.allowed lists the accepted types.
IDEMPOTENCY_KEY_REUSED
An Idempotency-Key was sent again with a different request body.
| HTTP | Type | Retry? |
|---|---|---|
| 409 | idempotency_error | No |
What to do: Use a new key for a new operation. Reuse a key only to retry the identical request.
RATE_LIMITED
The key has used its requests for the current window.
| HTTP | Type | Retry? |
|---|---|---|
| 429 | rate_limit_error | Yes, after Retry-After |
What to do: Wait the number of seconds in Retry-After, then retry. Do not retry sooner.
INTERNAL_ERROR
Something failed on our side. Nothing about your request was wrong.
| HTTP | Type | Retry? |
|---|---|---|
| 500 | api_error | Yes, with backoff |
What to do: Retry with exponential backoff. If it keeps failing, write to us with the request_id.