Errors
Every error from Kudos CRM uses the same JSON envelope:
{
"errors": [
{
"status": "401",
"code": "signature_invalid",
"title": "Unauthorized",
"detail": "X-Kudos-Signature did not match any active signing secret on this source.",
"meta": { "source_id": "src_…" }
}
]
}
| Field | Notes |
|---|---|
status | Stringified HTTP status code. Mirrors the response line. |
code | Stable machine-readable identifier. Safe to switch on. |
title | Short human label. |
detail | One-sentence reason. Safe to surface in operator logs. Never carries tenant PII. |
meta | Optional structured context. Shape varies per code. |
Multiple errors per response are possible on validation paths; the array always contains at least one entry.
Status code reference
200 / 202 — Accepted
The ingest endpoint returns 202 on first acceptance and on idempotent replay. The body distinguishes:
{ "data": { "received": true, "event_id": "evt_…", "duplicate": false } }
{ "data": { "received": true, "event_id": "evt_…", "duplicate": true } }
Treat both as success; do not retry on duplicate: true.
400 — Bad Request
Malformed envelope: missing required field, invalid JSON, schema_version not understood. Discard the event; do not retry.
code | Cause |
|---|---|
invalid_json | The body is not parseable JSON. |
envelope_invalid | Required envelope field missing or wrong type. |
schema_version_invalid | schema_version is not an integer or not understood. |
401 — Unauthorized
Authentication failed.
code | Cause |
|---|---|
signature_missing | X-Kudos-Signature header absent. |
signature_malformed | Header present but does not parse. |
signature_invalid | HMAC did not match any active signing secret on the source. |
signature_stale | |now - t| > 300. Re-sign with fresh timestamp and retry. |
bearer_missing | (Pull / reverse channel) Authorization header absent. |
bearer_invalid | Bearer present but does not match an active key. |
bearer_expired | Bearer matched a key whose expires_at has passed. |
403 — Forbidden
Authentication succeeded, but the credential lacks the required scope.
code | Cause |
|---|---|
scope_missing | Bearer scopes do not include the one this endpoint requires. |
404 — Not Found
The route resolved to no resource. For ingest specifically:
code | Cause |
|---|---|
source_not_found | source_id in the URL does not match any registered source. Returned even when the source_id is wrong but the signature would otherwise verify, so attackers cannot enumerate valid source_ids. |
409 — Conflict
The resource is in a state that does not permit the operation. On the management endpoints, e.g.:
code | Cause |
|---|---|
sync_not_configured | The source has no sync_base_url or sync_api_key; pull-channel operations not possible. |
source_inactive | The source was deactivated by an operator. Reactivate or register a new one. |
413 — Payload Too Large
Body exceeds 1 MiB. Discard; do not retry with a larger body.
415 — Unsupported Media Type
Content-Type is not application/json; charset=utf-8 (or equivalent). Discard.
422 — Unprocessable Entity
Envelope is structurally valid but contents fail business rules.
code | Cause |
|---|---|
event_type_not_in_catalog | event_type is not one of the 36 documented types. |
event_type_not_allowed | event_type is in the catalog but the source's allowed_event_types list excludes it. |
tenant_external_ref_missing | An event that requires a tenant reference omitted tenant.external_ref. |
429 — Too Many Requests
You hit the per-source ingest rate limit (default 100 req/s, configurable per source). Honour Retry-After and back off.
502 — Bad Gateway
Kudos CRM tried to call a downstream (your /v1/sync/manifest for example) and got a non-2xx or unreachable. Retry from the operator side or address the downstream issue.
503 — Service Unavailable
Kudos CRM is degraded or in maintenance. The Retry-After header is set. Honour it and retry.
What to log
For every non-2xx your producer sees, log at least:
- the request
event_id(orX-Kudos-Idempotency-Keyfor reverse channel) - the response
statusandcode - the
detailtext - the URL
That four-field log line is enough for support to triage 99 % of integration tickets without asking you to re-run anything.