KYTRIX Developer documentation Financial Crime Control Plane

RFC 9457 problem+json

Error catalog

Every governed error code KYTRIX can return, what it means, and which ones are worth a branch in your integration.

Generated from ERROR_CATALOG in @kytrix/contracts · 38 codes

#The problem document

Every KYTRIX rejection is an RFC 9457 problem document served as application/problem+json. It always carries a code from the catalog below, and where a field is at fault, the dotted path to it.

json — a rejection
{
  "type": "kytrix:validation/pan_detected",
  "title": "Payload contains a card-number (PAN) pattern",
  "status": 400,
  "code": "kytrix:validation/pan_detected",
  "detail": "data.memo: must not contain a card number",
  "instance": "/v1/movements",
  "errors": [
    {
      "path": "data.memo",
      "code": "kytrix:validation/pan_detected",
      "message": "must not contain a card number"
    }
  ],
  "correlation_id": "6f9f0f2e-6b0e-4a1d-9d2f-2f6c3f2a1b77"
}
MemberMeaning
codeThe governed catalog value. This is what you branch on.
typeThe same value again, so a generic RFC 9457 client can key on either member.
titleShort, stable, human-readable summary of the code.
statusThe HTTP status. Also on the response line.
detailA human explanation naming the fix. Free text — never parse it.
instanceThe request path that produced the problem.
errors[]Field-level breakdown: path (dotted, array indices numeric), code, message. An empty path means the whole body.
correlation_idQuote this to KYTRIX support. Send your own with Kytrix-Correlation-Id and it travels into the logs.
typescript
import { ErrorCode, isKytrixApiError } from '@kytrix/sdk';

try {
  await kytrix.movements.submit(event, { idempotencyKey: row.id });
} catch (err) {
  if (!isKytrixApiError(err)) throw err;
  switch (err.code) {
    case ErrorCode.ValidationPanDetected:   // a card number reached a free-text field
      return quarantineRow(row, err.errorsAt('data.memo'));
    case ErrorCode.SequenceConflict:        // same entity version, different content
      return bumpSequenceAndRetry(row);
    case ErrorCode.IdempotencyKeyConflict:  // this key was used with a different body
      return alertOncall('idempotency key reused', err.correlationId);
    case ErrorCode.RateLimitExceeded:       // the SDK already backed off maxAttempts times
      return deferRow(row, err.rateLimit?.retryAfterSeconds ?? 60);
    default:
      throw err;
  }
}

#What is retryable

StatusRetry?Why
400 / 422NeverThe payload cannot become valid by being sent again. Fix the mapping.
401Only after fixingA bad signature, a stale clock or a reused nonce. Re-signing with a fresh nonce and a correct clock is a new request, not a retry.
403NeverThe credential lacks the scope. Issue a key that has it.
409Never blindlysequence/conflict means your content differs — decide what the right version is. idempotency/key_conflict means you reused a key; pick a new one or resend the original body.
413NeverSplit the batch or the body.
429YesHonour Retry-After; back off with jitter.
500 / 502 / 503 / 504Yes, with a keyRetry only writes that carried an Idempotency-Key. An unkeyed POST that timed out must not be repeated — a duplicated movement is worse than a failure you can see.
no response at allYes, with a keySame rule. The SDK enforces it for you.

#The catalog

All 38 codes, generated from ERROR_CATALOG in packages/contracts/src/errors.ts. Codes are grouped by family; the family is the segment between kytrix: and /.

#auth/*

The request signature, the timestamp, the nonce or the credential is the problem.

CodeStatusTitle
kytrix:auth/invalid_signature401Request signature is invalid
kytrix:auth/timestamp_skew401Request timestamp is outside the allowed clock window
kytrix:auth/nonce_replayed401Request nonce was already used
kytrix:auth/unknown_key401Unknown API key id
kytrix:auth/key_revoked401API key has been revoked
kytrix:auth/key_expired401API key has expired
kytrix:auth/insufficient_scope403Credential lacks the scope required for this operation
kytrix:auth/unauthenticated401Authentication required

#authz/*

Authenticated, but not allowed to do this.

CodeStatusTitle
kytrix:authz/forbidden403Forbidden

#validation/*

The payload itself is unacceptable. Fix the mapping — a retry cannot help.

CodeStatusTitle
kytrix:validation/schema400Request does not match the schema
kytrix:validation/pan_detected400Payload contains a card-number (PAN) pattern
kytrix:validation/money_scale_mismatch400Money scale does not match the asset registry
kytrix:validation/timestamp_out_of_range400Timestamp is outside the accepted range
kytrix:validation/unsupported_schema_version400Unsupported schema_version

#sequence/*

A version conflict on an identity you already sent. Never a silent overwrite.

CodeStatusTitle
kytrix:sequence/conflict409Same identity with different content and no higher sequence
kytrix:sequence/stale409Sequence is lower than an already-applied version

#idempotency/*

Your Idempotency-Key was used differently, or is still in flight.

CodeStatusTitle
kytrix:idempotency/key_conflict409Idempotency-Key was already used with a different body
kytrix:idempotency/in_flight409A request with this Idempotency-Key is still being processed

#batch/*

The batch envelope broke a limit.

CodeStatusTitle
kytrix:batch/too_large413Batch exceeds the maximum number of events

#reference/*

Well formed, but it points at a canonical object KYTRIX does not know. Send the referenced object first.

CodeStatusTitle
kytrix:reference/unknown_party422Referenced party is unknown
kytrix:reference/unknown_account422Referenced account is unknown
kytrix:reference/unknown_instrument422Referenced instrument is unknown
kytrix:reference/unknown_movement422Referenced movement is unknown

#rate_limit/*

You are over the per-tenant budget. Honour Retry-After.

CodeStatusTitle
kytrix:rate_limit/exceeded429Rate limit exceeded

#request/*

A transport-level limit.

CodeStatusTitle
kytrix:request/payload_too_large413Request payload too large

#not_found/*

No such object in this tenant. Cross-tenant reads do not exist to be found.

CodeStatusTitle
kytrix:not_found/resource404Resource not found

#conflict/*

A resource-state conflict with no more specific family.

CodeStatusTitle
kytrix:conflict/resource409Resource conflict

#webhook/*

The webhook endpoint URL is not one KYTRIX will deliver to.

CodeStatusTitle
kytrix:webhook/invalid_endpoint400Webhook endpoint URL is not allowed

#import/*

Something about a historical import or one of its chunks.

CodeStatusTitle
kytrix:import/invalid_chunk400Import chunk is invalid
kytrix:import/not_found404Import not found

#tenant/*

The tenant behind the credential is unknown or suspended.

CodeStatusTitle
kytrix:tenant/unknown401Unknown tenant
kytrix:tenant/suspended403Tenant is suspended

#governance/*

A governed workflow refused: approval missing, maker equals checker, an immutable object, or an illegal state transition. Mostly console surfaces.

CodeStatusTitle
kytrix:governance/approval_required409Operation requires an approval that is not present
kytrix:governance/maker_checker_violation403Approver must differ from the maker
kytrix:governance/immutable409Object is immutable
kytrix:governance/invalid_transition409State transition is not allowed

#service/*

KYTRIX is shedding load or degraded. Retry with backoff.

CodeStatusTitle
kytrix:service/unavailable503Service temporarily unavailable

#internal/*

A KYTRIX bug. Quote correlation_id to support.

CodeStatusTitle
kytrix:internal/error500Internal error

#The ones you will actually meet

Most of the catalog belongs to console workflows you will never call. On the ingestion path, these are the codes worth writing a branch for on day one: