Generated from the contract
API reference
Every endpoint on the KYTRIX public API host, with request and response shapes read directly out of the zod schemas the server validates against.
Generated from @kytrix/contracts · payload schema_version 2026-09-01 · 31 operations · 19 schemas
#How this page is generated
Every field, type and constraint below is read out of the zod schemas in @kytrix/contracts at build time — the same schema objects the edge calls safeParse with on your request. Nothing on this page is transcribed by hand, so it cannot drift from what the server enforces.
Endpoint paths come from a table that names the source file registering each route; the build asserts the path literal is still in that file and fails otherwise. Four response shapes are mirrored from their route handler rather than generated, because @kytrix/contracts does not define them yet; each one says so where it appears.
The same document is published as OpenAPI 3.1 for client generation.
#Conventions
| Rule | Detail |
|---|---|
| JSON field names | Always snake_case, request and response alike. |
| Schema version | Every event envelope carries schema_version: "2026-09-01". Any other value is kytrix:validation/unsupported_schema_version. |
| Unknown members | Envelopes are strict. An unrecognised key is a rejection, never a silently dropped field. |
| Timestamps | RFC 3339 with an explicit offset or Z, colon required in the offset (-04:00, not -0400). Lowercase t/z are refused. |
| Money | Always { value, asset, scale } where value is a string of signed integer minor units. A JSON number is rejected. |
| Errors | RFC 9457 application/problem+json with a code from the governed catalog. Branch on code, never on detail. |
| Pagination | Opaque cursors. Pass next_cursor back verbatim; never construct or parse one. |
| Ids | KYTRIX-issued ids are prefixed (evt_, alrt_, epsd_, whk_, imp_). Your own ids are external_id and stay yours. |
#Ingestion
The six event families, the batch endpoint and the dry-run. Every write here is behind the single-transaction acknowledgment of invariant I6.
ingestEVENT_SCHEMAS.party; unknown members are rejected rather than dropped. The 202 is returned only after one Postgres transaction has committed both the raw event and its processing obligation (invariant I6) — there is no acknowledged-but-lost state.Defined in 05 G.1 · 05 G.3 · 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: PartyEvent — the full expansion is below.
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "person" | "business" | "merchant" | "agent" | "financial_institution" | "internal" | "government" | "ngo" | "trust" | "other" required
- status string required 1–64 chars
- created_at string (date-time) required
- legal_names string[] optional max 20 items
- birth_or_inc_date string (date) optional
- identifiers object[] optional max 50 items
- array of
- type "national_id" | "tax_id" | "passport" | "phone" | "email" | "other" required
- value string required 1–256 chars
- verification_status "verified" | "unverified" | "pending" | "failed" optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- array of
- jurisdictions object optional
- nationality string optional pattern ^[A-Z]{2}$
- residency string optional pattern ^[A-Z]{2}$
- registration string optional pattern ^[A-Z]{2}$
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- addresses object[] optional max 20 items
- array of
- line1 string optional max 256 chars
- line2 string optional max 256 chars
- city string optional max 128 chars
- region string optional max 128 chars
- postal_code string optional max 32 chars
- country string optional pattern ^[A-Z]{2}$
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- array of
- occupation string optional max 256 chars
- industry_code string optional max 32 chars
- expected_profile object optional
- declared_monthly_volume Money optional
- corridors string[] optional max 50 items
- cash_intensity "low" | "medium" | "high" optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- kyc object optional
- tier string optional 1–64 chars
- updated_at string (date-time) optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- screening_status_reported object optional
- status "clear" | "potential_match" | "confirmed_match" | "pending" | "unknown" required
- pep boolean optional
- as_of string (date-time) optional
- provider string optional max 128 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- segment "consumer" | "merchant" | "remittance_agent" | "cash_agent" | "payroll" | "marketplace" | "financial_institution" | "other" optional
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"external_id": "party-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-01T09:30:00-04:00",
"sequence": 1,
"data": {
"type": "person",
"status": "active",
"created_at": "2026-08-01T09:30:00-04:00",
"legal_names": [
"Ana Pérez"
],
"identifiers": [
{
"type": "national_id",
"value": "001-1234567-8",
"verification_status": "verified"
}
],
"jurisdictions": {
"nationality": "DO",
"residency": "DO"
},
"expected_profile": {
"declared_monthly_volume": {
"value": "5000000",
"asset": "DOP",
"scale": 2
},
"corridors": [
"US"
],
"cash_intensity": "medium"
},
"kyc": {
"tier": "tier2",
"updated_at": "2026-08-01T09:30:00-04:00"
},
"segment": "consumer"
}
}Responses
| Status | Body | When |
|---|---|---|
202 | IngestReceipt | Accepted and committed. |
400 | ProblemDetails | The payload does not match the schema — kytrix:validation/* with field paths. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed — kytrix:auth/*. |
403 | ProblemDetails | The credential lacks the ingest scope — kytrix:auth/insufficient_scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body, or a sequence conflict. |
413 | ProblemDetails | Body above the payload cap — kytrix:request/payload_too_large. |
429 | ProblemDetails | Per-tenant rate limit — kytrix:rate_limit/exceeded, with Retry-After. |
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "party",
"external_id": "party-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Entities are versioned upserts: the dedup key is (tenant, object,
external_id,sequence). Send a strictly highersequencefor each new version; the samesequencewith different content is409 kytrix:sequence/conflict, never a silent overwrite. - An exact resubmission (same identity, same canonical body) is acknowledged as a duplicate and coalesced onto the ORIGINAL
event_id, withidempotent_replay: true.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingestEVENT_SCHEMAS.account; unknown members are rejected rather than dropped. The 202 is returned only after one Postgres transaction has committed both the raw event and its processing obligation (invariant I6) — there is no acknowledged-but-lost state.Defined in 05 G.1 · 05 G.3 · 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: AccountEvent — the full expansion is below.
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "wallet" | "checking" | "savings" | "merchant" | "settlement" | "stored_value" | "loan" | "remittance" | "other" required
- holder_party_id string required 1–256 chars
- asset string required pattern ^[A-Z]{3}$|^X-[A-Z0-9]{2,10}$
- status "active" | "dormant" | "closed" | "restricted" | "frozen" | "unrestricted" | "withdrawals_restricted" | "account_restricted" | "transfers_suspended" required
- opened_at string (date-time) required
- product_code string optional 1–64 chars
- purpose string optional max 256 chars
- expected_use string optional max 512 chars
- agent_of_opening_party_id string optional 1–256 chars
- jurisdiction string optional pattern ^[A-Z]{2}$
- closed_at string (date-time) optional
- closure_reason string optional max 256 chars
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"external_id": "acct-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-01T09:30:00-04:00",
"sequence": 1,
"data": {
"type": "wallet",
"holder_party_id": "party-001",
"asset": "DOP",
"status": "active",
"opened_at": "2026-08-01T09:30:00-04:00",
"product_code": "wallet-basic"
}
}Responses
| Status | Body | When |
|---|---|---|
202 | IngestReceipt | Accepted and committed. |
400 | ProblemDetails | The payload does not match the schema — kytrix:validation/* with field paths. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed — kytrix:auth/*. |
403 | ProblemDetails | The credential lacks the ingest scope — kytrix:auth/insufficient_scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body, or a sequence conflict. |
413 | ProblemDetails | Body above the payload cap — kytrix:request/payload_too_large. |
429 | ProblemDetails | Per-tenant rate limit — kytrix:rate_limit/exceeded, with Retry-After. |
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "account",
"external_id": "acct-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Entities are versioned upserts: the dedup key is (tenant, object,
external_id,sequence). Send a strictly highersequencefor each new version; the samesequencewith different content is409 kytrix:sequence/conflict, never a silent overwrite. - An exact resubmission (same identity, same canonical body) is acknowledged as a duplicate and coalesced onto the ORIGINAL
event_id, withidempotent_replay: true.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingestEVENT_SCHEMAS.instrument; unknown members are rejected rather than dropped. The 202 is returned only after one Postgres transaction has committed both the raw event and its processing obligation (invariant I6) — there is no acknowledged-but-lost state.Defined in 05 G.1 · 05 G.3 · 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: InstrumentEvent — the full expansion is below.
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "external_bank_account" | "card_token" | "mobile_wallet" | "payment_credential" | "virtual_account" | "other" required
- status string required 1–64 chars
- first_seen_at string (date-time) required
- issuer_ref string optional 1–128 chars
- masked_identifier string optional 1–64 chars
- country string optional pattern ^[A-Z]{2}$
- holder_name_as_entered string optional max 256 chars
- verification_status "verified" | "unverified" | "pending" | "failed" optional
- fingerprint string optional 1–128 chars
- external_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"external_id": "inst-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-01T09:30:00-04:00",
"sequence": 1,
"data": {
"type": "card_token",
"status": "active",
"first_seen_at": "2026-08-01T09:30:00-04:00",
"issuer_ref": "BPD",
"masked_identifier": "411111******1111",
"country": "DO",
"verification_status": "verified",
"fingerprint": "fp_9c1e2d"
}
}Responses
| Status | Body | When |
|---|---|---|
202 | IngestReceipt | Accepted and committed. |
400 | ProblemDetails | The payload does not match the schema — kytrix:validation/* with field paths. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed — kytrix:auth/*. |
403 | ProblemDetails | The credential lacks the ingest scope — kytrix:auth/insufficient_scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body, or a sequence conflict. |
413 | ProblemDetails | Body above the payload cap — kytrix:request/payload_too_large. |
429 | ProblemDetails | Per-tenant rate limit — kytrix:rate_limit/exceeded, with Retry-After. |
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "instrument",
"external_id": "inst-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Entities are versioned upserts: the dedup key is (tenant, object,
external_id,sequence). Send a strictly highersequencefor each new version; the samesequencewith different content is409 kytrix:sequence/conflict, never a silent overwrite. - An exact resubmission (same identity, same canonical body) is acknowledged as a duplicate and coalesced onto the ORIGINAL
event_id, withidempotent_replay: true.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingestEVENT_SCHEMAS.movement; unknown members are rejected rather than dropped. The 202 is returned only after one Postgres transaction has committed both the raw event and its processing obligation (invariant I6) — there is no acknowledged-but-lost state.Defined in 05 G.1 · 05 G.3 · 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: MovementEvent — the full expansion is below.
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "p2p" | "merchant_payment" | "deposit" | "withdrawal" | "cash_in" | "cash_out" | "remittance" | "ach" | "wire" | "refund" | "reversal" | "settlement" | "internal_transfer" | "other" required
- status "completed" | "pending" | "failed" | "reversed" required
- amount Money required
- debit object required
- account_id string optional 1–256 chars
- party_id string optional 1–256 chars
- counterparty object optional
- institution string optional 1–128 chars
- country string optional pattern ^[A-Z]{2}$
- name_as_given string optional max 256 chars
- instrument_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- credit object required
- account_id string optional 1–256 chars
- party_id string optional 1–256 chars
- counterparty object optional
- institution string optional 1–128 chars
- country string optional pattern ^[A-Z]{2}$
- name_as_given string optional max 256 chars
- instrument_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- links object optional
- reverses string optional 1–256 chars
- refunds string optional 1–256 chars
- settles string optional 1–256 chars
- part_of_batch string optional 1–256 chars
- correction_of string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- cash boolean optional
- memo string optional max 1000 chars
- channel string optional 1–64 chars
- rail string optional 1–64 chars
- product string optional 1–64 chars
- agent_party_id string optional 1–256 chars
- instrument_ref string optional 1–256 chars
- device_ref string optional 1–256 chars
- ip string (ip) optional
- geo object optional
- country string optional pattern ^[A-Z]{2}$
- region string optional max 128 chars
- lat number optional ≥ -90 · ≤ 90
- lon number optional ≥ -180 · ≤ 180
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- fees Money[] optional max 20 items
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"external_id": "mov-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-30T12:00:00-04:00",
"recorded_at": "2026-08-30T12:00:00-04:00",
"data": {
"type": "p2p",
"status": "completed",
"amount": {
"value": "18000000",
"asset": "DOP",
"scale": 2
},
"debit": {
"account_id": "acct-001",
"party_id": "party-001"
},
"credit": {
"counterparty": {
"institution": "BPD",
"country": "DO",
"name_as_given": "J. Gómez"
}
},
"cash": false,
"memo": "rent august",
"channel": "app",
"rail": "internal",
"ip": "190.166.1.10",
"geo": {
"country": "DO",
"lat": 18.47,
"lon": -69.9
}
}
}Responses
| Status | Body | When |
|---|---|---|
202 | IngestReceipt | Accepted and committed. |
400 | ProblemDetails | The payload does not match the schema — kytrix:validation/* with field paths. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed — kytrix:auth/*. |
403 | ProblemDetails | The credential lacks the ingest scope — kytrix:auth/insufficient_scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body, or a sequence conflict. |
413 | ProblemDetails | Body above the payload cap — kytrix:request/payload_too_large. |
429 | ProblemDetails | Per-tenant rate limit — kytrix:rate_limit/exceeded, with Retry-After. |
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "movement",
"external_id": "mov-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Facts are immutable: the dedup key is (tenant, object,
external_id) with nosequence. Any differing resubmission of the sameexternal_idis409 kytrix:sequence/conflict— corrections are NEW events carryinglinks.correction_ofand acorrection_reason. - An exact resubmission is acknowledged as a duplicate and coalesced onto the ORIGINAL
event_id, withidempotent_replay: true.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingestEVENT_SCHEMAS.relationship; unknown members are rejected rather than dropped. The 202 is returned only after one Postgres transaction has committed both the raw event and its processing obligation (invariant I6) — there is no acknowledged-but-lost state.Defined in 05 G.1 · 05 G.3 · 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: RelationshipEvent — the full expansion is below.
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "beneficial_owner" | "authorized_signer" | "joint_holder" | "employer" | "household_member" | "parent_org" | "merchant_owner" | "agent" | "referral" | "onboarding" | "owns_instrument" | "funds_account" | "same_as" | "other" required
- from EntityRef required
- to EntityRef required
- valid_from string (date-time) required
- valid_to string (date-time) optional
- ownership_pct number optional ≥ 0 · ≤ 100
- source "kyc" | "declared" | "derived_by_customer" optional
- confidence number optional ≥ 0 · ≤ 1
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"external_id": "rel-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-01T09:30:00-04:00",
"sequence": 1,
"data": {
"type": "owns_instrument",
"from": {
"kind": "party",
"external_id": "party-001"
},
"to": {
"kind": "instrument",
"external_id": "inst-001"
},
"valid_from": "2026-08-01T09:30:00-04:00",
"source": "kyc",
"confidence": 1
}
}Responses
| Status | Body | When |
|---|---|---|
202 | IngestReceipt | Accepted and committed. |
400 | ProblemDetails | The payload does not match the schema — kytrix:validation/* with field paths. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed — kytrix:auth/*. |
403 | ProblemDetails | The credential lacks the ingest scope — kytrix:auth/insufficient_scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body, or a sequence conflict. |
413 | ProblemDetails | Body above the payload cap — kytrix:request/payload_too_large. |
429 | ProblemDetails | Per-tenant rate limit — kytrix:rate_limit/exceeded, with Retry-After. |
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "relationship",
"external_id": "rel-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Entities are versioned upserts: the dedup key is (tenant, object,
external_id,sequence). Send a strictly highersequencefor each new version; the samesequencewith different content is409 kytrix:sequence/conflict, never a silent overwrite. - An exact resubmission (same identity, same canonical body) is acknowledged as a duplicate and coalesced onto the ORIGINAL
event_id, withidempotent_replay: true.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingestEVENT_SCHEMAS.control_event; unknown members are rejected rather than dropped. The 202 is returned only after one Postgres transaction has committed both the raw event and its processing obligation (invariant I6) — there is no acknowledged-but-lost state.Defined in 05 G.1 · 05 G.3 · 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: ControlEventEvent — the full expansion is below.
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "transfer.failed" | "withdrawal.blocked" | "velocity_check.failed" | "sanctions.hit_reported" | "screening.status_changed" | "kyc.upgraded" | "kyc.downgraded" | "kyc.verification_failed" | "auth.failed" | "auth.suspicious_login" | "device.changed" | "password.reset" | "beneficiary.added" | "account.restricted_by_customer" | "transfer.retried_repeatedly" | "limit.changed" required
- subject EntityRef required
- outcome string optional 1–64 chars
- reason_code string optional 1–128 chars
- device_ref string optional 1–256 chars
- ip string (ip) optional
- actor_kind "customer_system" | "end_user" optional
- customer_severity string optional 1–32 chars
- linked_movement_id string optional 1–256 chars
- external_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"external_id": "ctl-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-30T12:00:00-04:00",
"data": {
"type": "withdrawal.blocked",
"subject": {
"kind": "account",
"external_id": "acct-001"
},
"outcome": "blocked",
"reason_code": "daily_limit",
"actor_kind": "customer_system",
"customer_severity": "medium",
"linked_movement_id": "mov-001"
}
}Responses
| Status | Body | When |
|---|---|---|
202 | IngestReceipt | Accepted and committed. |
400 | ProblemDetails | The payload does not match the schema — kytrix:validation/* with field paths. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed — kytrix:auth/*. |
403 | ProblemDetails | The credential lacks the ingest scope — kytrix:auth/insufficient_scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body, or a sequence conflict. |
413 | ProblemDetails | Body above the payload cap — kytrix:request/payload_too_large. |
429 | ProblemDetails | Per-tenant rate limit — kytrix:rate_limit/exceeded, with Retry-After. |
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "control_event",
"external_id": "ctl-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Facts are immutable: the dedup key is (tenant, object,
external_id) with nosequence. Any differing resubmission of the sameexternal_idis409 kytrix:sequence/conflict— corrections are NEW events carryinglinks.correction_ofand acorrection_reason. - An exact resubmission is acknowledged as a duplicate and coalesced onto the ORIGINAL
event_id, withidempotent_replay: true.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingestrejected result instead of failing the request, so a bad row never loses the other 999. Duplicates count as accepted and carry idempotent_replay: true. The rate-limit cost is the number of submitted events, so one 1,000-event batch and 1,000 single submits are throttled identically.Defined in 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Idempotency-Key | no | 1–255 characters, scoped to (tenant, endpoint, key) and retained ≥ 24 h. Send one per logical event so a retry replays instead of double-posting. |
Kytrix-Correlation-Id | no | Your trace id; recorded on the inbox row and every KYTRIX log line for it. |
Request body
Schema: BatchRequest — the full expansion is below.
- events one of 6[] required min 1 items · max 1000 items
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"events": [
{
"object": "party",
"event": {
"external_id": "party-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-01T09:30:00-04:00",
"sequence": 1,
"data": {
"type": "person",
"status": "active",
"created_at": "2026-08-01T09:30:00-04:00",
"legal_names": [
"Ana Pérez"
],
"identifiers": [
{
"type": "national_id",
"value": "001-1234567-8",
"verification_status": "verified"
}
],
"jurisdictions": {
"nationality": "DO",
"residency": "DO"
},
"expected_profile": {
"declared_monthly_volume": {
"value": "5000000",
"asset": "DOP",
"scale": 2
},
"corridors": [
"US"
],
"cash_intensity": "medium"
},
"kyc": {
"tier": "tier2",
"updated_at": "2026-08-01T09:30:00-04:00"
},
"segment": "consumer"
}
}
},
{
"object": "movement",
"event": {
"external_id": "mov-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-30T12:00:00-04:00",
"recorded_at": "2026-08-30T12:00:00-04:00",
"data": {
"type": "p2p",
"status": "completed",
"amount": {
"value": "18000000",
"asset": "DOP",
"scale": 2
},
"debit": {
"account_id": "acct-001",
"party_id": "party-001"
},
"credit": {
"counterparty": {
"institution": "BPD",
"country": "DO",
"name_as_given": "J. Gómez"
}
},
"cash": false,
"memo": "rent august",
"channel": "app",
"rail": "internal",
"ip": "190.166.1.10",
"geo": {
"country": "DO",
"lat": 18.47,
"lon": -69.9
}
}
}
}
]
}Responses
| Status | Body | When |
|---|---|---|
202 | BatchResponse | At least one item was accepted. |
422 | BatchResponse | Every item was rejected — the same BatchResponse body with accepted: 0. |
400 | ProblemDetails | The batch envelope itself is malformed (events missing or empty). |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the ingest scope. |
409 | ProblemDetails | Idempotency-Key reuse with a different body. |
413 | ProblemDetails | More than 1,000 events (kytrix:batch/too_large) or an oversized body. |
429 | ProblemDetails | Per-tenant rate limit. |
{
"accepted": 1,
"rejected": 1,
"results": [
{
"index": 0,
"outcome": "accepted",
"receipt": {
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "party",
"external_id": "party-001",
"received_at": "2026-09-05T14:22:11.412Z",
"idempotent_replay": false
}
},
{
"index": 1,
"outcome": "rejected",
"problem": {
"type": "kytrix:validation/schema",
"title": "Request does not match the schema",
"status": 400,
"code": "kytrix:validation/schema",
"detail": "event.data.amount: Required",
"errors": [
{
"path": "event.data.amount",
"code": "kytrix:validation/schema",
"message": "Required"
}
]
}
}
]
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Idempotent-Replayed | true when the body is the stored result of an earlier identical request. |
Notes
- Check
results[i].outcomefor every item. A 202 does NOT mean every event landed. - An
Idempotency-Keyon a batch covers the batch acceptance; per-event dedup still applies through the external ids.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
ingest or read{ events: [...] }) or a single { object, event } item and runs exactly the validation the live endpoints run. No transaction is opened on this path at all, so nothing is written: no inbox row, no obligation, no idempotency record. This is the mapping-iteration loop — run it until valid is true before you send anything real.Defined in 06 H.1 · 05 G.2
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Request body
A BatchRequest, or one { object, event } item.
{
"object": "movement",
"event": {
"external_id": "mov-001",
"schema_version": "2026-09-01",
"occurred_at": "2026-08-30T12:00:00-04:00",
"recorded_at": "2026-08-30T12:00:00-04:00",
"data": {
"type": "p2p",
"status": "completed",
"amount": {
"value": "18000000",
"asset": "DOP",
"scale": 2
},
"debit": {
"account_id": "acct-001",
"party_id": "party-001"
},
"credit": {
"counterparty": {
"institution": "BPD",
"country": "DO",
"name_as_given": "J. Gómez"
}
},
"cash": false,
"memo": "rent august",
"channel": "app",
"rail": "internal",
"ip": "190.166.1.10",
"geo": {
"country": "DO",
"lat": 18.47,
"lon": -69.9
}
}
}
}Responses
| Status | Body | When |
|---|---|---|
200 | inline schema (below) | Every item validated. |
422 | inline schema (below) | At least one item is invalid — same body, valid: false. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential has neither the ingest nor the read scope. |
413 | ProblemDetails | More than 1,000 items, or an oversized body. |
429 | ProblemDetails | Per-tenant rate limit. |
200 body
- valid boolean required true only when every item validated.
- checked integer required
- accepted integer required Items that would be accepted. Nothing is stored.
- rejected integer required
- results object[] required
- array of
- index integer required
- outcome "valid" | "rejected" required
- object "party" | "account" | "instrument" | "movement" | "relationship" | "control_event" optional
- external_id string optional
- problem ProblemDetails optional
- array of
422 body
- valid boolean required true only when every item validated.
- checked integer required
- accepted integer required Items that would be accepted. Nothing is stored.
- rejected integer required
- results object[] required
- array of
- index integer required
- outcome "valid" | "rejected" required
- object "party" | "account" | "instrument" | "movement" | "relationship" | "control_event" optional
- external_id string optional
- problem ProblemDetails optional
- array of
{
"valid": true,
"checked": 1,
"accepted": 1,
"rejected": 0,
"results": [
{
"index": 0,
"outcome": "valid",
"object": "movement",
"external_id": "mov-001"
}
]
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Notes
- A dry-run is not a reservation. It proves the shape is right; it does not hold an
external_idor promise the live submit will not hit a sequence conflict.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
#Event status
Where is event X — answerable from the moment of the 202.
ingestreceived stage ledger row, so there is never a window where a receipt exists but the event is "not found". stage_history is the processing trail; rejection is the structured reason to resubmit on.Defined in 05 G.2
Parameters
| Name | In | Type | Description |
|---|---|---|---|
event_id | path · required | string | The evt_… id from the receipt. A bare uuid and evt_<uuid> are also accepted. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | inline schema (below) | The inbox record and its stage history. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the ingest scope. |
404 | ProblemDetails | No such event in this tenant, or the id is not a KYTRIX event id. |
200 body
- event_id string required pattern ^evt_[0-9A-Za-z-]{8,64}$
- object "party" | "account" | "instrument" | "movement" | "relationship" | "control_event" required
- external_id string required
- sequence integer required nullablenull for movements and control events (immutable facts carry no sequence).
- status string required ingest.inbox_events.status, e.g. pending, rejected.
- rejection object required nullableThe structured reject reason; null unless status is rejected.
- status_reason object required nullable
- attempts integer required
- received_at string (date-time) required
- occurred_at string (date-time) required nullable
- terminal_at string (date-time) required nullable
- historical boolean required
- import_id string required nullable
- content_hash string required pattern ^sha256:[0-9a-f]{64}$
- stage_history object[] required
- array of
- stage string required
- status string required
- at string (date-time) required
- worker string required nullable
- duration_ms integer required nullable
- detail object required nullable
- array of
{
"event_id": "evt_01k4f2b9m7q0z8t3d5r6y7w8xc",
"object": "movement",
"external_id": "mov-001",
"sequence": null,
"status": "pending",
"rejection": null,
"status_reason": null,
"attempts": 0,
"received_at": "2026-09-05T14:22:11.412Z",
"occurred_at": "2026-08-30T12:00:00-04:00",
"terminal_at": null,
"historical": false,
"import_id": null,
"content_hash": "sha256:0f5c…",
"stage_history": [
{
"stage": "received",
"status": "ok",
"at": "2026-09-05T14:22:11.412Z",
"worker": null,
"duration_ms": null,
"detail": null
}
]
}Response headers
| Header | Meaning |
|---|---|
Kytrix-RateLimit-Limit | Burst size in events (the token-bucket capacity). |
Kytrix-RateLimit-Remaining | Tokens left in this tenant’s bucket. |
Kytrix-RateLimit-Reset | Seconds until the bucket is full again. |
Notes
sequenceisnullfor movements and control events: immutable facts have no version.
Registered in packages/ingest/src/routes.ts — the build fails if this path is no longer there.
#Canonical reads and posture
What KYTRIX made of what you sent, and the band-only posture read. Eventually consistent: the 202 acknowledges the inbox commit, canonicalization happens after it.
readPartyView TypeScript interface in packages/contracts/src/views.ts — a type, not a zod schema — so it is not in the generated schema section below and the SDK does not validate it either. Identifiers come back as keyed hashes, never as the raw values you sent.Defined in 06 H.1 · 03 §3.7
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your id for the party, exactly as submitted. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | — | PartyView (see packages/contracts/src/views.ts). |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown in this tenant — a governed 404 problem, never an empty 200. |
Notes
- Eventually consistent. The 202 acknowledges the inbox commit; canonicalization runs after it. Do not poll this in a loop straight after a submit — use
GET /v1/events/{event_id}to follow processing.
Registered in packages/canonical/src/routes.ts — the build fails if this path is no longer there.
readAccountView interface in packages/contracts/src/views.ts.Defined in 06 H.1
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your account id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | — | AccountView. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown in this tenant. |
Registered in packages/canonical/src/routes.ts — the build fails if this path is no longer there.
readInstrumentView interface in packages/contracts/src/views.ts.Defined in 06 H.1
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your instrument id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | — | InstrumentView. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown in this tenant. |
Registered in packages/canonical/src/routes.ts — the build fails if this path is no longer there.
readMovementView interface in packages/contracts/src/views.ts. It carries your amount plus, when the tenant base asset differs, a normalized amount with its complete FX provenance record.Defined in 06 H.1 · 03 §3.2
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your movement id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | — | MovementView. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown in this tenant. |
Registered in packages/canonical/src/routes.ts — the build fails if this path is no longer there.
readMovementView. Cursors are opaque — pass next_cursor back verbatim and never construct one.Defined in 06 H.2
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your party id. |
cursor | query | string | Opaque cursor from a previous page. |
limit | query | integer (1–200, default 50) | Page size. |
direction | query | in \| out \| both | in = the party is on the credit side (value arriving), out = the debit side (value leaving), both = either. |
from | query | RFC 3339 | Lower bound on the movement time. |
to | query | RFC 3339 | Upper bound on the movement time. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | — | { data: MovementView[], has_more: boolean, next_cursor?: string }. |
400 | ProblemDetails | Malformed cursor, limit, direction or time range. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown party in this tenant. |
Registered in packages/canonical/src/routes.ts — the build fails if this path is no longer there.
readDefined in 06 H.1 · 14 P.5 · 08 J.1
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your party id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | PostureResponse | The posture. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown party in this tenant. |
{
"subject": {
"kind": "party",
"external_id": "party-001"
},
"risk_state": "watch",
"as_of": "2026-09-05T14:20:00Z",
"freshness_seconds": 131,
"coverage_state": "complete"
}Notes
unknownandincompleteare real answers, not errors — they mean "no basis" and "gaps in the data behind this band". Treat them as safely ignorable, never as "blocked" (invariant I7).- KYTRIX advises; you enforce. Nothing in this API writes your account status.
Registered in packages/alerts/src/routes.ts — the build fails if this path is no longer there.
readDefined in 06 H.1 · 14 P.5
Parameters
| Name | In | Type | Description |
|---|---|---|---|
external_id | path · required | string | Your account id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | PostureResponse | The posture. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | Unknown account in this tenant. |
Registered in packages/alerts/src/routes.ts — the build fails if this path is no longer there.
readclassification: "restricted_aml". Keep it out of operational systems, support tooling and status pages.Defined in 06 H.1 · 10 L.5 · 14 P.5
Parameters
| Name | In | Type | Description |
|---|---|---|---|
status | query | string (≤64) | Alert status filter. |
since | query | RFC 3339 | Only alerts opened at or after this instant. |
cursor | query | string (≤4096) | Opaque cursor. |
limit | query | integer (1–200, default 50) | Page size. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | Alert | { data: Alert[], has_more: boolean, next_cursor?: string }. |
400 | ProblemDetails | Bad query parameter. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
Notes
- The served resource widens the contracts
Alertin one place:ruleset_version_idmay benullfor firings produced before ruleset provenance was stamped. Inventing one would be a fabricated fact.
Registered in packages/alerts/src/routes.ts — the build fails if this path is no longer there.
readDefined in 06 H.1
Parameters
| Name | In | Type | Description |
|---|---|---|---|
alert_id | path · required | string (alrt_…) | The alert id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | Alert | The alert. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | No such alert in this tenant. |
Registered in packages/alerts/src/routes.ts — the build fails if this path is no longer there.
readDefined in 06 H.1 · 10 L.5
Parameters
| Name | In | Type | Description |
|---|---|---|---|
state | query | EpisodeState | Episode state filter. |
since | query | RFC 3339 | Lower bound on time. |
cursor | query | string (≤4096) | Opaque cursor. |
limit | query | integer (1–200, default 50) | Page size. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | Episode | { data: Episode[], has_more: boolean, next_cursor?: string }. |
400 | ProblemDetails | Bad query parameter. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
Registered in packages/alerts/src/routes.ts — the build fails if this path is no longer there.
readDefined in 06 H.1
Parameters
| Name | In | Type | Description |
|---|---|---|---|
episode_id | path · required | string (epsd_…) | The episode id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | Episode | The episode. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the read scope. |
404 | ProblemDetails | No such episode in this tenant. |
Registered in packages/alerts/src/routes.ts — the build fails if this path is no longer there.
#Webhook endpoint management
Register, list, disable, test and rotate the endpoints KYTRIX delivers to.
webhook_mgmtchannel is a compliance control, not a preference: compliance endpoints receive restricted AML content, operational endpoints receive only sanitized events.Defined in 06 H.1 · 06 H.6 · 14 P.5
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Request body
- url string required 1–2000 charsHTTPS only. Private, link-local and cloud-metadata ranges are refused with kytrix:webhook/invalid_endpoint (06 H.6 SSRF posture, AT-23).
- channel "compliance" | "operational" required
- description string optional max 1000 chars
- event_types string[] optional max 64 itemsSubset of the catalog deliverable on this channel; omit or leave empty for all deliverable events. Subscribing a restricted_aml event on an operational endpoint is refused.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"url": "https://paydoly.example/kytrix/webhooks",
"channel": "compliance",
"event_types": [
"alert.opened",
"episode.escalated"
]
}Responses
| Status | Body | When |
|---|---|---|
201 | inline schema (below) | Registered. secret is present in this response and never again. |
400 | ProblemDetails | Not HTTPS, a private/link-local/metadata address, or a restricted event type on an operational channel — kytrix:webhook/invalid_endpoint. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
201 body
- endpoint_id string required pattern ^whk_[0-9A-Za-z-]{8,64}$
- tenant_id string (uuid) required
- url string (uri) required
- channel "compliance" | "operational" required
- event_types string[] required
- status "active" | "disabled" required
- created_at string (date-time) required
- secret string required base64url, 32 bytes. Store it in your secret manager immediately.
Notes
- This endpoint does not honour
Idempotency-Key— only the ingest endpoints implement the idempotency matrix today, so the SDK sends no automatic key here and never retries it.
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmtDefined in 06 H.1
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | WebhookEndpoint | { endpoints: WebhookEndpoint[] }. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmtDefined in 06 H.1
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path · required | string (whk_…) | The endpoint id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | WebhookEndpoint | The endpoint. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
404 | ProblemDetails | No such endpoint in this tenant. |
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmtstatus: "disabled".Defined in 06 H.6
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path · required | string (whk_…) | The endpoint id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | WebhookEndpoint | The disabled endpoint. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
404 | ProblemDetails | No such endpoint in this tenant. |
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmtprevious_secret_valid_until. During the overlap KYTRIX signs every delivery with both secrets (two v1= entries), so a receiver that verifies against [new, previous] never drops a message.Defined in 06 H.6
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path · required | string (whk_…) | The endpoint id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | inline schema (below) | Rotated. The new secret is present here and never again. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
404 | ProblemDetails | No such endpoint in this tenant. |
200 body
- endpoint_id string required pattern ^whk_[0-9A-Za-z-]{8,64}$
- tenant_id string (uuid) required
- url string (uri) required
- channel "compliance" | "operational" required
- event_types string[] required
- status "active" | "disabled" required
- created_at string (date-time) required
- secret string required base64url, 32 bytes. Store it in your secret manager immediately.
- previous_secret_valid_until string (date-time) required Until this instant BOTH secrets sign every delivery, so verify with both. The overlap is the webhooks package `secret_rotation_overlap_hours` config surface.
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmtwebhook.test envelope through the ordinary delivery path — same envelope builder, same signature, same SSRF checks, same delivery row. A test that took a shortcut would verify nothing. webhook.test is classified operational and is deliverable on both channels, so it can verify a compliance endpoint without carrying AML content.Defined in 06 H.6 · A10
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path · required | string (whk_…) | The endpoint id. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | inline schema (below) | Your endpoint answered 2xx (verified: true). |
502 | inline schema (below) | The delivery attempt did not succeed. The body is the same TestEventResult, with the status code or transport reason — the failure is reported, never hidden. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
404 | ProblemDetails | No such endpoint in this tenant. |
200 body
- endpoint_id string required
- delivery_id string required nullable
- event_id string required
- disposition "delivered" | "retry_scheduled" | "dead" | "skipped_duplicate" | "not_deliverable" required
- response_status integer required nullable
- error string required nullable
- verified boolean required true when your endpoint answered 2xx.
502 body
- endpoint_id string required
- delivery_id string required nullable
- event_id string required
- disposition "delivered" | "retry_scheduled" | "dead" | "skipped_duplicate" | "not_deliverable" required
- response_status integer required nullable
- error string required nullable
- verified boolean required true when your endpoint answered 2xx.
Notes
- KNOWN DEPLOYMENT LIMITATION: this is the only route on the API host that makes an outbound request, and in the current VPS deployment the edge container is attached to the internal network only. Until egress is granted it fails visibly with a transport error (502). Documented in
apps/edge/src/app.ts; verify signatures against the published webhook test vectors in the meantime.
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmt(created_at, delivery_id), so the page is stable under concurrent writes.Defined in 06 H.6
Parameters
| Name | In | Type | Description |
|---|---|---|---|
endpoint | query | string (whk_…) | Filter to one endpoint. |
status | query | pending \| in_flight \| delivered \| failed \| dead | Delivery status filter. |
event_type | query | WebhookEventType | Filter to one event type. |
cursor | query | string (≤512) | Opaque cursor from next_cursor. |
limit | query | integer (1–200, default 50) | Page size. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
200 | inline schema (below) | { deliveries: WebhookDelivery[], next_cursor: string | null }. |
400 | ProblemDetails | Malformed cursor or query parameter. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
200 body
- delivery_id string required
- endpoint_id string required pattern ^whk_[0-9A-Za-z-]{8,64}$
- event_id string required
- event_type string required
- classification "restricted_aml" | "advisory" | "operational" required
- attempt integer required
- status "pending" | "in_flight" | "delivered" | "failed" | "dead" required
- response_status integer required nullable
- latency_ms integer required nullable
- error string required nullable
- next_attempt_at string (date-time) required nullable
- created_at string (date-time) required
- delivered_at string (date-time) required nullable
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
webhook_mgmtid — dedupe on it, do not treat it as new.Defined in 06 H.6
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path · required | string | The delivery id to replay. |
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Responses
| Status | Body | When |
|---|---|---|
202 | — | { replay_of, delivery_id, event_id, disposition, response_status, error } (mirrored from ReplayResult in packages/webhooks/src/replay.ts). |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the webhook_mgmt scope. |
404 | ProblemDetails | Unknown delivery, an endpoint that no longer exists, or a source payload that is no longer retained. |
Registered in packages/webhooks/src/routes.ts — the build fails if this path is no longer there.
#Reconciliation
Customer-declared control totals, compared against what KYTRIX observed.
ingestDefined in 17 S.2 · 22 X.2
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Request body
- feed string required 1–200 chars
- object_type "party" | "account" | "instrument" | "movement" | "relationship" | "control_event" optional
- window_from string (date-time) required
- window_to string (date-time) required
- record_count integer optional ≥ 0
- monetary_total Money optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
{
"feed": "core-banking-movements",
"object_type": "movement",
"window_from": "2026-09-04T00:00:00-04:00",
"window_to": "2026-09-05T00:00:00-04:00",
"record_count": 184233,
"monetary_total": {
"value": "918442300",
"asset": "DOP",
"scale": 2
}
}Responses
| Status | Body | When |
|---|---|---|
202 | inline schema (below) | Recorded; reconciliation happens in the next coverage sweep. |
400 | ProblemDetails | Body does not match the schema. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the ingest scope. |
202 body
- submission_id string required
- feed string required
- window_from string (date-time) required
- window_to string (date-time) required
- status string required
- detail string required
Notes
monetary_totalisMoney— an integer string of minor units, like every other amount.
Registered in packages/coverage/src/routes.ts — the build fails if this path is no longer there.
#Historical import (backfill)
The Backfill stage, not the Connect stage. Rows are committed by the same acceptEvents transaction the live edge uses.
ingestPUT /v1/imports/{id}/chunks/{n}), dry-run, submit, and the reject report. Every imported row is committed by the same acceptEvents transaction the live edge uses — there is no second ingestion path. See the field-mapping worksheet for the mapping file.Defined in 05 G.7 · 22 X.2
Request headers
| Header | Required | Meaning |
|---|---|---|
Kytrix-Key-Id | yes | Your API key id (kx_test_…/kx_live_…). |
Kytrix-Timestamp | yes | Unix seconds, decimal. ±300 s of KYTRIX time. |
Kytrix-Nonce | yes | 1–64 chars of A-Za-z0-9._~-, single-use for 600 s per key. |
Kytrix-Signature | yes | v1=<lower-hex HMAC-SHA256> over the canonical string. |
Request body
{ mapping, mode?, dry_run?, declared_control_totals?, go_live_watermark_at? } — see packages/imports/README.md for every field and the full endpoint list.
Responses
| Status | Body | When |
|---|---|---|
201 | — | The import view (status open). |
400 | ProblemDetails | The mapping is invalid — unknown target paths are named, with the nearby real fields. |
401 | ProblemDetails | Signature missing, wrong, stale or replayed. |
403 | ProblemDetails | The credential lacks the ingest scope. |
Notes
- The import surface is documented here as a pointer, not in full: it is the Backfill stage, not the Connect gate, the SDK has no typed methods for it yet, and the conformance CLI does not certify it.
packages/imports/README.mdis the reference.
Registered in packages/imports/src/routes.ts — the build fails if this path is no longer there.
#Health
Unauthenticated liveness of the API host.
Defined in 20 V
Responses
| Status | Body | When |
|---|---|---|
200 | HealthzResponse | Healthy. |
503 | — | A dependency is unreachable. |
{
"status": "ok",
"checks": {
"db": true,
"redis": true
}
}Registered in apps/edge/src/health.ts — the build fails if this path is no longer there.
#Schemas
Generated from @kytrix/contracts. required marks a member the schema demands; everything else is optional. Constraints are the zod checks themselves — a pattern here is the exact regular expression the server applies.
#AccountEvent
Generated from packages/contracts/src/entities/account.ts
Type: object
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "wallet" | "checking" | "savings" | "merchant" | "settlement" | "stored_value" | "loan" | "remittance" | "other" required
- holder_party_id string required 1–256 chars
- asset string required pattern ^[A-Z]{3}$|^X-[A-Z0-9]{2,10}$
- status "active" | "dormant" | "closed" | "restricted" | "frozen" | "unrestricted" | "withdrawals_restricted" | "account_restricted" | "transfers_suspended" required
- opened_at string (date-time) required
- product_code string optional 1–64 chars
- purpose string optional max 256 chars
- expected_use string optional max 512 chars
- agent_of_opening_party_id string optional 1–256 chars
- jurisdiction string optional pattern ^[A-Z]{2}$
- closed_at string (date-time) optional
- closure_reason string optional max 256 chars
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#Alert
Generated from packages/contracts/src/alerts.ts
Type: object
- alert_id string required pattern ^alrt_[0-9A-Za-z-]{8,64}$
- tenant_id string (uuid) required
- subject EntityRef required
- entity_refs EntityRef[] required
- detectors object[] required min 1 items
- array of
- id string required pattern ^[A-Z]{3}-\d{3}$
- version string required pattern ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$
- array of
- signal_ids string[] required min 1 items
- episode_id string optional pattern ^epsd_[0-9A-Za-z-]{8,64}$
- family "AMT" | "VEL" | "STR" | "FAN" | "FLW" | "NID" | "BCH" | "CTL" required
- typology string required 1–128 chars
- severity "low" | "medium" | "high" | "critical" required
- confidence object required
- band "low" | "medium" | "high" required
- factors object[] required
- array of
- factor string required 1–128 chars
- weight number required ≥ 0 · ≤ 1
- note string optional max 500 chars
- array of
- evidence_bundle_hash string required pattern ^sha256:[0-9a-f]{64}$
- movement_refs string[] required
- control_event_refs string[] required
- graph_paths object[] optional
- array of
- nodes object[] required min 1 items
- array of
- kind "party" | "account" | "instrument" | "device" | "external_entity" required
- ref string required min 1 chars
- array of
- edges object[] required
- array of
- type "movement" | "ownership" | "authorized_control" | "beneficial_ownership" | "shared_instrument" | "shared_device" | "shared_identifier" | "referral" | "onboarding" | "counterparty" | "same_as" required
- from_idx integer required ≥ 0
- to_idx integer required ≥ 0
- occurred_at string (date-time) optional
- amount Money optional
- confidence number optional ≥ 0 · ≤ 1
- reliability_class "high" | "medium" | "low" optional
- supporting_refs string[] optional
- array of
- span_seconds integer optional ≥ 0
- total_amount Money optional
- nodes object[] required min 1 items
- array of
- observed_values object required
- thresholds object required
- ruleset_version_id string required pattern ^rsv_[0-9A-Za-z-]{8,64}$
- coverage_stamp object required
- state "complete" | "degraded" | "unknown" required
- detail string optional max 1000 chars
- occurred_at string (date-time) required
- evaluated_at string (date-time) required
- opened_at string (date-time) required
- recommended_action object optional
- code "review_withdrawal_capability" | "review_account" | "enhanced_due_diligence" | "monitor" required
- severity "low" | "medium" | "high" | "critical" required
- classification "restricted_aml" required
#BatchRequest
Generated from packages/contracts/src/events.ts
Type: object
- events one of 6[] required min 1 items · max 1000 items
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#BatchResponse
Generated from packages/contracts/src/events.ts
Type: object
- accepted integer required ≥ 0
- rejected integer required ≥ 0
- results one of 2[] required
#ControlEventEvent
Generated from packages/contracts/src/entities/control-event.ts
Type: object
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "transfer.failed" | "withdrawal.blocked" | "velocity_check.failed" | "sanctions.hit_reported" | "screening.status_changed" | "kyc.upgraded" | "kyc.downgraded" | "kyc.verification_failed" | "auth.failed" | "auth.suspicious_login" | "device.changed" | "password.reset" | "beneficiary.added" | "account.restricted_by_customer" | "transfer.retried_repeatedly" | "limit.changed" required
- subject EntityRef required
- outcome string optional 1–64 chars
- reason_code string optional 1–128 chars
- device_ref string optional 1–256 chars
- ip string (ip) optional
- actor_kind "customer_system" | "end_user" optional
- customer_severity string optional 1–32 chars
- linked_movement_id string optional 1–256 chars
- external_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#EntityRef
Generated from packages/contracts/src/envelope.ts
Type: object
- kind "party" | "account" | "instrument" required
- external_id string required 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#Episode
Generated from packages/contracts/src/episodes.ts
Type: object
- episode_id string required pattern ^epsd_[0-9A-Za-z-]{8,64}$
- tenant_id string (uuid) required
- key object required
- subject EntityRef required
- typology_family "AMT" | "VEL" | "STR" | "FAN" | "FLW" | "NID" | "BCH" | "CTL" required
- correlation_group_id string required min 1 chars
- state "opened" | "updated" | "materially_escalated" | "linked_to_case" | "resolved" | "reopened" | "continuing_activity" required
- severity "low" | "medium" | "high" | "critical" required
- member_signal_ids string[] required
- alert_ids string[] required
- escalation_events object[] required
- array of
- trigger "severity_increase" | "participant_threshold" | "new_signal_family" | "cash_exit" | "amount_band_jump" required
- at string (date-time) required
- delta object required
- array of
- case_id string optional pattern ^case_[0-9A-Za-z-]{8,64}$
- continuation_window one of 3 required
- One of the following, selected by
kind. - kind: "rolling"
- kind "rolling" required
- duration string required pattern ^P(?!$)(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?=\d)(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- kind: "calendar"
- kind "calendar" required
- unit "day" | "month" required
- tz string required min 1 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- kind: "business"
- kind "business" required
- calendar_id string required 1–128 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- One of the following, selected by
- opened_at string (date-time) required
- last_activity_at string (date-time) required
- resolved_at string (date-time) optional
- reopened_from object optional
- resolved_at string (date-time) required
- case_id string optional pattern ^case_[0-9A-Za-z-]{8,64}$
- classification "restricted_aml" required
#FieldError
Generated from packages/contracts/src/errors.ts
Type: object
- path string required
- code "kytrix:validation/schema" | "kytrix:validation/pan_detected" | "kytrix:validation/money_scale_mismatch" | "kytrix:validation/timestamp_out_of_range" | "kytrix:validation/unsupported_schema_version" | "kytrix:idempotency/key_conflict" | "kytrix:idempotency/in_flight" | "kytrix:auth/invalid_signature" | "kytrix:auth/timestamp_skew" | "kytrix:auth/nonce_replayed" | "kytrix:auth/unknown_key" | "kytrix:auth/key_revoked" | "kytrix:auth/key_expired" | "kytrix:auth/insufficient_scope" | "kytrix:auth/unauthenticated" | "kytrix:authz/forbidden" | "kytrix:reference/unknown_party" | "kytrix:reference/unknown_account" | "kytrix:reference/unknown_instrument" | "kytrix:reference/unknown_movement" | "kytrix:sequence/conflict" | "kytrix:sequence/stale" | "kytrix:rate_limit/exceeded" | "kytrix:batch/too_large" | "kytrix:import/invalid_chunk" | "kytrix:import/not_found" | "kytrix:governance/approval_required" | "kytrix:governance/maker_checker_violation" | "kytrix:governance/immutable" | "kytrix:governance/invalid_transition" | "kytrix:tenant/unknown" | "kytrix:tenant/suspended" | "kytrix:webhook/invalid_endpoint" | "kytrix:request/payload_too_large" | "kytrix:conflict/resource" | "kytrix:not_found/resource" | "kytrix:service/unavailable" | "kytrix:internal/error" required
- message string required
#HealthzResponse
Generated from packages/contracts/src/api.ts
Type: object
- status "ok" | "degraded" required
- checks object required
#IngestReceipt
Generated from packages/contracts/src/events.ts
Type: object
- event_id string required pattern ^evt_[0-9A-Za-z-]{8,64}$
- object "party" | "account" | "instrument" | "movement" | "relationship" | "control_event" required
- external_id string required 1–256 chars
- received_at string (date-time) required
- idempotent_replay boolean required
- import_id string optional pattern ^imp_[0-9A-Za-z-]{8,64}$
#InstrumentEvent
Generated from packages/contracts/src/entities/instrument.ts
Type: object
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "external_bank_account" | "card_token" | "mobile_wallet" | "payment_credential" | "virtual_account" | "other" required
- status string required 1–64 chars
- first_seen_at string (date-time) required
- issuer_ref string optional 1–128 chars
- masked_identifier string optional 1–64 chars
- country string optional pattern ^[A-Z]{2}$
- holder_name_as_entered string optional max 256 chars
- verification_status "verified" | "unverified" | "pending" | "failed" optional
- fingerprint string optional 1–128 chars
- external_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#Money
Generated from packages/contracts/src/money.ts
Type: object
- value string required pattern ^-?\d+$
- asset string required pattern ^[A-Z]{3}$|^X-[A-Z0-9]{2,10}$
- scale integer required ≥ 0 · ≤ 18
#MovementEvent
Generated from packages/contracts/src/entities/movement.ts
Type: object
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "p2p" | "merchant_payment" | "deposit" | "withdrawal" | "cash_in" | "cash_out" | "remittance" | "ach" | "wire" | "refund" | "reversal" | "settlement" | "internal_transfer" | "other" required
- status "completed" | "pending" | "failed" | "reversed" required
- amount Money required
- debit object required
- account_id string optional 1–256 chars
- party_id string optional 1–256 chars
- counterparty object optional
- institution string optional 1–128 chars
- country string optional pattern ^[A-Z]{2}$
- name_as_given string optional max 256 chars
- instrument_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- credit object required
- account_id string optional 1–256 chars
- party_id string optional 1–256 chars
- counterparty object optional
- institution string optional 1–128 chars
- country string optional pattern ^[A-Z]{2}$
- name_as_given string optional max 256 chars
- instrument_ref string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- links object optional
- reverses string optional 1–256 chars
- refunds string optional 1–256 chars
- settles string optional 1–256 chars
- part_of_batch string optional 1–256 chars
- correction_of string optional 1–256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- cash boolean optional
- memo string optional max 1000 chars
- channel string optional 1–64 chars
- rail string optional 1–64 chars
- product string optional 1–64 chars
- agent_party_id string optional 1–256 chars
- instrument_ref string optional 1–256 chars
- device_ref string optional 1–256 chars
- ip string (ip) optional
- geo object optional
- country string optional pattern ^[A-Z]{2}$
- region string optional max 128 chars
- lat number optional ≥ -90 · ≤ 90
- lon number optional ≥ -180 · ≤ 180
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- fees Money[] optional max 20 items
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#PartyEvent
Generated from packages/contracts/src/entities/party.ts
Type: object
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "person" | "business" | "merchant" | "agent" | "financial_institution" | "internal" | "government" | "ngo" | "trust" | "other" required
- status string required 1–64 chars
- created_at string (date-time) required
- legal_names string[] optional max 20 items
- birth_or_inc_date string (date) optional
- identifiers object[] optional max 50 items
- array of
- type "national_id" | "tax_id" | "passport" | "phone" | "email" | "other" required
- value string required 1–256 chars
- verification_status "verified" | "unverified" | "pending" | "failed" optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- array of
- jurisdictions object optional
- nationality string optional pattern ^[A-Z]{2}$
- residency string optional pattern ^[A-Z]{2}$
- registration string optional pattern ^[A-Z]{2}$
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- addresses object[] optional max 20 items
- array of
- line1 string optional max 256 chars
- line2 string optional max 256 chars
- city string optional max 128 chars
- region string optional max 128 chars
- postal_code string optional max 32 chars
- country string optional pattern ^[A-Z]{2}$
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- array of
- occupation string optional max 256 chars
- industry_code string optional max 32 chars
- expected_profile object optional
- declared_monthly_volume Money optional
- corridors string[] optional max 50 items
- cash_intensity "low" | "medium" | "high" optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- kyc object optional
- tier string optional 1–64 chars
- updated_at string (date-time) optional
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- screening_status_reported object optional
- status "clear" | "potential_match" | "confirmed_match" | "pending" | "unknown" required
- pep boolean optional
- as_of string (date-time) optional
- provider string optional max 128 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- segment "consumer" | "merchant" | "remittance_agent" | "cash_agent" | "payroll" | "marketplace" | "financial_institution" | "other" optional
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#PostureResponse
Generated from packages/contracts/src/risk.ts
Type: object
- subject EntityRef required
- risk_state "clear" | "watch" | "elevated" | "critical" | "unknown" | "incomplete" required
- as_of string (date-time) required
- freshness_seconds integer required ≥ 0
- coverage_state "complete" | "degraded" | "unknown" required
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#ProblemDetails
Generated from packages/contracts/src/errors.ts
Type: object
- type string required
- title string required
- status integer required ≥ 100 · ≤ 599
- detail string optional
- instance string optional
- code "kytrix:validation/schema" | "kytrix:validation/pan_detected" | "kytrix:validation/money_scale_mismatch" | "kytrix:validation/timestamp_out_of_range" | "kytrix:validation/unsupported_schema_version" | "kytrix:idempotency/key_conflict" | "kytrix:idempotency/in_flight" | "kytrix:auth/invalid_signature" | "kytrix:auth/timestamp_skew" | "kytrix:auth/nonce_replayed" | "kytrix:auth/unknown_key" | "kytrix:auth/key_revoked" | "kytrix:auth/key_expired" | "kytrix:auth/insufficient_scope" | "kytrix:auth/unauthenticated" | "kytrix:authz/forbidden" | "kytrix:reference/unknown_party" | "kytrix:reference/unknown_account" | "kytrix:reference/unknown_instrument" | "kytrix:reference/unknown_movement" | "kytrix:sequence/conflict" | "kytrix:sequence/stale" | "kytrix:rate_limit/exceeded" | "kytrix:batch/too_large" | "kytrix:import/invalid_chunk" | "kytrix:import/not_found" | "kytrix:governance/approval_required" | "kytrix:governance/maker_checker_violation" | "kytrix:governance/immutable" | "kytrix:governance/invalid_transition" | "kytrix:tenant/unknown" | "kytrix:tenant/suspended" | "kytrix:webhook/invalid_endpoint" | "kytrix:request/payload_too_large" | "kytrix:conflict/resource" | "kytrix:not_found/resource" | "kytrix:service/unavailable" | "kytrix:internal/error" required
- errors FieldError[] optional
- correlation_id string optional
#RelationshipEvent
Generated from packages/contracts/src/entities/relationship.ts
Type: object
- external_id string required 1–256 chars
- schema_version "2026-09-01" required
- occurred_at string (date-time) required
- recorded_at string (date-time) optional
- sequence integer optional ≥ 0
- data object required
- type "beneficial_owner" | "authorized_signer" | "joint_holder" | "employer" | "household_member" | "parent_org" | "merchant_owner" | "agent" | "referral" | "onboarding" | "owns_instrument" | "funds_account" | "same_as" | "other" required
- from EntityRef required
- to EntityRef required
- valid_from string (date-time) required
- valid_to string (date-time) optional
- ownership_pct number optional ≥ 0 · ≤ 100
- source "kyc" | "declared" | "derived_by_customer" optional
- confidence number optional ≥ 0 · ≤ 1
- external_ref string optional 1–256 chars
- correction_reason string optional max 256 chars
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
- No other members are accepted — an unrecognised key is a reject, not a dropped field.
#WebhookEndpoint
Generated from packages/contracts/src/webhooks.ts
Type: object
- endpoint_id string required pattern ^whk_[0-9A-Za-z-]{8,64}$
- tenant_id string (uuid) required
- url string (uri) required pattern ^https://
- channel "compliance" | "operational" required
- event_types "alert.opened" | "episode.updated" | "episode.escalated" | "episode.resolved" | "episode.reopened" | "case.status_changed" | "case.disposed" | "risk_state.changed" | "recommendation.issued" | "coverage.degraded" | "coverage.recovered" | "import.progress" | "import.completed" | "import.failed" | "ruleset.candidate_ready" | "ruleset.activated" | "ruleset.rolled_back" | "conformance.drift_detected" | "webhook.test"[] required
- status "active" | "disabled" required
- created_at string (date-time) required
#WebhookEnvelope
Generated from packages/contracts/src/webhooks.ts
Type: object
- id string required pattern ^whe_[0-9A-Za-z-]{8,64}$
- type "alert.opened" | "episode.updated" | "episode.escalated" | "episode.resolved" | "episode.reopened" | "case.status_changed" | "case.disposed" | "risk_state.changed" | "recommendation.issued" | "coverage.degraded" | "coverage.recovered" | "import.progress" | "import.completed" | "import.failed" | "ruleset.candidate_ready" | "ruleset.activated" | "ruleset.rolled_back" | "conformance.drift_detected" | "webhook.test" required
- classification "restricted_aml" | "advisory" | "operational" required
- created_at string (date-time) required
- tenant_id string (uuid) required
- data object required
- delivery object optional
- attempt integer required ≥ 1
- delivery_id string required pattern ^whd_[0-9A-Za-z-]{8,64}$
#OpenAPI document
The machine-readable form of everything above lives at /openapi.json. It is regenerated by the same build, from the same schemas.
# generate a typed client
npx openapi-typescript https://docs.<your-domain>/openapi.json -o kytrix-api.d.ts
# or import it into Postman / Insomnia / Bruno directly