Guide
Errors
Every error is an RFC 9457 problem document (application/problem+json) carrying
a machine-readable code. Programs branch on code and
status; the human-readable fields exist for logs.
The shape
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
X-Request-Id: 0f0a2f6c-3f2f-4f7a-9f0e-2d1b8c4a55e1
{
"type": "https://kelomo.fi/developers/guides/error-codes/#code-read_only_key",
"title": "Forbidden",
"status": 403,
"detail": "read_only_key",
"code": "read_only_key",
"requestId": "0f0a2f6c-3f2f-4f7a-9f0e-2d1b8c4a55e1"
} | Field | Meaning |
|---|---|
type |
Problem type URI. For a catalogued code it links to that code's entry in the
error code reference;
about:blank otherwise.
|
title | Short human-readable phrase for the status class. |
status | The HTTP status code, repeated in the body. |
detail | Human-readable specifics for this occurrence. |
requestId |
Identifier of this request, mirrored in the X-Request-Id response header.
Quote it in support requests.
|
code | Stable machine-readable identifier — the field to branch on. Translation into user-facing language is the client's job. |
Every response — success or failure — also carries X-Request-Id, and the problem
document repeats it as requestId. Log it: it is the one value support can use to
find your exact request.
Status codes and representative codes
| Status | When | Example code |
|---|---|---|
400 | Malformed input, failed validation, or a missing required header. | idempotency_key_required |
401 | Missing, revoked or expired credentials. | api_key_expired, api_key_owner_inactive |
403 | Authenticated but not allowed — scope, capability or key mode. | read_only_key, report_type_not_public |
404 | The resource does not exist in your organisation. | — |
409 | Conflict with current state — often an idempotency collision. | idempotency_key_reused, idempotency_key_in_flight |
422 | Well-formed but semantically rejected. | key_scope_exceeds_creator |
429 | Rate limit exceeded; honour Retry-After. | — |
5xx | Server-side failure — safe to retry idempotent requests with backoff. | — |
Handling errors
const response = await fetch(url, { headers });
if (!response.ok) {
const problem = await response.json().catch(() => null);
switch (problem?.code) {
case 'api_key_expired':
// mint or rotate the key, then retry
break;
case 'idempotency_key_reused':
// same Idempotency-Key sent with a different payload — bug on our side
break;
default:
if (response.status === 429) {
const wait = Number(response.headers.get('retry-after') ?? '5');
await new Promise((r) => setTimeout(r, wait * 1000));
// …retry
} else {
throw new Error(`Kelomo API ${response.status}: ${problem?.code ?? 'unknown'}`);
}
}
} - Branch on
code, not ondetailortitle— codes are part of the contract, wording is not. -
Retry
429afterRetry-Afterand5xxwith exponential backoff. Retry POSTs only with an Idempotency-Key. - Treat unknown codes as fatal for the request but not for the integration: new codes are added over time as new operations join the public surface. Every code is listed in the error code reference.