{"openapi":"3.1.0","info":{"title":"Skoor Risk API","version":"2026-09-17","summary":"Money movement. Skoored by AI.","description":"A risk platform for money movement: every dollar that moves across a bank's rails, scored, decided, and on the record. This is the same API the dashboard and the Claude connector use.\n\nEvery score is null until measured and always carries its n. Money is integer cents serialised as strings. Ids are the bank's own where the object is the bank's (transfers, entities, counterparties) and UUIDs where the platform minted them.\n\nAuthentication: `Authorization: Bearer <operator token or API key>` proves the tenant; `X-Operator: <who is acting>` names the person and is written on every decision. Optional `?tenant=<slug>` makes the tenant explicit. Planned with the gateway fork: `Skoor-Version: 2026-09-17` request/response header, `X-Request-Id` on every response, `Idempotency-Key` on POST/PUT/PATCH/DELETE under /api, and the error envelope `{ error, requestId }`.\n\nOperations marked `x-status: planned` are documented from docs/MILESTONE-C.md and land with their fork (keys, webhooks, sandbox). Admin routes (/api/admin/*) and simulated-ingest internals are not part of the public API.\n\nGetting started: docs/API.md in the repository, or /docs on this server.","contact":{"name":"Skoor","url":"https://skoor.ai"}},"servers":[{"url":"https://skoor.ai/risk","description":"Production, behind skoor.ai"},{"url":"https://skoor-risk-production.up.railway.app/risk","description":"Production, direct"}],"tags":[{"name":"Program","description":"A program is a fintech platform on the bank's rails: payroll, remittance, marketplace, lending. Column identifies it by the webhook endpoint that delivered the event. Its declared activity (volume, rails, countries) is what the Skoor reads as \"expected\". Programs are created by the platform admin; a tenant reads them through reports, and rescoring runs per program.\n\n### Program\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `tenantId` | string (uuid) | The bank this program banks on. |\n| `slug` | string | Stable, URL-safe name; unique per tenant. |\n| `name` | string |  |\n| `webhookEndpointId` | string \\| null | Column `Webhook-Endpoint-Id` header that identifies the platform delivering events. |\n| `declaredMonthlyVolumeCents` | string \\| null | What the program declared at onboarding; the Skoor reads it as expected activity. Integer cents as a string, or null. |\n| `declaredRails` | string[] | Rails declared at onboarding. |\n| `declaredCountries` | string[] | Countries declared at onboarding (default [\"US\"]). |\n| `programReserveAccountId` | string \\| null | Reserve account whose balance backs the reserve-coverage KRI. |\n| `source` | \"column\" \\| \"simulated\" | Where the program's events come from. Simulated programs are labelled everywhere. |\n| `createdAt` | string (date-time) |  |","x-summary":"A business that banks on the tenant.","x-objects":["Program"]},{"name":"Transfer","description":"Every transfer the bank's webhooks describe, on any rail, keyed by Column's id. The Transaction Risk Skoor (trs-v1) is a number from 0 to 100 stored on the row with its band, version, n and confidence; null means too little history, never zero risk. Bands: clear 0–29, review 30–69, hold 70–100. Transfers are written by ingest; the API rescores them and reads them inside reports and pre-flights.\n\n### Transfer\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string | Column transfer id, any rail. |\n| `tenantId` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `rail` | \"ach\" \\| \"wire\" \\| \"realtime\" \\| \"book\" \\| \"intl_wire\" \\| \"check\" \\| \"card\" |  |\n| `direction` | \"incoming\" \\| \"outgoing\" |  |\n| `type` | \"CREDIT\" \\| \"DEBIT\" \\| null | ACH type, null on other rails. |\n| `amountCents` | string | Integer cents as a string (bigint-safe). |\n| `currency` | string | ISO 4217, default USD. |\n| `status` | string | Column's status enum, upper-cased. |\n| `entityId` | string \\| null |  |\n| `accountId` | string \\| null |  |\n| `counterpartyId` | string \\| null |  |\n| `counterpartyCountry` | string \\| null | ISO 3166-1 alpha-2. |\n| `secCode` | string \\| null | ACH SEC code. |\n| `returnCode` | string \\| null | ACH return code when returned (R01…). |\n| `returnedAt` | string \\| null (date-time) |  |\n| `effectiveAt` | string \\| null (date-time) |  |\n| `columnCreatedAt` | string (date-time) | When the bank created it. |\n| `skoor` | integer \\| null | Transaction Risk Skoor 0–100; null until scored or when history is too thin. |\n| `skoorBand` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" \\| null |  |\n| `skoorVersion` | string \\| null | Scorer version, e.g. trs-v1. |\n| `skoorN` | integer \\| null | Observations behind the baselines. |\n| `skoorConfidence` | number \\| null | 0–1; null when the Skoor is null. |\n| `skoorInputs` | object \\| null |  |\n| `skooredAt` | string \\| null (date-time) |  |\n| `raw` | object | The bank's own object as last delivered. |\n| `updatedAt` | string (date-time) |  |\n\n### Reason\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string | Stable reason id, e.g. TRS-ENT-01. |\n| `signal` | string | The signal code the scorer emitted. |\n| `weight` | number | Points added. |\n| `hard` | boolean |  |\n| `sentence` | string | What a compliance officer reads. |\n| `detail` (optional) | string |  |\n\n### SkoorSignal\n\n| Field | Type | Notes |\n|---|---|---|\n| `code` | string |  |\n| `weight` | number |  |\n| `hard` (optional) | boolean |  |\n| `detail` (optional) | string |  |","x-summary":"A dollar moving on a rail, with its Transaction Risk Skoor.","x-objects":["Transfer","Reason","SkoorSignal"]},{"name":"Entity","description":"The customer behind a transfer: verification state, screening state and PEP status are read from the bank verbatim. The Entity Risk Skoor (ers-v1) is the latest actor_skoors row for the entity; it appears in reports, on pre-flight answers through the limit ladder, and in signed proofs.\n\n### Entity\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string | Column entity id. |\n| `tenantId` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `type` | \"PERSON\" \\| \"BUSINESS\" |  |\n| `name` | string |  |\n| `verificationStatus` | \"UNVERIFIED\" \\| \"PENDING\" \\| \"MANUAL_REVIEW\" \\| \"VERIFIED\" \\| \"DENIED\" |  |\n| `isHighRisk` | boolean |  |\n| `pepStatus` | \"not_checked\" \\| \"no\" \\| \"yes\" \\| \"potential\" |  |\n| `requiresScreening` | boolean |  |\n| `reviewReasons` | string[] |  |\n| `lastScreenedAt` | string \\| null (date-time) |  |\n| `countryCode` | string \\| null |  |\n| `legalType` | string \\| null |  |\n| `expectedPaymentRails` | string[] |  |\n| `raw` | object | The bank's own object as last delivered. |\n| `createdAt` | string (date-time) |  |\n| `updatedAt` | string (date-time) |  |","x-summary":"A person or business on a program, with its Entity Risk Skoor.","x-objects":["Entity"]},{"name":"Counterparty","description":"Routing number, last four and country as the bank delivered them; first seen when the program first paid or was paid by it. The Counterparty Risk Skoor (crs-v1) rolls up returns and first-time signals. There is no dedicated counterparty endpoint: counterparties are read through actor reports, the limit ladder, pre-flight answers and proofs.\n\n### Counterparty\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string | Column counterparty id. |\n| `tenantId` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `name` | string \\| null |  |\n| `routingNumber` | string \\| null |  |\n| `accountLast4` | string \\| null |  |\n| `countryCode` | string \\| null |  |\n| `raw` | object | The bank's own object as last delivered. |\n| `firstSeenAt` | string (date-time) | First transfer with this counterparty on the program. |","x-summary":"The other side of a transfer, with its Counterparty Risk Skoor.","x-objects":["Counterparty"]},{"name":"Agent","description":"On-chain agents are imported as entities on the program `agents` and scored with the Agent Risk Skoor (ars-v1) on the same bands. Import pulls agents, their compliance profiles and their settlements from the scan index; an hourly refresh runs for tenants that have the program.\n\n### Agent\n\n| Field | Type | Notes |\n|---|---|---|\n| `entityId` | string | The entity row the agent is stored as (program `agents`). |\n| `name` | string |  |\n| `chainId` | integer \\| null |  |\n| `agentId` | string \\| null | On-chain agent id from the scan index. |\n| `address` | string \\| null | Wallet address. |\n| `verificationStatus` | string |  |\n| `screeningStatus` | string \\| null | Latest compliance-profile screening status, or null when never screened. |\n| `lastScreenedAt` | string \\| null (date-time) |  |\n| `settlements` | integer | Transfers imported for this agent. |\n| `volumeCents` | string | Sum of imported transfers. Integer cents as a string (bigint-safe). |\n| `skoor` | integer \\| null | Agent Risk Skoor (ars-v1); null until history is enough. |\n| `bandName` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" |  |\n| `n` | integer |  |\n| `confidence` | number \\| null |  |\n| `version` | string \\| null |  |\n\n### ImportResult\n\n| Field | Type | Notes |\n|---|---|---|\n| `programId` | string (uuid) |  |\n| `agents` | integer |  |\n| `profiles` | integer |  |\n| `settlementsScanned` | integer |  |\n| `transfers` | integer |  |\n| `counterparties` | integer |  |\n| `agentCounterparties` | integer |  |","x-summary":"An AI agent moving money, imported from the public scan index.","x-objects":["Agent","ImportResult"]},{"name":"Alert","description":"Detectors open alerts from scored transfers and from KRI snapshots; the policy routes each one automated or reviewed when it opens and stamps a due time by SLA tier. The alert carries its evidence, the Skoor and band at the time, and whether a hard signal was present. Alerts are decided on the dashboard and inside Claude (connector tool `decide_alert`); the API lists the ones past their clock and exports them on the record.\n\n### Alert\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `tenantId` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `detector` | string | Which detector opened it, e.g. structuring_pattern, or rule:<id>. |\n| `severity` | \"low\" \\| \"medium\" \\| \"high\" |  |\n| `subjectType` | \"transfer\" \\| \"entity\" \\| \"program\" \\| \"counterparty\" \\| \"account\" |  |\n| `subjectId` | string |  |\n| `transferId` | string \\| null |  |\n| `summary` | string |  |\n| `evidence` | object | What the detector saw; carries routeReason, autoHold and escalation when they apply. |\n| `skoorAtAlert` | integer \\| null |  |\n| `bandAtAlert` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" \\| null |  |\n| `hardSignal` | boolean | A hard signal always routes to a person. |\n| `status` | \"open\" \\| \"auto_closed\" \\| \"closed\" \\| \"held\" \\| \"escalated\" |  |\n| `route` | \"automated\" \\| \"reviewed\" \\| null | Decided by the policy when opened. |\n| `policyVersion` | string \\| null |  |\n| `openedAt` | string (date-time) |  |\n| `closedAt` | string \\| null (date-time) |  |\n| `dedupeKey` | string \\| null |  |\n| `dueAt` | string \\| null (date-time) | Decision clock: when a person must have decided by. |\n| `escalatedAt` | string \\| null (date-time) | Stamped once when the clock ran out. |\n| `slaTier` | \"auto\" \\| \"standard\" \\| \"complex\" \\| \"appeal\" \\| null |  |\n\n### OverdueAlert\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `detector` | string |  |\n| `severity` | string |  |\n| `status` | string |  |\n| `route` | string \\| null |  |\n| `slaTier` | string \\| null |  |\n| `openedAt` | string (date-time) |  |\n| `dueAt` | string \\| null (date-time) |  |\n| `escalatedAt` | string \\| null (date-time) |  |\n| `summary` | string |  |","x-summary":"What a detector opened, routed automated or reviewed, on a decision clock.","x-objects":["Alert","OverdueAlert"]},{"name":"Disposition","description":"Every decision on an alert — by the policy alone or by a person — is a disposition with the narrative that was signed, the draft's recommendation and confidence, whether the person overturned the draft, and a hash chained to the previous disposition on the tenant. Dispositions are written on the dashboard and through the connector; the API verifies and exports the chain.\n\n### Disposition\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `tenantId` | string (uuid) |  |\n| `alertId` | string (uuid) |  |\n| `decidedBy` | string | `policy:<version>` when the platform decided alone; `operator:<name>` when a person signed. |\n| `decision` | \"close\" \\| \"hold\" \\| \"escalate\" \\| \"release\" \\| \"no_action\" |  |\n| `narrative` | string | The examiner-shaped narrative that was signed. |\n| `narrativeModel` | string \\| null | Model alias that drafted it; null when template or human-written. |\n| `recommendation` | string \\| null | What the draft recommended. |\n| `confidence` | number \\| null | Draft confidence; never present on a template. |\n| `policyVersion` | string |  |\n| `overturnsDraft` | boolean \\| null | True when the person disagreed with the draft. |\n| `prevHash` | string \\| null | Hash of the previous disposition on the tenant chain. |\n| `hash` | string | sha256(prevHash \\| canonical(body)). |\n| `createdAt` | string (date-time) |  |","x-summary":"A signed decision on an alert, hash-chained per tenant.","x-objects":["Disposition"]},{"name":"Action","description":"Clear or cancel a held transfer, pause a card, suspend a card account, freeze an account, send a request to a program, assemble a periodic review. freeze_account, request_to_program and suspend_card_account need a second, distinct approver; the requester approving is recorded as refused. Execution uses the tenant's own Column key (test_ only in this build); a tenant without a key gets a recorded dry run that says so.\n\n### Action\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `tenantId` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `alertId` | string \\| null (uuid) |  |\n| `type` | \"clear_hold\" \\| \"cancel_transfer\" \\| \"pause_card\" \\| \"suspend_card_account\" \\| \"freeze_account\" \\| \"request_to_program\" \\| \"periodic_review\" |  |\n| `targetType` | string | What the action acts on (transfer, account, card, program…). |\n| `targetId` | string |  |\n| `requestedBy` | string | The X-Operator who requested it. |\n| `approvedBy` | string \\| null | Second, distinct approver for freeze_account, request_to_program, suspend_card_account. |\n| `status` | \"requested\" \\| \"approved\" \\| \"executed\" \\| \"failed\" \\| \"refused\" |  |\n| `columnResponse` | object \\| null |  |\n| `error` | string \\| null |  |\n| `executedAt` | string \\| null (date-time) |  |\n| `prevHash` | string \\| null |  |\n| `hash` | string | Chained at insert. |\n| `createdAt` | string (date-time) |  |","x-summary":"A request through the bank's own API, attributed and chained.","x-objects":["Action"]},{"name":"Pre-flight","description":"The same features, scorer and bands as a landed transfer, answered inside the bank's decision window and recorded so it can be graded against the outcome once the transfer lands and is matched back (same program, counterparty and amount within 24 hours). `degraded` is true when a source the answer leans on is missing or stale, so a thin answer is never mistaken for a confident one.\n\n### Preflight\n\n| Field | Type | Notes |\n|---|---|---|\n| `preflightId` | string (uuid) |  |\n| `decision` | \"clear\" \\| \"review\" \\| \"hold\" | hold = hold band, hard signal or hard limit hit; review = review band, unscored or any soft limit hit; clear otherwise. |\n| `skoor` | integer \\| null |  |\n| `band` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" |  |\n| `version` | string |  |\n| `n` | integer |  |\n| `confidence` | number \\| null |  |\n| `reasons` | Reason[] |  |\n| `limitsHit` | LimitHit[] |  |\n| `degraded` | boolean | True when a feature source the answer leans on is missing or stale. |\n| `degradedReasons` | string[] | Why it is degraded (empty on a replayed answer). |\n| `latencyMs` | integer |  |\n| `idempotent` | boolean | True when the answer was replayed from the idempotency key. |\n| `programId` | string (uuid) |  |\n\n### PreflightRequest\n\n| Field | Type | Notes |\n|---|---|---|\n| `programId` (optional) | string (uuid) | One of programId or programSlug is required. |\n| `programSlug` (optional) | string |  |\n| `idempotencyKey` (optional) | string | Same key on the same tenant returns the same answer (200 instead of 201). |\n| `transfer` | object |  |\n| `transfer.rail` | \"ach\" \\| \"wire\" \\| \"realtime\" \\| \"book\" \\| \"intl_wire\" \\| \"check\" \\| \"card\" |  |\n| `transfer.direction` | \"outgoing\" \\| \"incoming\" |  |\n| `transfer.type` (optional) | \"CREDIT\" \\| \"DEBIT\" \\| null |  |\n| `transfer.amountCents` | string | Integer cents as a string. |\n| `transfer.currency` (optional) | string | Default USD. |\n| `transfer.entityId` (optional) | string |  |\n| `transfer.counterpartyId` (optional) | string |  |\n| `transfer.counterparty` (optional) | object | Describe a counterparty not on file; matched by routing + last4, else by name, else scored as a first-time stranger. |\n| `transfer.counterparty.name` | string |  |\n| `transfer.counterparty.routingNumber` (optional) | string |  |\n| `transfer.counterparty.accountLast4` (optional) | string |  |\n| `transfer.counterparty.countryCode` (optional) | string |  |\n| `transfer.effectiveOn` (optional) | string |  |\n| `transfer.description` (optional) | string |  |\n\n### PreflightDetail\n\n| Field | Type | Notes |\n|---|---|---|\n| `preflightId` | string (uuid) |  |\n| `decision` | \"clear\" \\| \"review\" \\| \"hold\" |  |\n| `skoor` | integer \\| null |  |\n| `band` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" |  |\n| `version` | string |  |\n| `n` | integer |  |\n| `confidence` | number \\| null |  |\n| `reasons` | Reason[] |  |\n| `limitsHit` | LimitHit[] |  |\n| `degraded` | boolean |  |\n| `degradedReasons` | string[] |  |\n| `latencyMs` | integer |  |\n| `idempotent` | boolean |  |\n| `programId` | string (uuid) |  |\n| `request` | object | The request as submitted plus `operator` and `resolved` {entityId, counterpartyId, counterpartyCountry}. |\n| `matchedTransferId` | string \\| null | Set when the transfer later landed and was matched back (same program, counterparty, amount, within 24h). |\n| `createdAt` | string (date-time) |  |\n\n### PreflightStats\n\n| Field | Type | Notes |\n|---|---|---|\n| `windowDays` | integer |  |\n| `count` | integer |  |\n| `byDecision` | object |  |\n| `byDecision.clear` | integer |  |\n| `byDecision.review` | integer |  |\n| `byDecision.hold` | integer |  |\n| `latencyMs` | object |  |\n| `latencyMs.p50` | number \\| null |  |\n| `latencyMs.p90` | number \\| null |  |\n| `latencyMs.n` | integer |  |\n| `matched` | object | Share of pre-flights matched to a landed transfer. |\n| `matched.value` | number \\| null | null when nothing was measured |\n| `matched.n` | integer | observations behind the value |\n| `degraded` | object | Share of degraded answers. |\n| `degraded.value` | number \\| null | null when nothing was measured |\n| `degraded.n` | integer | observations behind the value |\n| `graded` | object | Matched pre-flights whose transfer later received a bad/ok label. |\n| `graded.n` | integer |  |\n| `graded.holdAndBad` | integer |  |\n| `graded.holdAndOk` | integer |  |\n| `graded.notHoldAndBad` | integer |  |\n| `graded.notHoldAndOk` | integer |  |\n| `graded.holdPrecision` | number \\| null |  |\n| `graded.holdRecall` | number \\| null |  |\n\n### LimitHit\n\n| Field | Type | Notes |\n|---|---|---|\n| `limit` | string | `<actor>.<kind>`: entity\\|counterparty × per_transaction\\|daily\\|monthly. |\n| `actor` | \"counterparty\" \\| \"entity\" |  |\n| `actorId` | string |  |\n| `band` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" |  |\n| `cap` | string | The cap at that band. Integer cents as a string (bigint-safe). |\n| `observed` | string | Already moved in the window (0 for per_transaction). Integer cents as a string (bigint-safe). |\n| `proposed` | string | This amount. Integer cents as a string (bigint-safe). |\n| `severity` | \"hard\" \\| \"soft\" |  |","x-summary":"Score a proposed transfer before origination.","x-objects":["Preflight","PreflightRequest","PreflightDetail","PreflightStats","LimitHit"]},{"name":"KRI snapshot","description":"Per program: ACH return rates against NACHA thresholds, reserve coverage, review and hold aging, overdraft and freeze events, velocity, counterparty concentration, verification denial rate, card decline mix. Each KRI carries value | null, n, threshold and a status; the Program Risk Skoor (prs-v1) is null until at least three KRIs have n above zero. Snapshots run every 15 minutes, after every 200 transfers, and on demand.\n\n### KriSnapshot\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `tenantId` | string (uuid) |  |\n| `programId` | string (uuid) |  |\n| `asOf` | string (date-time) |  |\n| `windowDays` | integer |  |\n| `kris` | Record<string, KriValue> | One KriValue per KRI name (ach_overall_return_rate, reserve_coverage, …). |\n| `programSkoor` | integer \\| null | Program Risk Skoor (prs-v1); null until at least three KRIs have n > 0. |\n| `programSkoorN` | integer \\| null |  |\n| `programSkoorVersion` | string \\| null |  |\n| `createdAt` | string (date-time) |  |\n\n### KriValue\n\n| Field | Type | Notes |\n|---|---|---|\n| `value` | number \\| null | null = not measured. |\n| `n` | integer |  |\n| `unit` | \"ratio\" \\| \"count\" \\| \"cents\" \\| \"days\" \\| \"hours\" |  |\n| `threshold` (optional) | number \\| null |  |\n| `status` | \"ok\" \\| \"watch\" \\| \"breach\" \\| \"unmeasured\" |  |\n| `note` (optional) | string |  |","x-summary":"Program KRIs over a rolling window, rolled into the Program Risk Skoor.","x-objects":["KriSnapshot","KriValue"]},{"name":"Policy","description":"Policy is versioned data, never code: which detectors the platform may close alone and in which band, the history floor, which actions need two approvers, how long each SLA tier may wait, the per-band caps for entities and counterparties, and a per-program autonomy tier that widens only after the labelled set shows a detector is safe (and narrows on its own when overturns rise). Every alert and disposition records the policy version in force.\n\n### Policy\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `tenantId` | string (uuid) |  |\n| `programId` | string \\| null (uuid) | null = tenant-wide; set = a program's autonomy tier / limits. |\n| `version` | string |  |\n| `body` | object | PolicyBody, ClockPolicy, LimitLadder or a program autonomy body, by version prefix. |\n| `active` | boolean |  |\n| `createdAt` | string (date-time) |  |\n\n### PolicyBody\n\n| Field | Type | Notes |\n|---|---|---|\n| `version` | string |  |\n| `autoClosableDetectors` | string[] | Detectors the platform may close alone. |\n| `autoCloseBands` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\"[] | v1: clear only. |\n| `minProgramN` | integer | History floor for any automated decision. |\n| `alwaysReviewed` | string[] |  |\n| `twoApproverActions` | \"clear_hold\" \\| \"cancel_transfer\" \\| \"pause_card\" \\| \"suspend_card_account\" \\| \"freeze_account\" \\| \"request_to_program\" \\| \"periodic_review\"[] |  |\n| `holdBandAutoHold` | boolean | Hold band → held before anyone decides. |\n\n### ClockPolicy\n\n| Field | Type | Notes |\n|---|---|---|\n| `autoHours` | number |  |\n| `standardHours` | number |  |\n| `complexHours` | number |  |\n| `appealHours` | number |  |\n\n### LimitLadder\n\n| Field | Type | Notes |\n|---|---|---|\n| `counterparty` | Ladder |  |\n| `entity` | Ladder |  |\n| `rescreenDays` | object | Re-screen cadence by band. |\n| `rescreenDays.clear` | integer |  |\n| `rescreenDays.review` | integer |  |\n| `rescreenDays.hold` | integer |  |\n| `rescreenDays.unscored` | integer |  |\n\n### LimitEvaluation\n\n| Field | Type | Notes |\n|---|---|---|\n| `hits` | LimitHit[] |  |\n| `hard` | boolean | True when any hit is hard (an actor in the hold band). |\n| `bands` | object |  |\n| `bands.entity` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" \\| null |  |\n| `bands.counterparty` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" \\| null |  |\n| `rescreen` | object |  |\n| `rescreen.entity` | object \\| null |  |\n| `rescreen.counterparty` | object \\| null |  |\n| `ladder` | LimitLadder |  |\n\n### ProgramAutonomy\n\n| Field | Type | Notes |\n|---|---|---|\n| `programId` | string (uuid) |  |\n| `tier` | \"supervised\" \\| \"standard\" \\| \"trusted\" |  |\n| `version` | string \\| null | Active program version; null = policy v1 only. |\n| `granted` | string[] |  |\n| `effective` | PolicyBody |  |\n| `eligibility` | DetectorEligibility[] |  |\n| `lastChangeAt` | string \\| null (date-time) |  |\n| `nextChangeAllowedAt` | string \\| null (date-time) | Cadence: one widening per week. |\n| `history` | object[] |  |\n| `history[].version` | string |  |\n| `history[].createdAt` | string (date-time) |  |\n| `history[].body` | ProgramPolicyBody |  |\n| `history[].active` | boolean |  |","x-summary":"The automated-or-reviewed line, decision clocks, the limit ladder, program autonomy.","x-objects":["Policy","PolicyBody","ClockPolicy","LimitLadder","LimitEvaluation","ProgramAutonomy"]},{"name":"Rule","description":"A bank authors rules as data over the same features the scorer uses. A rule never changes a Skoor: it opens an alert (detector `rule:<id>`) that always waits for a person. Activation needs a backtest against the tenant's own last 90 days that is younger than 24 hours, a second approver, and writes a versioned rules policy on the record chain.\n\n### Rule\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string | Stable id, lowercase. |\n| `name` | string |  |\n| `scope` | \"transfer\" \\| \"entity\" \\| \"counterparty\" \\| \"program\" | Default transfer; only transfer-scoped rules fire today. |\n| `programId` | string \\| null (uuid) | null = every program on the tenant. |\n| `when` | RuleCondition[] | AND of conditions; at least one. |\n| `action` | \"alert\" \\| \"hold\" \\| \"review\" \\| \"log\" |  |\n| `severity` | \"low\" \\| \"medium\" \\| \"high\" |  |\n| `status` | \"draft\" \\| \"testing\" \\| \"active\" \\| \"paused\" \\| \"archived\" |  |\n| `note` (optional) | string |  |\n\n### RuleCondition\n\n| Field | Type | Notes |\n|---|---|---|\n| `field` | \"amountCents\" \\| \"rail\" \\| \"direction\" \\| \"type\" \\| \"status\" \\| \"counterpartyCountry\" \\| \"cardDecisionReason\" \\| \"entity.verificationStatus\" \\| \"entity.pepStatus\" \\| \"entity.isHighRisk\" \\| \"entity.requiresScreening\" \\| \"entity.screeningAgeDays\" \\| \"entity.count30d\" \\| \"velocity24hCount\" \\| \"velocity24hSumCents\" \\| \"entity.priorTransfers\" \\| \"entity.priorReturns\" \\| \"entity.achDebits60d\" \\| \"entity.unauthorizedReturns60d\" \\| \"structuringWindowCount\" \\| \"counterparty.firstTime\" \\| \"counterparty.n\" \\| \"counterparty.priorReturns\" \\| \"counterparty.priorUnauthorizedReturns\" \\| \"program.n90d\" \\| \"program.medianCents\" \\| \"program.p95Cents\" \\| \"program.dailyAvgCents30d\" \\| \"program.declaredMonthlyVolumeCents\" \\| \"program.unauthorizedReturnRate60d\" \\| \"declared.countries\" \\| \"typology.roundTrip\" \\| \"typology.layeringHops\" \\| \"typology.dormantDays\" \\| \"typology.justBelowThreshold\" | A fixed vocabulary over the same features the scorer uses. |\n| `op` | \"gt\" \\| \"gte\" \\| \"lt\" \\| \"lte\" \\| \"eq\" \\| \"neq\" \\| \"in\" \\| \"not_in\" \\| \"between\" \\| \"is_null\" \\| \"not_null\" |  |\n| `value` (optional) | string \\| number \\| boolean \\| null \\| string \\| number \\| boolean \\| null[] | Scalar (string, number, boolean, null) or an array of scalars for in / not_in / between; omitted for is_null / not_null. |\n\n### Backtest\n\n| Field | Type | Notes |\n|---|---|---|\n| `key` | string |  |\n| `ruleId` | string |  |\n| `at` | string (date-time) |  |\n| `days` | integer |  |\n| `scanned` | integer | Transfers replayed (capped at 1000). |\n| `sampled` | boolean | True when the cap truncated the window. |\n| `matched` | integer |  |\n| `matchedShare` | number \\| null |  |\n| `wouldHold` | integer |  |\n| `wouldAlert` | integer |  |\n| `overlapWithExistingAlerts` | integer |  |\n| `byBand` | Record<string, integer> |  |\n| `sampleTransferIds` | string[] |  |\n\n### RulesVersion\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string |  |\n| `version` | string |  |\n| `rules` | Rule[] |  |\n| `createdAt` | string (date-time) |  |\n| `active` | boolean |  |\n| `note` (optional) | string |  |","x-summary":"The rule studio: draft → backtest → activate with two approvers → live alerts.","x-objects":["Rule","RuleCondition","Backtest","RulesVersion"]},{"name":"Metrics","description":"Calibration per band, coverage, hold-band precision and recall, auto-closed and overturned rates, time to decision by route, and dollars approved without waiting — each { value | null, n } over a window (default 30 days), persisted daily. The demo tenant's figures are public and labelled simulated.\n\n### Metrics\n\n| Field | Type | Notes |\n|---|---|---|\n| `asOf` | string (date-time) |  |\n| `windowDays` | integer |  |\n| `calibration` | object | Per band: labelled transfers that went bad ÷ labelled transfers. |\n| `calibration.clear` | Measured |  |\n| `calibration.review` | Measured |  |\n| `calibration.hold` | Measured |  |\n| `calibration.unscored` | Measured |  |\n| `coverage` | object |  |\n| `coverage.transfers` | Measured |  |\n| `coverage.programs` | Measured |  |\n| `holdBand` | object |  |\n| `holdBand.precision` | Measured |  |\n| `holdBand.recall` | Measured |  |\n| `holdBand.tp` | integer |  |\n| `holdBand.fp` | integer |  |\n| `holdBand.fn` | integer |  |\n| `autoClosed` | Measured |  |\n| `overturned` | Measured |  |\n| `timeToDecisionHours` | object |  |\n| `timeToDecisionHours.automated` | object | Closed alerts routed automated. |\n| `timeToDecisionHours.automated.median` | number \\| null |  |\n| `timeToDecisionHours.automated.p90` | number \\| null |  |\n| `timeToDecisionHours.automated.n` | integer |  |\n| `timeToDecisionHours.reviewed` | object | Closed alerts routed reviewed. |\n| `timeToDecisionHours.reviewed.median` | number \\| null |  |\n| `timeToDecisionHours.reviewed.p90` | number \\| null |  |\n| `timeToDecisionHours.reviewed.n` | integer |  |\n| `approvedWithoutWaiting` | object | Clear-band transfers that never raised an alert. |\n| `approvedWithoutWaiting.count` | integer |  |\n| `approvedWithoutWaiting.cents` | string | Integer cents as a string (bigint-safe). |\n| `approvedWithoutWaiting.n` | integer | Scored transfers. |\n| `eventCoverage` | object | Event families understood ÷ families known. |\n| `eventCoverage.value` | number \\| null |  |\n| `eventCoverage.n` | integer |  |\n| `eventCoverage.seen` | string[] |  |\n\n### Measured\n\n| Field | Type | Notes |\n|---|---|---|\n| `value` | number \\| null | null when nothing was measured |\n| `n` | integer | observations behind the value |","x-summary":"The six numbers the platform reports on itself.","x-objects":["Metrics","Measured"]},{"name":"Report","description":"Reports are built from rows the platform already persisted: the current Skoor with its reasons, history and band transitions, transfers in the window, alerts and dispositions. The record is two hash chains per tenant (dispositions, actions); verification recomputes every hash and reports the first break, and the export is JSON lines an examiner can walk for a date range.\n\n### ActorReport\n\n| Field | Type | Notes |\n|---|---|---|\n| `kind` | const \"actor\" |  |\n| `type` | \"entity\" \\| \"counterparty\" \\| \"agent\" |  |\n| `id` | string |  |\n| `name` | string \\| null |  |\n| `programId` | string (uuid) |  |\n| `generatedAt` | string (date-time) |  |\n| `current` | object \\| null |  |\n| `history` | SkoorPoint[] |  |\n| `transitions` | BandTransition[] |  |\n| `signalTrends` | object \\| null |  |\n| `transfers` | TransfersSummary |  |\n| `alerts` | object[] |  |\n| `alerts[].id` | string (uuid) |  |\n| `alerts[].detector` | string |  |\n| `alerts[].status` | string |  |\n| `alerts[].route` | string \\| null |  |\n| `alerts[].openedAt` | string (date-time) |  |\n| `alerts[].summary` | string |  |\n| `dispositions` | object[] |  |\n| `dispositions[].alertId` | string (uuid) |  |\n| `dispositions[].decidedBy` | string |  |\n| `dispositions[].decision` | string |  |\n| `dispositions[].overturnsDraft` | boolean \\| null |  |\n| `dispositions[].createdAt` | string (date-time) |  |\n\n### ProgramReport\n\n| Field | Type | Notes |\n|---|---|---|\n| `kind` | const \"program\" |  |\n| `id` | string (uuid) |  |\n| `name` | string |  |\n| `generatedAt` | string (date-time) |  |\n| `current` | object \\| null |  |\n| `history` | object[] |  |\n| `history[].asOf` | string (date-time) |  |\n| `history[].programSkoor` | integer \\| null |  |\n| `history[].band` | \"clear\" \\| \"review\" \\| \"hold\" \\| \"unscored\" |  |\n| `history[].n` | integer |  |\n| `transitions` | BandTransition[] |  |\n| `kris` | Record<string, KriValue> |  |\n| `kriDeltas` | object[] |  |\n| `kriDeltas[].name` | string |  |\n| `kriDeltas[].from` | number \\| null |  |\n| `kriDeltas[].to` | number \\| null |  |\n| `kriDeltas[].delta` | number \\| null |  |\n| `kriDeltas[].statusFrom` | string \\| null |  |\n| `kriDeltas[].statusTo` | string |  |\n| `kriDeltas[].n` | integer |  |\n| `rank` | object \\| null |  |\n| `transfers` | TransfersSummary |  |\n| `alertsByDetector` | object[] |  |\n| `alertsByDetector[].detector` | string |  |\n| `alertsByDetector[].route` | string \\| null |  |\n| `alertsByDetector[].count` | integer |  |\n\n### WeeklyReport\n\n| Field | Type | Notes |\n|---|---|---|\n| `kind` | const \"weekly\" |  |\n| `key` | string | `weekly:<programId>:<week>` |\n| `programId` | string (uuid) |  |\n| `programName` | string |  |\n| `isoWeek` | string | e.g. 2026-W37 |\n| `from` | string (date-time) |  |\n| `to` | string (date-time) |  |\n| `generatedAt` | string (date-time) |  |\n| `programSkoor` | object |  |\n| `programSkoor.current` | integer \\| null |  |\n| `programSkoor.prior` | integer \\| null |  |\n| `programSkoor.band` | string |  |\n| `programSkoor.n` | integer |  |\n| `kris` | object[] |  |\n| `kris[].name` | string |  |\n| `kris[].value` | number \\| null |  |\n| `kris[].prior` | number \\| null |  |\n| `kris[].delta` | number \\| null |  |\n| `kris[].status` | string |  |\n| `kris[].priorStatus` | string \\| null |  |\n| `kris[].n` | integer |  |\n| `kris[].unit` | string |  |\n| `kris[].threshold` | number \\| null |  |\n| `alerts` | object |  |\n| `alerts.total` | integer |  |\n| `alerts.byDetector` | object[] |  |\n| `alerts.byDetector[].detector` | string |  |\n| `alerts.byDetector[].route` | string \\| null |  |\n| `alerts.byDetector[].count` | integer |  |\n| `alerts.byStatus` | Record<string, integer> |  |\n| `enteredHold` | object[] |  |\n| `enteredHold[].type` | string |  |\n| `enteredHold[].id` | string |  |\n| `enteredHold[].skoor` | integer \\| null |  |\n| `enteredHold[].n` | integer |  |\n| `enteredHold[].at` | string (date-time) |  |\n| `dispositions` | object |  |\n| `dispositions.total` | integer |  |\n| `dispositions.byPlatform` | integer |  |\n| `dispositions.byPerson` | integer |  |\n| `dispositions.overturned` | integer |  |\n| `timeToDecisionHours` | object | Closed alerts in the week. |\n| `timeToDecisionHours.median` | number \\| null |  |\n| `timeToDecisionHours.p90` | number \\| null |  |\n| `timeToDecisionHours.n` | integer |  |\n| `transfers` | object |  |\n| `transfers.count` | integer |  |\n| `transfers.volumeCents` | string | Integer cents as a string (bigint-safe). |\n| `transfers.returns` | object |  |\n| `transfers.returns.any` | integer |  |\n| `transfers.returns.unauthorized` | integer |  |\n| `transfers.bands` | Record<string, integer> |  |\n| `summary` | string | Plain-text summary written from a template, never a model. |\n\n### ChainReport\n\n| Field | Type | Notes |\n|---|---|---|\n| `ok` | boolean |  |\n| `checked` | integer |  |\n| `firstBreakAt` | object \\| null |  |\n| `chains` | object |  |\n| `chains.dispositions` | object |  |\n| `chains.dispositions.ok` | boolean |  |\n| `chains.dispositions.checked` | integer |  |\n| `chains.actions` | object |  |\n| `chains.actions.ok` | boolean |  |\n| `chains.actions.checked` | integer |  |\n\n### RecordLine\n\n| Field | Type | Notes |\n|---|---|---|\n| `kind` | \"policy\" \\| \"alert\" \\| \"disposition\" \\| \"action\" |  |","x-summary":"Actor, program and weekly reports, plus the examiner record.","x-objects":["ActorReport","ProgramReport","WeeklyReport","ChainReport","RecordLine"]},{"name":"Proof","description":"An EdDSA-signed compact token carrying what the platform observed about one actor at one moment: the Skoor, its band, the version, n, confidence and the reason ids. Unscored actors cannot be proved (409): there is no proof of a number that does not exist. The public key is published as a JWKS.\n\n### ProofClaims\n\n| Field | Type | Notes |\n|---|---|---|\n| `iss` | string | Issuer: the platform URL. |\n| `sub` | string | `<type>:<id>` |\n| `tenant` | string | Tenant slug. |\n| `program` | string \\| null | Program id. |\n| `skoor` | integer |  |\n| `band` | string |  |\n| `version` | string |  |\n| `n` | integer |  |\n| `confidence` | number \\| null |  |\n| `reasons` | string[] | Reason-code ids only. |\n| `simulated` (optional) | const true | Present only when the underlying data is simulated. |\n| `iat` | integer |  |\n| `exp` | integer | 24 hours after iat. |\n| `jti` | string | The actor_skoors row id. |\n\n### ProofVerification\n\n| Field | Type | Notes |\n|---|---|---|\n| `valid` | boolean |  |\n| `reason` (optional) | string | malformed \\| unsupported alg \\| unknown kid \\| bad signature \\| expired \\| no skoor in claims \\| token required |\n| `claims` (optional) | ProofClaims |  |\n| `expired` (optional) | boolean |  |\n\n### Jwks\n\n| Field | Type | Notes |\n|---|---|---|\n| `keys` | object[] |  |\n| `keys[].kty` | string |  |\n| `keys[].crv` | string |  |\n| `keys[].x` | string |  |\n| `keys[].kid` | string |  |\n| `keys[].use` | const \"sig\" |  |\n| `keys[].alg` | const \"EdDSA\" |  |","x-summary":"A signed Skoor another bank can verify offline.","x-objects":["ProofClaims","ProofVerification","Jwks"]},{"name":"Ingest","description":"Column delivers events here, one endpoint per program. The raw body is verified against the program's signing secret (`Column-Signature`, HMAC-SHA256 hex); an invalid signature stores nothing. Deduplicated by event id, safe under redelivery, answered inside the 10-second budget; scoring, detection and triage run after the response.\n\n### ColumnEvent\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string | Column event id; redeliveries carry the same id and are deduplicated. |\n| `created_at` | string |  |\n| `type` | string | e.g. ach.outgoing_transfer.returned |\n| `data` (optional) | object | The bank's object. |","x-summary":"The inbound Column webhook.","x-objects":["ColumnEvent"]},{"name":"Connector","description":"A minimal MCP server: JSON-RPC 2.0 over POST, request/response only. Tools: list_queue, get_alert, decide_alert, skoor_preflight, program_report, actor_report, weekly_report, metrics. Every write goes through the same services as the dashboard, so the record is identical.","x-summary":"Skoor Risk inside Claude (MCP over streamable HTTP).","x-objects":[]},{"name":"Key","description":"Keys let a program or a bank integration call the API without the operator token. A `test` key may only touch simulated programs and the sandbox; a `live` key only Column-sourced programs (403 either way). The secret is shown once at creation and only its sha256 is stored. For a key, the operator on the record is `key:<prefix>` unless an X-Operator header names a person.\n\n### ApiKey\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `name` | string |  |\n| `mode` | \"test\" \\| \"live\" | test_ keys may only touch simulated programs and the sandbox; live_ keys touch Column programs. |\n| `prefix` | string | First 12 characters of the secret, for display. |\n| `programId` | string \\| null (uuid) | null = every program on the tenant. |\n| `scopes` | string[] | [] = all scopes the mode allows. |\n| `lastUsedAt` | string \\| null (date-time) |  |\n| `revokedAt` | string \\| null (date-time) |  |\n| `createdAt` | string (date-time) |  |","x-summary":"API keys: test_ and live_.","x-objects":["ApiKey"]},{"name":"Webhook","description":"Register an https endpoint and receive alert.opened, disposition.signed, action.created, action.approved, preflight.scored, transfer.scored, label.recorded, kri.snapshot as JSON, signed with `Skoor-Signature: t=<unix seconds>,v1=<hex hmac-sha256(secret, \"<t>.<body>\")>` plus `Skoor-Event` and `Skoor-Delivery` headers. Failed deliveries retry with backoff (2^attempts minutes, capped at 6 hours) up to 10 times, then are marked exhausted. The endpoint secret (`whsec_…`) is shown once.\n\n### WebhookEndpoint\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `programId` | string \\| null (uuid) | null = every program on the tenant. |\n| `url` | string | https only (http://127.0.0.1 and http://localhost in test). |\n| `events` | \"alert.opened\" \\| \"disposition.signed\" \\| \"action.created\" \\| \"action.approved\" \\| \"preflight.scored\" \\| \"transfer.scored\" \\| \"label.recorded\" \\| \"kri.snapshot\" \\| \"ping\"[] | [] = every event. |\n| `enabled` | boolean |  |\n| `description` | string \\| null |  |\n| `createdBy` | string |  |\n| `createdAt` | string (date-time) |  |\n\n### WebhookDelivery\n\n| Field | Type | Notes |\n|---|---|---|\n| `id` | string (uuid) |  |\n| `endpointId` | string (uuid) |  |\n| `event` | \"alert.opened\" \\| \"disposition.signed\" \\| \"action.created\" \\| \"action.approved\" \\| \"preflight.scored\" \\| \"transfer.scored\" \\| \"label.recorded\" \\| \"kri.snapshot\" \\| \"ping\" |  |\n| `eventId` | string | Stable id of the emitted event, for the receiver's dedupe. |\n| `payload` | object | { id, type, createdAt, tenant, program, data } |\n| `status` | \"pending\" \\| \"delivered\" \\| \"failed\" \\| \"exhausted\" |  |\n| `attempts` | integer |  |\n| `nextAttemptAt` | string (date-time) | now + min(2^attempts minutes, 6h) after a failure. |\n| `lastStatusCode` | integer \\| null |  |\n| `lastError` | string \\| null |  |\n| `deliveredAt` | string \\| null (date-time) |  |\n| `createdAt` | string (date-time) |  |","x-summary":"Outbound signed webhooks.","x-objects":["WebhookEndpoint","WebhookDelivery"]},{"name":"Sandbox","description":"Build Column-shaped events against a simulated program and push them through the same ingest, scoring, detection and triage as production, then read the result: the transfer's Skoor with its n, the alerts it opened, the label a return produced. Every endpoint requires a test principal and a program whose source is simulated; a Column-sourced program is refused (403).\n\n### SandboxResult\n\n| Field | Type | Notes |\n|---|---|---|\n| `events` | string[] | Ids of the Column-shaped events stored (source = simulated). |\n| `transferId` (optional) | string |  |\n| `alertIds` | string (uuid)[] |  |\n| `skoor` | object \\| null |  |","x-summary":"Simulation endpoints for test_ keys.","x-objects":["SandboxResult"]}],"paths":{"/api/preflight":{"post":{"tags":["Pre-flight"],"summary":"Score a proposed transfer","operationId":"postApiPreflight","description":"Answers clear / review / hold for a transfer that has not been originated yet, and records the answer. 201 on a new answer; 200 when `idempotencyKey` replays a stored one (then `idempotent: true` and `degradedReasons` is empty). Body limit 64 KB.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreflightRequest"},"example":{"programSlug":"northwind-payroll","idempotencyKey":"pf-2026-09-17-0001","transfer":{"rail":"ach","direction":"outgoing","type":"CREDIT","amountCents":"185000","currency":"USD","entityId":"enti_sim_acme","counterparty":{"name":"Blue Ridge Landscaping","routingNumber":"021000021","accountLast4":"4421","countryCode":"US"},"description":"Payroll 2026-09-19"}}}}},"responses":{"200":{"description":"Replayed from the idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preflight"}}}},"201":{"description":"Scored and recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preflight"},"example":{"preflightId":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11","decision":"clear","skoor":12,"band":"clear","version":"trs-v1","n":210,"confidence":0.81,"reasons":[{"id":"TRS-CP-01","signal":"counterparty.first_time","weight":8,"hard":false,"sentence":"This is the first transfer with this counterparty."}],"limitsHit":[],"degraded":false,"degradedReasons":[],"latencyMs":41,"idempotent":false,"programId":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"}}}},"400":{"description":"Validation failed; `error` lists the paths.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/preflight?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"programSlug\":\"northwind-payroll\",\"idempotencyKey\":\"pf-2026-09-17-0001\",\"transfer\":{\"rail\":\"ach\",\"direction\":\"outgoing\",\"type\":\"CREDIT\",\"amountCents\":\"185000\",\"currency\":\"USD\",\"entityId\":\"enti_sim_acme\",\"counterparty\":{\"name\":\"Blue Ridge Landscaping\",\"routingNumber\":\"021000021\",\"accountLast4\":\"4421\",\"countryCode\":\"US\"},\"description\":\"Payroll 2026-09-19\"}}'"}},"/api/preflight/stats":{"get":{"tags":["Pre-flight"],"summary":"Pre-flight statistics for a window","operationId":"getApiPreflightStats","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"days","in":"query","description":"Window in days, 1–365 (default 30).","required":false,"schema":{"type":"integer","minimum":1,"maximum":365},"example":30}],"responses":{"200":{"description":"Counts by decision, latency percentiles, matched and degraded shares, and the graded confusion counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreflightStats"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/preflight/stats?tenant=demo&days=30\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/preflight/{id}":{"get":{"tags":["Pre-flight"],"summary":"One pre-flight answer with its request","operationId":"getApiPreflightById","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Pre-flight id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"The stored answer, the request as submitted, and the matched transfer when one landed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreflightDetail"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown pre-flight on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/preflight/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/actions":{"post":{"tags":["Action"],"summary":"Request an action","operationId":"postApiActions","description":"Writes the chained request attributed to X-Operator. Single-approver actions execute at once through the bank's API (or a recorded dry run); two-approver actions wait for `approve`.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"programId":{"type":"string","format":"uuid"},"alertId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["clear_hold","cancel_transfer","pause_card","suspend_card_account","freeze_account","request_to_program","periodic_review"]},"targetType":{"type":"string","description":"What the action acts on.","minLength":1},"targetId":{"type":"string","minLength":1}},"required":["programId","type","targetType","targetId"],"additionalProperties":false},"example":{"programId":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55","alertId":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11","type":"request_to_program","targetType":"program","targetId":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"}}}},"responses":{"201":{"description":"The action row; `status` is requested (two approvers), executed, or failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Action"}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/actions?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"programId\":\"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55\",\"alertId\":\"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11\",\"type\":\"request_to_program\",\"targetType\":\"program\",\"targetId\":\"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55\"}'"}},"/api/actions/{id}/approve":{"post":{"tags":["Action"],"summary":"Approve a two-approver action","operationId":"postApiActionsByIdApprove","description":"The approver defaults to X-Operator. The requester approving their own action is recorded as `refused` (200 with status refused), never executed.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Action id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"approverId":{"type":"string","description":"Overrides X-Operator as the approver of record."}},"required":[],"additionalProperties":false},"example":{"approverId":"second.approver@bank.example"}}}},"responses":{"200":{"description":"The action row after approval: approved / executed / failed / refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Action"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this tenant's action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/actions/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11/approve?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"approverId\":\"second.approver@bank.example\"}'"}},"/api/clocks/overdue":{"get":{"tags":["Alert"],"summary":"Alerts past their decision clock","operationId":"getApiClocksOverdue","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Waiting alerts (open, held, escalated) whose due time has passed, oldest due first, with the clock policy in force.","content":{"application/json":{"schema":{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"policy":{"$ref":"#/components/schemas/ClockPolicy"},"count":{"type":"integer"},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/OverdueAlert"}}},"required":["asOf","policy","count","alerts"],"additionalProperties":false}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/clocks/overdue?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/skoor/backfill/{programId}":{"post":{"tags":["Transfer"],"summary":"Rescore every transfer on a program","operationId":"postApiSkoorBackfillByProgramId","description":"Recomputes the Transaction Risk Skoor for every transfer on the program with the current scorer. The bearer must be the operator token of the tenant that owns the program; X-Operator is not required.\n\nAuth: Bearer operator token of the tenant that owns the program. `X-Operator` is not required.","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"}],"responses":{"200":{"description":"How many transfers were scored.","content":{"application/json":{"schema":{"type":"object","properties":{"scored":{"type":"integer"}},"required":["scored"],"additionalProperties":false}}}},"401":{"description":"Bearer is not the owning tenant's operator token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/skoor/backfill/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\""}},"/api/skoor/actors/backfill/{programId}":{"post":{"tags":["Entity"],"summary":"Rescore every actor on a program","operationId":"postApiSkoorActorsBackfillByProgramId","description":"Recomputes the Entity, Counterparty and Agent Risk Skoors for every actor on the program and re-runs the actor detectors. Operator token of the owning tenant; X-Operator is not required.\n\nAuth: Bearer operator token of the tenant that owns the program. `X-Operator` is not required.","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"}],"responses":{"200":{"description":"Actors scored, by kind (agents are counted inside entities or counterparties as well).","content":{"application/json":{"schema":{"type":"object","properties":{"entities":{"type":"integer"},"counterparties":{"type":"integer"},"agents":{"type":"integer"}},"required":["entities","counterparties","agents"],"additionalProperties":false}}}},"401":{"description":"Bearer is not the owning tenant's operator token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/skoor/actors/backfill/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\""}},"/api/kri/snapshot/{programId}":{"post":{"tags":["KRI snapshot"],"summary":"Take a KRI snapshot now","operationId":"postApiKriSnapshotByProgramId","description":"Computes every KRI for the program over its window, stores the snapshot, and emits `kri.snapshot` so the program detectors run. Operator token of the owning tenant; X-Operator is not required.\n\nAuth: Bearer operator token of the tenant that owns the program. `X-Operator` is not required.","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"}],"responses":{"200":{"description":"The new snapshot id. Read it back inside the program report.","content":{"application/json":{"schema":{"type":"object","properties":{"snapshotId":{"type":"string","format":"uuid"}},"required":["snapshotId"],"additionalProperties":false}}}},"401":{"description":"Bearer is not the owning tenant's operator token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Program not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/kri/snapshot/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\""}},"/api/limits/ladder":{"get":{"tags":["Policy"],"summary":"The tenant's limit ladder","operationId":"getApiLimitsLadder","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Per-band caps for counterparties and entities and the re-screen cadence; the default ladder when the tenant has not written one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitLadder"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/limits/ladder?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/limits/evaluate":{"get":{"tags":["Policy"],"summary":"Evaluate an amount against the ladder","operationId":"getApiLimitsEvaluate","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"programId","in":"query","description":"Program id.","required":false,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"amountCents","in":"query","description":"Integer cents.","required":false,"schema":{"type":"string","pattern":"^\\d+$"},"example":"185000"},{"name":"entityId","in":"query","description":"Entity to evaluate, if any.","required":false,"schema":{"type":"string"},"example":"enti_sim_acme"},{"name":"counterpartyId","in":"query","description":"Counterparty to evaluate, if any.","required":false,"schema":{"type":"string"},"example":"cpty_sim_1"}],"responses":{"200":{"description":"Limit hits (hard when an actor is in the hold band), the actors' bands, their re-screen cadence and the ladder used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitEvaluation"}}}},"400":{"description":"programId and amountCents required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/limits/evaluate?tenant=demo&programId=a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55&amountCents=185000&entityId=enti_sim_acme&counterpartyId=cpty_sim_1\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/autonomy/{programId}":{"get":{"tags":["Policy"],"summary":"A program's autonomy tier and eligibility","operationId":"getApiAutonomyByProgramId","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Tier, granted detectors, the effective policy, per-detector eligibility from the last 30 days of person decisions, and the version history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramAutonomy"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/autonomy/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/autonomy/{programId}/activate":{"post":{"tags":["Policy"],"summary":"Grant a detector to the automated side","operationId":"postApiAutonomyByProgramIdActivate","description":"Needs an eligible detector (≥10 person decisions in 30 days, ≥90% matching the draft, none of the never-automate set) and a second approver distinct from X-Operator; at most one widening per week. Writes a new program policy version on the record.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"detector":{"type":"string"},"approverId":{"type":"string"}},"required":["detector","approverId"],"additionalProperties":false},"example":{"detector":"repeat_pattern","approverId":"second.approver@bank.example"}}}},"responses":{"201":{"description":"The new version and tier.","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"tier":{"type":"string","enum":["supervised","standard","trusted"]},"granted":{"type":"array","items":{"type":"string"}}},"required":["version","tier","granted"],"additionalProperties":false}}}},"400":{"description":"detector and approverId required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Refused: not eligible, same approver, cadence, or never-automate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/autonomy/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55/activate?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"detector\":\"repeat_pattern\",\"approverId\":\"second.approver@bank.example\"}'"}},"/api/autonomy/{programId}/demote":{"post":{"tags":["Policy"],"summary":"Run the demotion check now","operationId":"postApiAutonomyByProgramIdDemote","description":"Narrows the automated side for any granted detector whose overturn rate in the window is above 10%. The same check runs every 15 minutes.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Detectors demoted (empty when nothing changed) and the version written, if any.","content":{"application/json":{"schema":{"type":"object","properties":{"demoted":{"type":"array","items":{"type":"string"}},"version":{"type":["string","null"]}},"required":["demoted","version"],"additionalProperties":false}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/autonomy/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55/demote?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/rules":{"get":{"tags":["Rule"],"summary":"List rules with their last backtest","operationId":"getApiRules","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Every rule on the tenant, each with its latest backtest, plus the field vocabulary and operators the DSL accepts.","content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Rule"},{"type":"object","properties":{"lastBacktest":{"oneOf":[{"$ref":"#/components/schemas/Backtest"},{"type":"null"}]}},"required":["lastBacktest"],"additionalProperties":true}]}},"fields":{"type":"array","items":{"type":"string"}},"operators":{"type":"array","items":{"type":"string"}}},"required":["rules","fields","operators"],"additionalProperties":false}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/rules?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""},"post":{"tags":["Rule"],"summary":"Create or update a draft rule","operationId":"postApiRules","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleDraft"},"example":{"id":"large-first-time-wire","name":"Large wire to a first-time counterparty","when":[{"field":"rail","op":"eq","value":"wire"},{"field":"counterparty.firstTime","op":"eq","value":true},{"field":"amountCents","op":"gte","value":1000000}],"action":"alert","severity":"high"}}}},"responses":{"201":{"description":"The rule as stored (status draft).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rule"}}}},"400":{"description":"Invalid rule; `issues` lists the Zod problems.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The rule is active; pause it before editing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/rules?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"id\":\"large-first-time-wire\",\"name\":\"Large wire to a first-time counterparty\",\"when\":[{\"field\":\"rail\",\"op\":\"eq\",\"value\":\"wire\"},{\"field\":\"counterparty.firstTime\",\"op\":\"eq\",\"value\":true},{\"field\":\"amountCents\",\"op\":\"gte\",\"value\":1000000}],\"action\":\"alert\",\"severity\":\"high\"}'"}},"/api/rules/{id}/backtest":{"post":{"tags":["Rule"],"summary":"Backtest a rule against the tenant's own transfers","operationId":"postApiRulesByIdBacktest","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Rule id.","required":true,"schema":{"type":"string"},"example":"large-first-time-wire"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","description":"Window in days (default 90)."}},"required":[],"additionalProperties":false},"example":{"days":90}}}},"responses":{"200":{"description":"What the rule would have matched: scanned (capped at 1000, then `sampled`), matched, share, would-hold, overlap with existing alerts, by band, and sample transfer ids.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Backtest"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Rule not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/rules/large-first-time-wire/backtest?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"days\":90}'"}},"/api/rules/{id}/activate":{"post":{"tags":["Rule"],"summary":"Activate a rule with a second approver","operationId":"postApiRulesByIdActivate","description":"Needs a backtest younger than 24 hours and an approver distinct from X-Operator. Writes a new rules version and chains a `periodic_review` action (target `rule`, `<id>@<version>`) on the rule's program, else the tenant's first program.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Rule id.","required":true,"schema":{"type":"string"},"example":"large-first-time-wire"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"approverId":{"type":"string"}},"required":["approverId"],"additionalProperties":false},"example":{"approverId":"second.approver@bank.example"}}}},"responses":{"200":{"description":"The active rule, the rules version written, and the action id recorded on the chain (null when none).","content":{"application/json":{"schema":{"type":"object","properties":{"rule":{"$ref":"#/components/schemas/Rule"},"version":{"$ref":"#/components/schemas/RulesVersion"},"actionId":{"type":["string","null"]}},"required":["rule","version","actionId"],"additionalProperties":false}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Rule not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"No fresh backtest, same approver, or the rule is archived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/rules/large-first-time-wire/activate?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"approverId\":\"second.approver@bank.example\"}'"}},"/api/rules/{id}/pause":{"post":{"tags":["Rule"],"summary":"Pause an active rule","operationId":"postApiRulesByIdPause","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Rule id.","required":true,"schema":{"type":"string"},"example":"large-first-time-wire"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"The rule with status paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rule"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Rule not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/rules/large-first-time-wire/pause?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/agents":{"get":{"tags":["Agent"],"summary":"Imported agents with their Skoors","operationId":"getApiAgents","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Up to 500 agents on the tenant's agent program, newest update first.","content":{"application/json":{"schema":{"type":"object","properties":{"programId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"lastImportAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}},"required":["programId","lastImportAt","agents"],"additionalProperties":false}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/agents?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/agents/import":{"post":{"tags":["Agent"],"summary":"Import agents from the scan index","operationId":"postApiAgentsImport","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","description":"Agents to import, 1–200 (default 50).","minimum":1,"maximum":200},"chainId":{"type":"integer"},"offset":{"type":"integer"},"txLimit":{"type":"integer","description":"Explorer settlements to scan for matches."},"withProfiles":{"type":"boolean","description":"Fetch the compliance profile per agent."}},"required":[],"additionalProperties":false},"example":{"limit":50,"withProfiles":true}}}},"responses":{"201":{"description":"What was imported.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportResult"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Scan index unavailable; `detail` carries the upstream message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/agents/import?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"limit\":50,\"withProfiles\":true}'"}},"/api/metrics":{"get":{"tags":["Metrics"],"summary":"The six numbers for the tenant","operationId":"getApiMetrics","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"days","in":"query","description":"Window in days, 1–365 (default 30).","required":false,"schema":{"type":"integer","minimum":1,"maximum":365},"example":30}],"responses":{"200":{"description":"Every metric with its n; also persisted as today's daily snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Metrics"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/metrics?tenant=demo&days=30\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/governance":{"get":{"tags":["Metrics"],"summary":"The model-risk record (how the AI is governed)","operationId":"getApiGovernance","description":"Generated from the live record and the running code, in the shape of SR 11-7 and NIST AI RMF: model inventory, the automated-or-reviewed boundary per program, eval-set validation and live calibration, the six metrics, change control (policy and rule versions, hash chain), human oversight (attribution, clocks, overturns, the language model's role), data rules, and a requirement-to-evidence mapping. Every number carries n; unmeasured is null. Rendered for people at /governance.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"days","in":"query","description":"Window in days, 1–365 (default 30).","required":false,"schema":{"type":"integer","minimum":1,"maximum":365},"example":30}],"responses":{"200":{"description":"The record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GovernanceRecord"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/governance?tenant=demo&days=30\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/public/metrics":{"get":{"tags":["Metrics"],"summary":"The demo tenant's six numbers, public","operationId":"getApiPublicMetrics","description":"Auth: none.","parameters":[{"name":"days","in":"query","description":"Window in days, 1–90 (default 30; only the 30-day answer is cached for 60 s).","required":false,"schema":{"type":"integer","minimum":1,"maximum":90},"example":30}],"responses":{"200":{"description":"Metrics for the demo tenant, labelled simulated.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"tenant":{"type":"string"},"simulated":{"const":true}},"required":["tenant","simulated"],"additionalProperties":true},{"$ref":"#/components/schemas/Metrics"}]}}}},"404":{"description":"No demo tenant on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[],"x-curl":"curl -s \"https://skoor.ai/risk/api/public/metrics?days=30\""}},"/api/reports/actor/{type}/{id}":{"get":{"tags":["Report"],"summary":"Actor report","operationId":"getApiReportsActorByTypeById","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"type","in":"path","description":"entity, counterparty or agent.","required":true,"schema":{"type":"string","enum":["entity","counterparty","agent"]},"example":"entity"},{"name":"id","in":"path","description":"Actor id.","required":true,"schema":{"type":"string"},"example":"enti_sim_acme"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"The current Skoor with reasons, history, band transitions, signal trends, transfers, alerts and dispositions for the actor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActorReport"}}}},"400":{"description":"type must be entity, counterparty, or agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown actor on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/reports/actor/entity/enti_sim_acme?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/reports/program/{id}":{"get":{"tags":["Report"],"summary":"Program report","operationId":"getApiReportsProgramById","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"The Program Risk Skoor with history, KRIs and their deltas, rank inside the bank, transfers and alerts by detector.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramReport"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/reports/program/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/reports/weekly/{programId}":{"get":{"tags":["Report"],"summary":"Weekly program risk report","operationId":"getApiReportsWeeklyByProgramId","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"programId","in":"path","description":"Program id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"week","in":"query","description":"ISO week (YYYY-Www). Default: the last complete week.","required":false,"schema":{"type":"string","pattern":"^\\d{4}-W\\d{2}$"},"example":"2026-W37"},{"name":"rebuild","in":"query","description":"Pass 1 to rebuild a stored report.","required":false,"schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The stored report for the week, built on first request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WeeklyReport"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/reports/weekly/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55?tenant=demo&week=2026-W37\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/record/verify":{"get":{"tags":["Report"],"summary":"Verify the record chains","operationId":"getApiRecordVerify","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Every disposition and action hash recomputed from stored fields; the first break, if any.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainReport"},"example":{"ok":true,"checked":412,"firstBreakAt":null,"chains":{"dispositions":{"ok":true,"checked":380},"actions":{"ok":true,"checked":32}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/record/verify?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/record/export":{"get":{"tags":["Report"],"summary":"Examiner export (JSON lines)","operationId":"getApiRecordExport","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"from","in":"query","description":"Start of the range (ISO 8601). Default 30 days ago.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-08-18T00:00:00Z"},{"name":"to","in":"query","description":"End of the range. Default now.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-09-17T00:00:00Z"}],"responses":{"200":{"description":"application/x-ndjson, served as an attachment: the policy line first, then alerts, dispositions and actions in time order.","content":{"application/x-ndjson":{"schema":{"$ref":"#/components/schemas/RecordLine"}}}},"400":{"description":"Bad date.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/record/export?tenant=demo&from=2026-08-18T00%3A00%3A00Z&to=2026-09-17T00%3A00%3A00Z\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/proofs/actor":{"post":{"tags":["Proof"],"summary":"Issue a signed proof for an actor","operationId":"postApiProofsActor","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["entity","counterparty","agent"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"example":{"type":"entity","id":"enti_sim_acme"}}}},"responses":{"201":{"description":"A 24-hour EdDSA token, its claims, and where the public key lives.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Compact JWT (header.payload.signature), alg EdDSA."},"claims":{"$ref":"#/components/schemas/ProofClaims"},"jwksUrl":{"type":"string"}},"required":["token","claims","jwksUrl"],"additionalProperties":false}}}},"400":{"description":"type (entity|counterparty|agent) and id required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No Skoor on record for the actor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The actor is unscored; a proof is never issued for a null Skoor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/proofs/actor?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"entity\",\"id\":\"enti_sim_acme\"}'"}},"/api/proofs/verify":{"get":{"tags":["Proof"],"summary":"Verify a proof","operationId":"getApiProofsVerify","description":"Checks the signature against the platform key, the expiry and the presence of a Skoor. No database access; anyone may call it.\n\nAuth: none.","parameters":[{"name":"token","in":"query","description":"The compact token.","required":false,"schema":{"type":"string"},"example":"eyJhbGciOiJFZERTQSIs…"}],"responses":{"200":{"description":"valid with the claims, or invalid with the reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofVerification"}}}},"400":{"description":"token required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofVerification"},"example":{"valid":false,"reason":"token required"}}}}},"security":[],"x-curl":"curl -s \"https://skoor.ai/risk/api/proofs/verify?token=eyJhbGciOiJFZERTQSIs%E2%80%A6\""}},"/.well-known/skoor-risk-jwks.json":{"get":{"tags":["Proof"],"summary":"The public key set","operationId":"getWellKnownSkoorRiskJwksJson","description":"Auth: none.","responses":{"200":{"description":"JWKS with the current Ed25519 key (cached 5 minutes).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Jwks"}}}}},"security":[],"x-curl":"curl -s \"https://skoor.ai/risk/.well-known/skoor-risk-jwks.json\""}},"/webhooks/column/{key}":{"post":{"tags":["Ingest"],"summary":"Receive a Column event","operationId":"postWebhooksColumnByKey","description":"Raw body, 2 MB limit, any content type. Verified before anything is parsed; stored, deduplicated by event id, answered at once. Writes are awaited; scoring, detection and triage run after the response. Column registers this URL itself when a tenant connects its key.\n\nAuth: none.","parameters":[{"name":"key","in":"path","description":"The program slug (or the Column webhook endpoint id).","required":true,"schema":{"type":"string"},"example":"northwind-payroll"},{"name":"Column-Signature","in":"header","description":"HMAC-SHA256 of the raw body with the program's webhook secret, hex.","required":true,"schema":{"type":"string"}},{"name":"Webhook-Endpoint-Id","in":"header","description":"Column's endpoint id; resolves the program before the path key.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColumnEvent"},"example":{"id":"evnt_2Zx9…","created_at":"2026-09-17T12:00:00Z","type":"ach.outgoing_transfer.returned","data":{"id":"acht_…","return_details":[{"return_code":"R10"}]}}}}},"responses":{"200":{"description":"Stored (or a redelivery).","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean"},"duplicate":{"type":"boolean"}},"required":["received","duplicate"],"additionalProperties":false}}}},"400":{"description":"Invalid JSON or an event without id, created_at and type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid signature; nothing stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown program.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/webhooks/column/northwind-payroll\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"id\":\"evnt_2Zx9…\",\"created_at\":\"2026-09-17T12:00:00Z\",\"type\":\"ach.outgoing_transfer.returned\",\"data\":{\"id\":\"acht_…\",\"return_details\":[{\"return_code\":\"R10\"}]}}'"}},"/connector/manifest.json":{"get":{"tags":["Connector"],"summary":"Connector manifest","operationId":"getConnectorManifestJson","description":"Auth: none.","responses":{"200":{"description":"Name, version, transport, MCP URL, protocol version, auth shape and tool names.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"transport":{"const":"streamable-http"},"url":{"type":"string"},"protocolVersion":{"type":"string"},"auth":{"type":"object","properties":{"type":{"const":"bearer"},"header":{"type":"string"},"extraHeaders":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}},"required":["type","header","extraHeaders","note"],"additionalProperties":false},"tools":{"type":"array","items":{"type":"string"}}},"required":["name","version","description","transport","url","protocolVersion","auth","tools"],"additionalProperties":false}}}}},"security":[],"x-curl":"curl -s \"https://skoor.ai/risk/connector/manifest.json\""}},"/mcp":{"post":{"tags":["Connector"],"summary":"MCP JSON-RPC endpoint","operationId":"postMcp","description":"Streamable HTTP, request/response only: `initialize`, `tools/list`, `tools/call`. Notifications (no id) are answered 202 with no body. Body limit 1 MB. Protocol version 2025-06-18.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"const":"2.0"},"id":{"type":["string","integer","null"]},"method":{"type":"string"},"params":{"type":"object","additionalProperties":true}},"required":["jsonrpc","method"],"additionalProperties":false},"example":{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_queue","arguments":{"route":"reviewed","limit":5}}}}}},"responses":{"200":{"description":"A JSON-RPC response (or a batch of them).","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"const":"2.0"},"id":{"type":["string","integer","null"]},"result":{"type":"object","additionalProperties":true},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"type":"object","additionalProperties":true}},"required":["code","message"],"additionalProperties":false}},"required":["jsonrpc","id"],"additionalProperties":false}}}},"202":{"description":"Notification accepted; no body."},"400":{"description":"X-Operator header required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/mcp?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"list_queue\",\"arguments\":{\"route\":\"reviewed\",\"limit\":5}}}'"},"get":{"tags":["Connector"],"summary":"No server-initiated streams","operationId":"getMcp","description":"Auth: none.","responses":{"405":{"description":"Streamable HTTP: POST JSON-RPC to this URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[],"x-curl":"curl -s \"https://skoor.ai/risk/mcp\""}},"/api/keys":{"post":{"tags":["Key"],"summary":"Create an API key","operationId":"postApiKeys","description":"`live` keys may be created only when the tenant's Column environment is live or the operator passes `confirmLive: true`. The secret (`test_`/`live_` + 32 base64url characters) is returned exactly once.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"mode":{"type":"string","enum":["test","live"]},"programId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"scopes":{"type":"array","items":{"type":"string"}},"confirmLive":{"type":"boolean"}},"required":["name","mode"],"additionalProperties":false},"example":{"name":"Northwind integration","mode":"test","programId":null}}}},"responses":{"201":{"description":"The key with its secret, shown once.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"mode":{"type":"string","enum":["test","live"]},"prefix":{"type":"string"},"secret":{"type":"string"}},"required":["id","name","mode","prefix","secret"],"additionalProperties":false}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/keys?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Northwind integration\",\"mode\":\"test\",\"programId\":null}'"},"get":{"tags":["Key"],"summary":"List keys (never the secret)","operationId":"getApiKeys","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Every key on the tenant, including revoked ones.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/keys?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/keys/{id}":{"delete":{"tags":["Key"],"summary":"Revoke a key","operationId":"deleteApiKeysById","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Key id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Revoked; the key stops working at once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this tenant's key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X DELETE \"https://skoor.ai/risk/api/keys/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/webhooks":{"post":{"tags":["Webhook"],"summary":"Register an endpoint","operationId":"postApiWebhooks","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"https only."},"events":{"type":"array","items":{"type":"string","enum":["alert.opened","disposition.signed","action.created","action.approved","preflight.scored","transfer.scored","label.recorded","kri.snapshot"]}},"programId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"description":{"type":"string"}},"required":["url"],"additionalProperties":false},"example":{"url":"https://bank.example/skoor/webhooks","events":["alert.opened","preflight.scored"]}}}},"responses":{"201":{"description":"The endpoint with its signing secret (`whsec_…`), shown once.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"secret":{"type":"string"}},"required":["id","url","events","secret"],"additionalProperties":false}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/webhooks?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://bank.example/skoor/webhooks\",\"events\":[\"alert.opened\",\"preflight.scored\"]}'"},"get":{"tags":["Webhook"],"summary":"List endpoints","operationId":"getApiWebhooks","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Endpoints on the tenant, without secrets.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/webhooks?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/webhooks/events":{"get":{"tags":["Webhook"],"summary":"Event catalogue","operationId":"getApiWebhooksEvents","description":"The event types an endpoint may subscribe to, with one example payload each.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Event names and example payloads.","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"}},"examples":{"type":"object","additionalProperties":true}},"required":[],"additionalProperties":false}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/webhooks/events?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/webhooks/{id}":{"delete":{"tags":["Webhook"],"summary":"Disable an endpoint","operationId":"deleteApiWebhooksById","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Endpoint id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this tenant's endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X DELETE \"https://skoor.ai/risk/api/webhooks/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/webhooks/{id}/deliveries":{"get":{"tags":["Webhook"],"summary":"Recent deliveries","operationId":"getApiWebhooksByIdDeliveries","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Endpoint id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"limit","in":"query","description":"Newest first.","required":false,"schema":{"type":"integer"},"example":20}],"responses":{"200":{"description":"Deliveries, latest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this tenant's endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s \"https://skoor.ai/risk/api/webhooks/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11/deliveries?tenant=demo&limit=20\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/webhooks/{id}/deliveries/{deliveryId}/redeliver":{"post":{"tags":["Webhook"],"summary":"Redeliver","operationId":"postApiWebhooksByIdDeliveriesByDeliveryIdRedeliver","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Endpoint id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"deliveryId","in":"path","description":"Delivery id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"Reset to pending; attempts unchanged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this tenant's delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/webhooks/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11/deliveries/a1e0c2d4-6b7f-4d8e-9a3b-0c1d2e3f4a55/redeliver?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/webhooks/{id}/test":{"post":{"tags":["Webhook"],"summary":"Send a ping","operationId":"postApiWebhooksByIdTest","description":"Auth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"Endpoint id.","required":true,"schema":{"type":"string","format":"uuid"},"example":"5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"}],"responses":{"200":{"description":"A `ping` delivery enqueued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this tenant's endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/webhooks/5f3c1c8e-2a44-4c0b-9d0f-2b0a2f6e7c11/test?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\""}},"/api/sandbox/transfers":{"post":{"tags":["Sandbox"],"summary":"Simulate a transfer through its Column lifecycle events and score it.","operationId":"postApiSandboxTransfers","description":"Builds the same event sequence Column would deliver (ACH: initiated → submitted → settled [→ completed]; wire/realtime: initiated → completed; book: completed; card: authorization) and runs scoring, detection and triage before answering. `entityId` and `counterpartyId` must already exist on the program; without a counterparty one is minted.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rail","direction","amountCents"],"properties":{"programId":{"type":"string","format":"uuid","description":"The simulated program. One of programId / programSlug is required."},"programSlug":{"type":"string","description":"The simulated program by slug."},"rail":{"type":"string","enum":["ach","wire","book","realtime","card"]},"direction":{"type":"string","enum":["incoming","outgoing"]},"amountCents":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","pattern":"^\\d{1,18}$"}],"description":"Integer cents. A numeric string is accepted for amounts above 2^53."},"status":{"type":"string","enum":["initiated","settled","completed","manual_review","hold"],"default":"settled","description":"Final state of the lifecycle. `completed` records an `ok` outcome label, as a completed Column transfer does."},"type":{"type":"string","enum":["CREDIT","DEBIT"],"description":"ACH only. Defaults to CREDIT outgoing, DEBIT incoming."},"entityId":{"type":"string"},"counterpartyId":{"type":"string"},"counterpartyCountry":{"type":"string","pattern":"^[A-Z]{2}$"},"memo":{"type":"string","maxLength":200},"at":{"type":"string","format":"date-time","description":"Column created_at of the transfer. Defaults to now; backdate to build history."},"cardDecision":{"type":"string","enum":["approved","declined"],"description":"card only"},"cardDecisionReason":{"type":"string","description":"card only, e.g. suspected_fraud"}}},"example":{"request":{"programSlug":"northwind-payroll","rail":"ach","direction":"outgoing","type":"DEBIT","amountCents":250000,"entityId":"enti_sb_1a2b3c4d5e6f7081","memo":"vendor payment"},"response":{"program":"northwind-payroll","events":["evnt_sb_m1a2b3c4_5e6f7a8b1","evnt_sb_m1a2b3c4_9c0d1e2f2","evnt_sb_m1a2b3c4_3a4b5c6d3"],"transferId":"acht_sb_9f2c1e0a7b3d4c55","transfer":{"id":"acht_sb_9f2c1e0a7b3d4c55","rail":"ach","direction":"outgoing","type":"DEBIT","amountCents":"250000","currency":"USD","status":"SETTLED","entityId":"enti_sb_1a2b3c4d5e6f7081","counterpartyId":"cpty_sb_0a1b2c3d4e5f6071","counterpartyCountry":"US","returnCode":null,"columnCreatedAt":"2026-09-17T15:04:05.000Z"},"skoor":{"value":null,"band":"unscored","n":0,"version":"trs-v1","confidence":null},"alertIds":[],"alerts":[],"labels":[]}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","events","transferId","transfer","skoor","alertIds","alerts","labels"],"properties":{"program":{"type":"string","description":"Program slug."},"events":{"type":"array","items":{"type":"string"},"description":"Ids of the Column-shaped events stored, in order."},"transferId":{"type":"string"},"transfer":{"$ref":"#/components/schemas/SandboxTransfer"},"skoor":{"$ref":"#/components/schemas/Skoor"},"alertIds":{"type":"array","items":{"type":"string","format":"uuid"}},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/SandboxAlert"}},"labels":{"type":"array","items":{"$ref":"#/components/schemas/SandboxLabel"}}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/transfers?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"programSlug\":\"northwind-payroll\",\"rail\":\"ach\",\"direction\":\"outgoing\",\"type\":\"DEBIT\",\"amountCents\":250000,\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"memo\":\"vendor payment\"},\"response\":{\"program\":\"northwind-payroll\",\"events\":[\"evnt_sb_m1a2b3c4_5e6f7a8b1\",\"evnt_sb_m1a2b3c4_9c0d1e2f2\",\"evnt_sb_m1a2b3c4_3a4b5c6d3\"],\"transferId\":\"acht_sb_9f2c1e0a7b3d4c55\",\"transfer\":{\"id\":\"acht_sb_9f2c1e0a7b3d4c55\",\"rail\":\"ach\",\"direction\":\"outgoing\",\"type\":\"DEBIT\",\"amountCents\":\"250000\",\"currency\":\"USD\",\"status\":\"SETTLED\",\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"counterpartyId\":\"cpty_sb_0a1b2c3d4e5f6071\",\"counterpartyCountry\":\"US\",\"returnCode\":null,\"columnCreatedAt\":\"2026-09-17T15:04:05.000Z\"},\"skoor\":{\"value\":null,\"band\":\"unscored\",\"n\":0,\"version\":\"trs-v1\",\"confidence\":null},\"alertIds\":[],\"alerts\":[],\"labels\":[]}}'"}},"/api/sandbox/transfers/{id}/return":{"post":{"tags":["Sandbox"],"summary":"Return an ACH transfer with a NACHA code; the outcome label follows production rules.","operationId":"postApiSandboxTransfersByIdReturn","description":"Replays the stored transfer as `ach.*_transfer.returned` with `return_details`. Unauthorized codes (R05, R07, R10, R11, R29, R51) record a `bad` label; NSF and administrative codes record `returned`. Only ACH transfers can be returned.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"id","required":true,"schema":{"type":"string"},"example":"acht_sandbox_0001"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","enum":["R01","R02","R03","R04","R05","R07","R08","R10","R11","R16","R29","R51"]},"at":{"type":"string","format":"date-time"}}},"example":{"request":{"code":"R10"},"response":{"program":"northwind-payroll","events":["evnt_sb_m1a2b3c4_7e8f9a0b4"],"transferId":"acht_sb_9f2c1e0a7b3d4c55","transfer":{"id":"acht_sb_9f2c1e0a7b3d4c55","rail":"ach","direction":"outgoing","type":"DEBIT","amountCents":"250000","currency":"USD","status":"RETURNED","entityId":"enti_sb_1a2b3c4d5e6f7081","counterpartyId":"cpty_sb_0a1b2c3d4e5f6071","counterpartyCountry":"US","returnCode":"R10","columnCreatedAt":"2026-09-17T15:04:05.000Z"},"skoor":{"value":null,"band":"unscored","n":0,"version":"trs-v1","confidence":null},"alertIds":[],"alerts":[],"labels":[{"label":"bad","source":"outcome:return","detail":"R10:unauthorized"}]}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","events","transferId","transfer","skoor","alertIds","alerts","labels"],"properties":{"program":{"type":"string","description":"Program slug."},"events":{"type":"array","items":{"type":"string"},"description":"Ids of the Column-shaped events stored, in order."},"transferId":{"type":"string"},"transfer":{"$ref":"#/components/schemas/SandboxTransfer"},"skoor":{"$ref":"#/components/schemas/Skoor"},"alertIds":{"type":"array","items":{"type":"string","format":"uuid"}},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/SandboxAlert"}},"labels":{"type":"array","items":{"$ref":"#/components/schemas/SandboxLabel"}}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/transfers/acht_sandbox_0001/return?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"code\":\"R10\"},\"response\":{\"program\":\"northwind-payroll\",\"events\":[\"evnt_sb_m1a2b3c4_7e8f9a0b4\"],\"transferId\":\"acht_sb_9f2c1e0a7b3d4c55\",\"transfer\":{\"id\":\"acht_sb_9f2c1e0a7b3d4c55\",\"rail\":\"ach\",\"direction\":\"outgoing\",\"type\":\"DEBIT\",\"amountCents\":\"250000\",\"currency\":\"USD\",\"status\":\"RETURNED\",\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"counterpartyId\":\"cpty_sb_0a1b2c3d4e5f6071\",\"counterpartyCountry\":\"US\",\"returnCode\":\"R10\",\"columnCreatedAt\":\"2026-09-17T15:04:05.000Z\"},\"skoor\":{\"value\":null,\"band\":\"unscored\",\"n\":0,\"version\":\"trs-v1\",\"confidence\":null},\"alertIds\":[],\"alerts\":[],\"labels\":[{\"label\":\"bad\",\"source\":\"outcome:return\",\"detail\":\"R10:unauthorized\"}]}}'"}},"/api/sandbox/transfers/{id}/dispute":{"post":{"tags":["Sandbox"],"summary":"Open a cardholder dispute on a card transaction.","operationId":"postApiSandboxTransfersByIdDispute","description":"Replays the stored card transaction as `card.transaction.dispute` (`event_type: dispute`). The transfer row moves to status DISPUTE and is re-scored. Only card transactions can be disputed.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"id","required":true,"schema":{"type":"string"},"example":"acht_sandbox_0001"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["fraud","not_received","duplicate","other"],"default":"fraud"},"at":{"type":"string","format":"date-time"}}},"example":{"request":{"reason":"fraud"},"response":{"program":"northwind-payroll","events":["evnt_sb_m1a2b3c4_1c2d3e4f5"],"transferId":"ctxn_sb_5d6e7f8091a2b3c4","transfer":{"id":"ctxn_sb_5d6e7f8091a2b3c4","rail":"card","direction":"outgoing","type":null,"amountCents":"250000","currency":"USD","status":"DISPUTE","entityId":"enti_sb_1a2b3c4d5e6f7081","counterpartyId":null,"counterpartyCountry":"US","returnCode":null,"columnCreatedAt":"2026-09-17T15:04:05.000Z"},"skoor":{"value":null,"band":"unscored","n":0,"version":"trs-v1","confidence":null},"alertIds":[],"alerts":[],"labels":[]}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","events","transferId","transfer","skoor","alertIds","alerts","labels"],"properties":{"program":{"type":"string","description":"Program slug."},"events":{"type":"array","items":{"type":"string"},"description":"Ids of the Column-shaped events stored, in order."},"transferId":{"type":"string"},"transfer":{"$ref":"#/components/schemas/SandboxTransfer"},"skoor":{"$ref":"#/components/schemas/Skoor"},"alertIds":{"type":"array","items":{"type":"string","format":"uuid"}},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/SandboxAlert"}},"labels":{"type":"array","items":{"$ref":"#/components/schemas/SandboxLabel"}}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/transfers/acht_sandbox_0001/dispute?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"reason\":\"fraud\"},\"response\":{\"program\":\"northwind-payroll\",\"events\":[\"evnt_sb_m1a2b3c4_1c2d3e4f5\"],\"transferId\":\"ctxn_sb_5d6e7f8091a2b3c4\",\"transfer\":{\"id\":\"ctxn_sb_5d6e7f8091a2b3c4\",\"rail\":\"card\",\"direction\":\"outgoing\",\"type\":null,\"amountCents\":\"250000\",\"currency\":\"USD\",\"status\":\"DISPUTE\",\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"counterpartyId\":null,\"counterpartyCountry\":\"US\",\"returnCode\":null,\"columnCreatedAt\":\"2026-09-17T15:04:05.000Z\"},\"skoor\":{\"value\":null,\"band\":\"unscored\",\"n\":0,\"version\":\"trs-v1\",\"confidence\":null},\"alertIds\":[],\"alerts\":[],\"labels\":[]}}'"}},"/api/sandbox/entities":{"post":{"tags":["Sandbox"],"summary":"Onboard an entity through identity.verification events.","operationId":"postApiSandboxEntities","description":"Emits `identity.verification.created` (UNVERIFIED), `.pending`, then the final status event, seconds apart, and runs the entity Skoor. `agent` marks the entity as an AI agent acting under an operator.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","name"],"properties":{"programId":{"type":"string","format":"uuid","description":"The simulated program. One of programId / programSlug is required."},"programSlug":{"type":"string","description":"The simulated program by slug."},"type":{"type":"string","enum":["PERSON","BUSINESS"]},"name":{"type":"string","maxLength":120},"verificationStatus":{"type":"string","enum":["UNVERIFIED","PENDING","VERIFIED","MANUAL_REVIEW","DENIED"],"default":"VERIFIED"},"pepStatus":{"type":"string","enum":["not_checked","no","yes","potential"],"default":"no"},"isHighRisk":{"type":"boolean","default":false},"requiresScreening":{"type":"boolean","default":false},"country":{"type":"string","pattern":"^[A-Z]{2}$","default":"US"},"expectedPaymentRails":{"type":"array","items":{"type":"string"},"default":["ach"]},"agent":{"type":"object","required":["purpose"],"properties":{"operatorEntityId":{"type":"string"},"purpose":{"type":"string"},"scope":{"type":"array","items":{"type":"string"},"default":["ach"]}}},"at":{"type":"string","format":"date-time","description":"Entity created_at. Defaults to now."}}},"example":{"request":{"programSlug":"northwind-payroll","type":"PERSON","name":"Dana Whitfield","verificationStatus":"VERIFIED"},"response":{"program":"northwind-payroll","events":["evnt_sb_m1a2b3c4_a1b2c3d46","evnt_sb_m1a2b3c4_e5f6a7b87","evnt_sb_m1a2b3c4_c9d0e1f28"],"entityId":"enti_sb_1a2b3c4d5e6f7081","entity":{"id":"enti_sb_1a2b3c4d5e6f7081","type":"PERSON","name":"Dana Whitfield","verificationStatus":"VERIFIED","isHighRisk":false,"pepStatus":"no","countryCode":"US"}}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","events","entityId","entity"],"properties":{"program":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"entityId":{"type":"string"},"entity":{"$ref":"#/components/schemas/SandboxEntity"}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/entities?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"programSlug\":\"northwind-payroll\",\"type\":\"PERSON\",\"name\":\"Dana Whitfield\",\"verificationStatus\":\"VERIFIED\"},\"response\":{\"program\":\"northwind-payroll\",\"events\":[\"evnt_sb_m1a2b3c4_a1b2c3d46\",\"evnt_sb_m1a2b3c4_e5f6a7b87\",\"evnt_sb_m1a2b3c4_c9d0e1f28\"],\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"entity\":{\"id\":\"enti_sb_1a2b3c4d5e6f7081\",\"type\":\"PERSON\",\"name\":\"Dana Whitfield\",\"verificationStatus\":\"VERIFIED\",\"isHighRisk\":false,\"pepStatus\":\"no\",\"countryCode\":\"US\"}}}'"}},"/api/sandbox/entities/{id}/verification":{"post":{"tags":["Sandbox"],"summary":"Change an entity’s verification decision.","operationId":"postApiSandboxEntitiesByIdVerification","description":"Emits one `identity.verification.<status>` event for an entity already on the program (DENIED adds `sanctions_match`, MANUAL_REVIEW adds `name_mismatch` to review_reasons, as the simulator does).\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"id","in":"path","description":"id","required":true,"schema":{"type":"string"},"example":"enti_sandbox_0001"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"programId":{"type":"string","format":"uuid","description":"The simulated program. One of programId / programSlug is required."},"programSlug":{"type":"string","description":"The simulated program by slug."},"status":{"type":"string","enum":["VERIFIED","MANUAL_REVIEW","DENIED"]},"at":{"type":"string","format":"date-time"}}},"example":{"request":{"programSlug":"northwind-payroll","status":"DENIED"},"response":{"program":"northwind-payroll","events":["evnt_sb_m1a2b3c4_0a1b2c3d9"],"entityId":"enti_sb_1a2b3c4d5e6f7081","entity":{"id":"enti_sb_1a2b3c4d5e6f7081","type":"PERSON","name":"Dana Whitfield","verificationStatus":"DENIED","isHighRisk":false,"pepStatus":"no","countryCode":"US"}}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","events","entityId","entity"],"properties":{"program":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"entityId":{"type":"string"},"entity":{"$ref":"#/components/schemas/SandboxEntity"}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/entities/enti_sandbox_0001/verification?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"programSlug\":\"northwind-payroll\",\"status\":\"DENIED\"},\"response\":{\"program\":\"northwind-payroll\",\"events\":[\"evnt_sb_m1a2b3c4_0a1b2c3d9\"],\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"entity\":{\"id\":\"enti_sb_1a2b3c4d5e6f7081\",\"type\":\"PERSON\",\"name\":\"Dana Whitfield\",\"verificationStatus\":\"DENIED\",\"isHighRisk\":false,\"pepStatus\":\"no\",\"countryCode\":\"US\"}}}'"}},"/api/sandbox/counterparties":{"post":{"tags":["Sandbox"],"summary":"Register a counterparty for later transfers.","operationId":"postApiSandboxCounterparties","description":"Column has no counterparty webhook (counterparties arrive embedded in transfer events), so this writes the row directly with a Column-shaped counterparty object in `raw`. Use the returned id as `counterpartyId` on transfers.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"programId":{"type":"string","format":"uuid","description":"The simulated program. One of programId / programSlug is required."},"programSlug":{"type":"string","description":"The simulated program by slug."},"name":{"type":"string","maxLength":120},"country":{"type":"string","pattern":"^[A-Z]{2}$","default":"US"},"routingNumber":{"type":"string","pattern":"^\\d{9}$"},"at":{"type":"string","format":"date-time"}}},"example":{"request":{"programSlug":"northwind-payroll","name":"Blue Ridge Landscaping","country":"US","routingNumber":"021000021"},"response":{"program":"northwind-payroll","counterpartyId":"cpty_sb_0a1b2c3d4e5f6071","counterparty":{"id":"cpty_sb_0a1b2c3d4e5f6071","name":"Blue Ridge Landscaping","countryCode":"US","routingNumber":"021000021"}}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","counterpartyId","counterparty"],"properties":{"program":{"type":"string"},"counterpartyId":{"type":"string"},"counterparty":{"$ref":"#/components/schemas/SandboxCounterparty"}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/counterparties?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"programSlug\":\"northwind-payroll\",\"name\":\"Blue Ridge Landscaping\",\"country\":\"US\",\"routingNumber\":\"021000021\"},\"response\":{\"program\":\"northwind-payroll\",\"counterpartyId\":\"cpty_sb_0a1b2c3d4e5f6071\",\"counterparty\":{\"id\":\"cpty_sb_0a1b2c3d4e5f6071\",\"name\":\"Blue Ridge Landscaping\",\"countryCode\":\"US\",\"routingNumber\":\"021000021\"}}}'"}},"/api/sandbox/patterns/{name}":{"post":{"tags":["Sandbox"],"summary":"Play a typology the detectors know and return the alerts it opened.","operationId":"postApiSandboxPatternsByName","description":"Patterns: `structuring` → structuring_pattern (Four ACH debits of $9,100–$9,900 from one entity inside 24 hours (5 hours apart). Hard signal: the third and fourth are held.); `just_below_threshold` → amount_just_below_threshold (A single $9,500 ACH debit with no other sub-threshold debit in 24 hours.); `velocity` → velocity_spike (Seven ACH credits from one entity inside two hours; the sixth onward exceeds five in 24 hours.); `round_trip` → round_trip (An outgoing ACH to a counterparty three days ago, then the same amount back from it today.); `layering` → layering_chain (Three similar-amount hops inside 48 hours: entity A → counterparty X, X → the entity, the entity → counterparty Y.); `dormant_reactivation` → dormant_reactivation (One transfer 75 days ago, silence, then three transfers inside 24 hours.); `burst_then_dormant` → burst_then_dormant (Six transfers on one day 20 days ago and nothing since; a KRI snapshot is taken so the snapshot-time detector runs.). Without `entityId`/`counterpartyId` the play creates its own actors. `at` is the play's end time (default now); history is placed relative to it.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"name","in":"path","description":"name","required":true,"schema":{"type":"string"},"example":"structuring"},{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"programId":{"type":"string","format":"uuid","description":"The simulated program. One of programId / programSlug is required."},"programSlug":{"type":"string","description":"The simulated program by slug."},"entityId":{"type":"string"},"counterpartyId":{"type":"string"},"at":{"type":"string","format":"date-time"}}},"example":{"request":{"programSlug":"northwind-payroll"},"response":{"program":"northwind-payroll","pattern":"structuring","expectedDetector":"structuring_pattern","events":["evnt_sb_m1a2b3c4_0000000a1","evnt_sb_m1a2b3c4_0000000a2"],"transferIds":["acht_sb_01","acht_sb_02","acht_sb_03","acht_sb_04"],"entityId":"enti_sb_1a2b3c4d5e6f7081","entityIds":["enti_sb_1a2b3c4d5e6f7081"],"counterpartyIds":["cpty_sb_0a1b2c3d4e5f6071"],"alertIds":["0d4c7b6e-2f1a-4e5b-9c8d-7a6b5c4d3e2f"],"alerts":[{"id":"0d4c7b6e-2f1a-4e5b-9c8d-7a6b5c4d3e2f","detector":"structuring_pattern","severity":"high","subjectType":"entity","subjectId":"enti_sb_1a2b3c4d5e6f7081","status":"open","route":"reviewed"}]}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","pattern","expectedDetector","events","transferIds","entityId","entityIds","counterpartyIds","alertIds","alerts"],"properties":{"program":{"type":"string"},"pattern":{"type":"string","enum":["structuring","just_below_threshold","velocity","round_trip","layering","dormant_reactivation","burst_then_dormant"]},"expectedDetector":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"transferIds":{"type":"array","items":{"type":"string"}},"entityId":{"type":"string"},"entityIds":{"type":"array","items":{"type":"string"},"description":"Entities the play created."},"counterpartyIds":{"type":"array","items":{"type":"string"}},"alertIds":{"type":"array","items":{"type":"string","format":"uuid"}},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/SandboxAlert"}},"snapshotId":{"type":"string","format":"uuid","description":"burst_then_dormant only: the KRI snapshot taken."}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/patterns/structuring?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"programSlug\":\"northwind-payroll\"},\"response\":{\"program\":\"northwind-payroll\",\"pattern\":\"structuring\",\"expectedDetector\":\"structuring_pattern\",\"events\":[\"evnt_sb_m1a2b3c4_0000000a1\",\"evnt_sb_m1a2b3c4_0000000a2\"],\"transferIds\":[\"acht_sb_01\",\"acht_sb_02\",\"acht_sb_03\",\"acht_sb_04\"],\"entityId\":\"enti_sb_1a2b3c4d5e6f7081\",\"entityIds\":[\"enti_sb_1a2b3c4d5e6f7081\"],\"counterpartyIds\":[\"cpty_sb_0a1b2c3d4e5f6071\"],\"alertIds\":[\"0d4c7b6e-2f1a-4e5b-9c8d-7a6b5c4d3e2f\"],\"alerts\":[{\"id\":\"0d4c7b6e-2f1a-4e5b-9c8d-7a6b5c4d3e2f\",\"detector\":\"structuring_pattern\",\"severity\":\"high\",\"subjectType\":\"entity\",\"subjectId\":\"enti_sb_1a2b3c4d5e6f7081\",\"status\":\"open\",\"route\":\"reviewed\"}]}}'"}},"/api/sandbox/reset":{"post":{"tags":["Sandbox"],"summary":"Delete a simulated program’s data rows.","operationId":"postApiSandboxReset","description":"Removes labels, dispositions, actions, alerts, actor Skoors, pre-flights, KRI snapshots, events, transfers, counterparties, accounts and entities of the program, in dependency order. Configuration (policies, keys, webhook endpoints) stays. Refused on a Column-sourced program.\n\nAuth: Bearer operator token or API key, plus `X-Operator` naming the person (400 without it).","parameters":[{"name":"tenant","in":"query","description":"Tenant slug. Optional: the bearer token already proves the tenant. Pass it to be explicit.","required":false,"schema":{"type":"string"},"example":"demo"},{"name":"Idempotency-Key","in":"header","description":"1–255 characters. A replay with the same body returns the stored status and body with `Idempotent-Replayed: true`; a different body under the same key is 409.","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"programId":{"type":"string","format":"uuid","description":"The simulated program. One of programId / programSlug is required."},"programSlug":{"type":"string","description":"The simulated program by slug."}}},"example":{"request":{"programSlug":"northwind-payroll"},"response":{"program":"northwind-payroll","reset":true,"counts":{"labels":1,"dispositions":0,"actions":0,"alerts":2,"actor_skoors":3,"preflights":0,"kri_snapshots":0,"events":12,"transfers":4,"counterparties":1,"accounts":0,"entities":1}}}}}},"responses":{"200":{"description":"What the pipeline produced.","content":{"application/json":{"schema":{"type":"object","required":["program","reset","counts"],"properties":{"program":{"type":"string"},"reset":{"type":"boolean"},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}}}}},"401":{"description":"Bearer token missing or unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a test principal, or the program is Column-sourced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[],"operator":[]}],"x-curl":"curl -s -X POST \"https://skoor.ai/risk/api/sandbox/reset?tenant=demo\" \\\n  -H \"Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" \\\n  -H \"X-Operator: you@bank.example\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":{\"programSlug\":\"northwind-payroll\"},\"response\":{\"program\":\"northwind-payroll\",\"reset\":true,\"counts\":{\"labels\":1,\"dispositions\":0,\"actions\":0,\"alerts\":2,\"actor_skoors\":3,\"preflights\":0,\"kri_snapshots\":0,\"events\":12,\"transfers\":4,\"counterparties\":1,\"accounts\":0,\"entities\":1}}}'"}}},"components":{"schemas":{"Skoor":{"type":["object","null"],"description":"The Transaction Risk Skoor as persisted. null when the scorer never ran; value null with n when history is too thin to score.","required":["value","band","n","version","confidence"],"properties":{"value":{"type":["integer","null"],"minimum":0,"maximum":100},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"n":{"type":"integer","minimum":0,"description":"Settled transfers the program had in the 90 days before this one."},"version":{"type":"string"},"confidence":{"type":["number","null"]}}},"SandboxAlert":{"type":"object","required":["id","detector","severity","subjectType","subjectId","status","route"],"properties":{"id":{"type":"string","format":"uuid"},"detector":{"type":"string"},"severity":{"type":"string","enum":["low","medium","high"]},"subjectType":{"type":"string"},"subjectId":{"type":"string"},"status":{"type":"string"},"route":{"type":["string","null"],"enum":["automated","reviewed",null]}}},"SandboxTransfer":{"type":"object","required":["id","rail","direction","type","amountCents","currency","status","entityId","counterpartyId","counterpartyCountry","returnCode","columnCreatedAt"],"properties":{"id":{"type":"string"},"rail":{"type":"string"},"direction":{"type":"string","enum":["incoming","outgoing"]},"type":{"type":["string","null"]},"amountCents":{"type":"string","pattern":"^\\d+$","description":"Integer cents as a decimal string."},"currency":{"type":"string"},"status":{"type":"string","description":"Column's status, upper-cased."},"entityId":{"type":["string","null"]},"counterpartyId":{"type":["string","null"]},"counterpartyCountry":{"type":["string","null"]},"returnCode":{"type":["string","null"]},"columnCreatedAt":{"type":"string","format":"date-time"}}},"SandboxLabel":{"type":"object","required":["label","source","detail"],"properties":{"label":{"type":"string","enum":["bad","ok","returned"]},"source":{"type":"string"},"detail":{"type":["string","null"]}}},"SandboxEntity":{"type":"object","required":["id","type","name","verificationStatus","isHighRisk","pepStatus","countryCode"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["PERSON","BUSINESS"]},"name":{"type":"string"},"verificationStatus":{"type":"string"},"isHighRisk":{"type":"boolean"},"pepStatus":{"type":"string"},"countryCode":{"type":["string","null"]}}},"SandboxCounterparty":{"type":"object","required":["id","name","countryCode","routingNumber"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"countryCode":{"type":["string","null"]},"routingNumber":{"type":["string","null"]}}},"Refusal":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in one sentence."},"requestId":{"type":"string","description":"Present once the gateway is live: the X-Request-Id of the failed request."}},"required":["error"],"additionalProperties":false,"title":"Error"},"Program":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid","description":"The bank this program banks on."},"slug":{"type":"string","description":"Stable, URL-safe name; unique per tenant."},"name":{"type":"string"},"webhookEndpointId":{"type":["string","null"],"description":"Column `Webhook-Endpoint-Id` header that identifies the platform delivering events."},"declaredMonthlyVolumeCents":{"type":["string","null"],"pattern":"^-?\\d+$","description":"What the program declared at onboarding; the Skoor reads it as expected activity. Integer cents as a string, or null."},"declaredRails":{"type":"array","items":{"type":"string"},"description":"Rails declared at onboarding."},"declaredCountries":{"type":"array","items":{"type":"string"},"description":"Countries declared at onboarding (default [\"US\"])."},"programReserveAccountId":{"type":["string","null"],"description":"Reserve account whose balance backs the reserve-coverage KRI."},"source":{"type":"string","enum":["column","simulated"],"description":"Where the program's events come from. Simulated programs are labelled everywhere."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","slug","name","webhookEndpointId","declaredMonthlyVolumeCents","declaredRails","declaredCountries","programReserveAccountId","source","createdAt"],"additionalProperties":false,"title":"Program"},"Transfer":{"type":"object","properties":{"id":{"type":"string","description":"Column transfer id, any rail."},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"rail":{"type":"string","enum":["ach","wire","realtime","book","intl_wire","check","card"]},"direction":{"type":"string","enum":["incoming","outgoing"]},"type":{"type":["string","null"],"enum":["CREDIT","DEBIT",null],"description":"ACH type, null on other rails."},"amountCents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."},"currency":{"type":"string","description":"ISO 4217, default USD."},"status":{"type":"string","description":"Column's status enum, upper-cased."},"entityId":{"type":["string","null"]},"accountId":{"type":["string","null"]},"counterpartyId":{"type":["string","null"]},"counterpartyCountry":{"type":["string","null"],"description":"ISO 3166-1 alpha-2."},"secCode":{"type":["string","null"],"description":"ACH SEC code."},"returnCode":{"type":["string","null"],"description":"ACH return code when returned (R01…)."},"returnedAt":{"type":["string","null"],"format":"date-time"},"effectiveAt":{"type":["string","null"],"format":"date-time"},"columnCreatedAt":{"type":"string","format":"date-time","description":"When the bank created it."},"skoor":{"type":["integer","null"],"description":"Transaction Risk Skoor 0–100; null until scored or when history is too thin.","minimum":0,"maximum":100},"skoorBand":{"type":["string","null"],"enum":["clear","review","hold","unscored",null]},"skoorVersion":{"type":["string","null"],"description":"Scorer version, e.g. trs-v1."},"skoorN":{"type":["integer","null"],"description":"Observations behind the baselines."},"skoorConfidence":{"type":["number","null"],"description":"0–1; null when the Skoor is null."},"skoorInputs":{"oneOf":[{"type":"object","additionalProperties":true,"description":"Features, signals, hardSignal and reasons the scorer used."},{"type":"null"}]},"skooredAt":{"type":["string","null"],"format":"date-time"},"raw":{"type":"object","additionalProperties":true,"description":"The bank's own object as last delivered."},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","programId","rail","direction","type","amountCents","currency","status","entityId","accountId","counterpartyId","counterpartyCountry","secCode","returnCode","returnedAt","effectiveAt","columnCreatedAt","skoor","skoorBand","skoorVersion","skoorN","skoorConfidence","skoorInputs","skooredAt","raw","updatedAt"],"additionalProperties":false,"title":"Transfer"},"Entity":{"type":"object","properties":{"id":{"type":"string","description":"Column entity id."},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["PERSON","BUSINESS"]},"name":{"type":"string"},"verificationStatus":{"type":"string","enum":["UNVERIFIED","PENDING","MANUAL_REVIEW","VERIFIED","DENIED"]},"isHighRisk":{"type":"boolean"},"pepStatus":{"type":"string","enum":["not_checked","no","yes","potential"]},"requiresScreening":{"type":"boolean"},"reviewReasons":{"type":"array","items":{"type":"string"}},"lastScreenedAt":{"type":["string","null"],"format":"date-time"},"countryCode":{"type":["string","null"]},"legalType":{"type":["string","null"]},"expectedPaymentRails":{"type":"array","items":{"type":"string"}},"raw":{"type":"object","additionalProperties":true,"description":"The bank's own object as last delivered."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","programId","type","name","verificationStatus","isHighRisk","pepStatus","requiresScreening","reviewReasons","lastScreenedAt","countryCode","legalType","expectedPaymentRails","raw","createdAt","updatedAt"],"additionalProperties":false,"title":"Entity"},"Counterparty":{"type":"object","properties":{"id":{"type":"string","description":"Column counterparty id."},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"routingNumber":{"type":["string","null"]},"accountLast4":{"type":["string","null"]},"countryCode":{"type":["string","null"]},"raw":{"type":"object","additionalProperties":true,"description":"The bank's own object as last delivered."},"firstSeenAt":{"type":"string","format":"date-time","description":"First transfer with this counterparty on the program."}},"required":["id","tenantId","programId","name","routingNumber","accountLast4","countryCode","raw","firstSeenAt"],"additionalProperties":false,"title":"Counterparty"},"Agent":{"type":"object","properties":{"entityId":{"type":"string","description":"The entity row the agent is stored as (program `agents`)."},"name":{"type":"string"},"chainId":{"type":["integer","null"]},"agentId":{"type":["string","null"],"description":"On-chain agent id from the scan index."},"address":{"type":["string","null"],"description":"Wallet address."},"verificationStatus":{"type":"string"},"screeningStatus":{"type":["string","null"],"description":"Latest compliance-profile screening status, or null when never screened."},"lastScreenedAt":{"type":["string","null"],"format":"date-time"},"settlements":{"type":"integer","description":"Transfers imported for this agent."},"volumeCents":{"type":"string","pattern":"^-?\\d+$","description":"Sum of imported transfers. Integer cents as a string (bigint-safe)."},"skoor":{"type":["integer","null"],"description":"Agent Risk Skoor (ars-v1); null until history is enough."},"bandName":{"type":"string","enum":["clear","review","hold","unscored"]},"n":{"type":"integer"},"confidence":{"type":["number","null"]},"version":{"type":["string","null"]}},"required":["entityId","name","chainId","agentId","address","verificationStatus","screeningStatus","lastScreenedAt","settlements","volumeCents","skoor","bandName","n","confidence","version"],"additionalProperties":false,"title":"Agent"},"Alert":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"detector":{"type":"string","description":"Which detector opened it, e.g. structuring_pattern, or rule:<id>."},"severity":{"type":"string","enum":["low","medium","high"]},"subjectType":{"type":"string","enum":["transfer","entity","program","counterparty","account"]},"subjectId":{"type":"string"},"transferId":{"type":["string","null"]},"summary":{"type":"string"},"evidence":{"type":"object","additionalProperties":true,"description":"What the detector saw; carries routeReason, autoHold and escalation when they apply."},"skoorAtAlert":{"type":["integer","null"]},"bandAtAlert":{"type":["string","null"],"enum":["clear","review","hold","unscored",null]},"hardSignal":{"type":"boolean","description":"A hard signal always routes to a person."},"status":{"type":"string","enum":["open","auto_closed","closed","held","escalated"]},"route":{"type":["string","null"],"enum":["automated","reviewed",null],"description":"Decided by the policy when opened."},"policyVersion":{"type":["string","null"]},"openedAt":{"type":"string","format":"date-time"},"closedAt":{"type":["string","null"],"format":"date-time"},"dedupeKey":{"type":["string","null"]},"dueAt":{"type":["string","null"],"format":"date-time","description":"Decision clock: when a person must have decided by."},"escalatedAt":{"type":["string","null"],"format":"date-time","description":"Stamped once when the clock ran out."},"slaTier":{"type":["string","null"],"enum":["auto","standard","complex","appeal",null]}},"required":["id","tenantId","programId","detector","severity","subjectType","subjectId","transferId","summary","evidence","skoorAtAlert","bandAtAlert","hardSignal","status","route","policyVersion","openedAt","closedAt","dedupeKey","dueAt","escalatedAt","slaTier"],"additionalProperties":false,"title":"Alert"},"OverdueAlert":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"detector":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"route":{"type":["string","null"]},"slaTier":{"type":["string","null"]},"openedAt":{"type":"string","format":"date-time"},"dueAt":{"type":["string","null"],"format":"date-time"},"escalatedAt":{"type":["string","null"],"format":"date-time"},"summary":{"type":"string"}},"required":["id","programId","detector","severity","status","route","slaTier","openedAt","dueAt","escalatedAt","summary"],"additionalProperties":false,"title":"OverdueAlert"},"Disposition":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"alertId":{"type":"string","format":"uuid"},"decidedBy":{"type":"string","description":"`policy:<version>` when the platform decided alone; `operator:<name>` when a person signed."},"decision":{"type":"string","enum":["close","hold","escalate","release","no_action"]},"narrative":{"type":"string","description":"The examiner-shaped narrative that was signed."},"narrativeModel":{"type":["string","null"],"description":"Model alias that drafted it; null when template or human-written."},"recommendation":{"type":["string","null"],"description":"What the draft recommended."},"confidence":{"type":["number","null"],"description":"Draft confidence; never present on a template."},"policyVersion":{"type":"string"},"overturnsDraft":{"type":["boolean","null"],"description":"True when the person disagreed with the draft."},"prevHash":{"type":["string","null"],"description":"Hash of the previous disposition on the tenant chain."},"hash":{"type":"string","description":"sha256(prevHash | canonical(body))."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","alertId","decidedBy","decision","narrative","narrativeModel","recommendation","confidence","policyVersion","overturnsDraft","prevHash","hash","createdAt"],"additionalProperties":false,"title":"Disposition"},"Action":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"alertId":{"type":["string","null"],"format":"uuid"},"type":{"type":"string","enum":["clear_hold","cancel_transfer","pause_card","suspend_card_account","freeze_account","request_to_program","periodic_review"]},"targetType":{"type":"string","description":"What the action acts on (transfer, account, card, program…)."},"targetId":{"type":"string"},"requestedBy":{"type":"string","description":"The X-Operator who requested it."},"approvedBy":{"type":["string","null"],"description":"Second, distinct approver for freeze_account, request_to_program, suspend_card_account."},"status":{"type":"string","enum":["requested","approved","executed","failed","refused"]},"columnResponse":{"oneOf":[{"type":"object","additionalProperties":true,"description":"What the bank's API answered, or the dry-run note."},{"type":"null"}]},"error":{"type":["string","null"]},"executedAt":{"type":["string","null"],"format":"date-time"},"prevHash":{"type":["string","null"]},"hash":{"type":"string","description":"Chained at insert."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","programId","alertId","type","targetType","targetId","requestedBy","approvedBy","status","columnResponse","error","executedAt","prevHash","hash","createdAt"],"additionalProperties":false,"title":"Action"},"Reason":{"type":"object","properties":{"id":{"type":"string","description":"Stable reason id, e.g. TRS-ENT-01."},"signal":{"type":"string","description":"The signal code the scorer emitted."},"weight":{"type":"number","description":"Points added."},"hard":{"type":"boolean"},"sentence":{"type":"string","description":"What a compliance officer reads."},"detail":{"type":"string"}},"required":["id","signal","weight","hard","sentence"],"additionalProperties":false,"title":"Reason"},"LimitHit":{"type":"object","properties":{"limit":{"type":"string","description":"`<actor>.<kind>`: entity|counterparty × per_transaction|daily|monthly."},"actor":{"type":"string","enum":["counterparty","entity"]},"actorId":{"type":"string"},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"cap":{"type":"string","pattern":"^-?\\d+$","description":"The cap at that band. Integer cents as a string (bigint-safe)."},"observed":{"type":"string","pattern":"^-?\\d+$","description":"Already moved in the window (0 for per_transaction). Integer cents as a string (bigint-safe)."},"proposed":{"type":"string","pattern":"^-?\\d+$","description":"This amount. Integer cents as a string (bigint-safe)."},"severity":{"type":"string","enum":["hard","soft"]}},"required":["limit","actor","actorId","band","cap","observed","proposed","severity"],"additionalProperties":false,"title":"LimitHit"},"PreflightRequest":{"type":"object","properties":{"programId":{"type":"string","format":"uuid","description":"One of programId or programSlug is required."},"programSlug":{"type":"string"},"idempotencyKey":{"type":"string","description":"Same key on the same tenant returns the same answer (200 instead of 201).","minLength":1,"maxLength":200},"transfer":{"type":"object","properties":{"rail":{"type":"string","enum":["ach","wire","realtime","book","intl_wire","check","card"]},"direction":{"type":"string","enum":["outgoing","incoming"]},"type":{"type":["string","null"],"enum":["CREDIT","DEBIT",null]},"amountCents":{"type":"string","description":"Integer cents as a string.","pattern":"^\\d+$"},"currency":{"type":"string","description":"Default USD.","minLength":3,"maxLength":3},"entityId":{"type":"string"},"counterpartyId":{"type":"string"},"counterparty":{"type":"object","properties":{"name":{"type":"string"},"routingNumber":{"type":"string"},"accountLast4":{"type":"string","minLength":4,"maxLength":4},"countryCode":{"type":"string","minLength":2,"maxLength":2}},"required":["name"],"additionalProperties":false,"description":"Describe a counterparty not on file; matched by routing + last4, else by name, else scored as a first-time stranger."},"effectiveOn":{"type":"string"},"description":{"type":"string","maxLength":255}},"required":["rail","direction","amountCents"],"additionalProperties":false}},"required":["transfer"],"additionalProperties":false,"title":"PreflightRequest"},"Preflight":{"type":"object","properties":{"preflightId":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["clear","review","hold"],"description":"hold = hold band, hard signal or hard limit hit; review = review band, unscored or any soft limit hit; clear otherwise."},"skoor":{"type":["integer","null"]},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"version":{"type":"string"},"n":{"type":"integer"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"}},"limitsHit":{"type":"array","items":{"$ref":"#/components/schemas/LimitHit"}},"degraded":{"type":"boolean","description":"True when a feature source the answer leans on is missing or stale."},"degradedReasons":{"type":"array","items":{"type":"string"},"description":"Why it is degraded (empty on a replayed answer)."},"latencyMs":{"type":"integer"},"idempotent":{"type":"boolean","description":"True when the answer was replayed from the idempotency key."},"programId":{"type":"string","format":"uuid"}},"required":["preflightId","decision","skoor","band","version","n","confidence","reasons","limitsHit","degraded","degradedReasons","latencyMs","idempotent","programId"],"additionalProperties":false,"title":"Preflight"},"PreflightDetail":{"type":"object","properties":{"preflightId":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["clear","review","hold"]},"skoor":{"type":["integer","null"]},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"version":{"type":"string"},"n":{"type":"integer"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"}},"limitsHit":{"type":"array","items":{"$ref":"#/components/schemas/LimitHit"}},"degraded":{"type":"boolean"},"degradedReasons":{"type":"array","items":{"type":"string"}},"latencyMs":{"type":"integer"},"idempotent":{"type":"boolean"},"programId":{"type":"string","format":"uuid"},"request":{"type":"object","additionalProperties":true,"description":"The request as submitted plus `operator` and `resolved` {entityId, counterpartyId, counterpartyCountry}."},"matchedTransferId":{"type":["string","null"],"description":"Set when the transfer later landed and was matched back (same program, counterparty, amount, within 24h)."},"createdAt":{"type":"string","format":"date-time"}},"required":["preflightId","decision","skoor","band","version","n","confidence","reasons","limitsHit","degraded","degradedReasons","latencyMs","idempotent","programId","request","matchedTransferId","createdAt"],"additionalProperties":false,"title":"PreflightDetail"},"PreflightStats":{"type":"object","properties":{"windowDays":{"type":"integer"},"count":{"type":"integer"},"byDecision":{"type":"object","properties":{"clear":{"type":"integer"},"review":{"type":"integer"},"hold":{"type":"integer"}},"required":["clear","review","hold"],"additionalProperties":false},"latencyMs":{"type":"object","properties":{"p50":{"type":["number","null"]},"p90":{"type":["number","null"]},"n":{"type":"integer"}},"required":["p50","p90","n"],"additionalProperties":false},"matched":{"type":"object","properties":{"value":{"type":["number","null"],"description":"null when nothing was measured"},"n":{"type":"integer","description":"observations behind the value"}},"required":["value","n"],"additionalProperties":false,"description":"Share of pre-flights matched to a landed transfer."},"degraded":{"type":"object","properties":{"value":{"type":["number","null"],"description":"null when nothing was measured"},"n":{"type":"integer","description":"observations behind the value"}},"required":["value","n"],"additionalProperties":false,"description":"Share of degraded answers."},"graded":{"type":"object","properties":{"n":{"type":"integer"},"holdAndBad":{"type":"integer"},"holdAndOk":{"type":"integer"},"notHoldAndBad":{"type":"integer"},"notHoldAndOk":{"type":"integer"},"holdPrecision":{"type":["number","null"]},"holdRecall":{"type":["number","null"]}},"required":["n","holdAndBad","holdAndOk","notHoldAndBad","notHoldAndOk","holdPrecision","holdRecall"],"additionalProperties":false,"description":"Matched pre-flights whose transfer later received a bad/ok label."}},"required":["windowDays","count","byDecision","latencyMs","matched","degraded","graded"],"additionalProperties":false,"title":"PreflightStats"},"KriValue":{"type":"object","properties":{"value":{"type":["number","null"],"description":"null = not measured."},"n":{"type":"integer"},"unit":{"type":"string","enum":["ratio","count","cents","days","hours"]},"threshold":{"type":["number","null"]},"status":{"type":"string","enum":["ok","watch","breach","unmeasured"]},"note":{"type":"string"}},"required":["value","n","unit","status"],"additionalProperties":false,"title":"KriValue"},"KriSnapshot":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":"string","format":"uuid"},"asOf":{"type":"string","format":"date-time"},"windowDays":{"type":"integer"},"kris":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KriValue"},"description":"One KriValue per KRI name (ach_overall_return_rate, reserve_coverage, …)."},"programSkoor":{"type":["integer","null"],"description":"Program Risk Skoor (prs-v1); null until at least three KRIs have n > 0."},"programSkoorN":{"type":["integer","null"]},"programSkoorVersion":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","programId","asOf","windowDays","kris","programSkoor","programSkoorN","programSkoorVersion","createdAt"],"additionalProperties":false,"title":"KriSnapshot"},"PolicyBody":{"type":"object","properties":{"version":{"type":"string"},"autoClosableDetectors":{"type":"array","items":{"type":"string"},"description":"Detectors the platform may close alone."},"autoCloseBands":{"type":"array","items":{"type":"string","enum":["clear","review","hold","unscored"]},"description":"v1: clear only."},"minProgramN":{"type":"integer","description":"History floor for any automated decision."},"alwaysReviewed":{"type":"array","items":{"type":"string"}},"twoApproverActions":{"type":"array","items":{"type":"string","enum":["clear_hold","cancel_transfer","pause_card","suspend_card_account","freeze_account","request_to_program","periodic_review"]}},"holdBandAutoHold":{"type":"boolean","description":"Hold band → held before anyone decides."}},"required":["version","autoClosableDetectors","autoCloseBands","minProgramN","alwaysReviewed","twoApproverActions","holdBandAutoHold"],"additionalProperties":false,"title":"PolicyBody"},"Policy":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"programId":{"type":["string","null"],"format":"uuid","description":"null = tenant-wide; set = a program's autonomy tier / limits."},"version":{"type":"string"},"body":{"type":"object","additionalProperties":true,"description":"PolicyBody, ClockPolicy, LimitLadder or a program autonomy body, by version prefix."},"active":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","tenantId","programId","version","body","active","createdAt"],"additionalProperties":false,"title":"Policy"},"ClockPolicy":{"type":"object","properties":{"autoHours":{"type":"number"},"standardHours":{"type":"number"},"complexHours":{"type":"number"},"appealHours":{"type":"number"}},"required":["autoHours","standardHours","complexHours","appealHours"],"additionalProperties":false,"description":"Hours a decision may wait, by SLA tier. Defaults 1 / 24 / 72 / 168.","title":"ClockPolicy"},"BandCaps":{"type":"object","properties":{"perTransactionCents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."},"dailyCents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."},"monthlyCents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."}},"required":["perTransactionCents","dailyCents","monthlyCents"],"additionalProperties":false,"title":"BandCaps"},"Ladder":{"type":"object","properties":{"clear":{"$ref":"#/components/schemas/BandCaps"},"review":{"$ref":"#/components/schemas/BandCaps"},"hold":{"$ref":"#/components/schemas/BandCaps"},"unscored":{"$ref":"#/components/schemas/BandCaps"}},"required":["clear","review","hold","unscored"],"additionalProperties":false,"title":"Ladder"},"LimitLadder":{"type":"object","properties":{"counterparty":{"$ref":"#/components/schemas/Ladder"},"entity":{"$ref":"#/components/schemas/Ladder"},"rescreenDays":{"type":"object","properties":{"clear":{"type":"integer"},"review":{"type":"integer"},"hold":{"type":"integer"},"unscored":{"type":"integer"}},"required":["clear","review","hold","unscored"],"additionalProperties":false,"description":"Re-screen cadence by band."}},"required":["counterparty","entity","rescreenDays"],"additionalProperties":false,"title":"LimitLadder"},"LimitEvaluation":{"type":"object","properties":{"hits":{"type":"array","items":{"$ref":"#/components/schemas/LimitHit"}},"hard":{"type":"boolean","description":"True when any hit is hard (an actor in the hold band)."},"bands":{"type":"object","properties":{"entity":{"type":["string","null"],"enum":["clear","review","hold","unscored",null]},"counterparty":{"type":["string","null"],"enum":["clear","review","hold","unscored",null]}},"required":["entity","counterparty"],"additionalProperties":false},"rescreen":{"type":"object","properties":{"entity":{"oneOf":[{"type":"object","properties":{"band":{"type":"string","enum":["clear","review","hold","unscored"]},"days":{"type":"integer"}},"required":["band","days"],"additionalProperties":false},{"type":"null"}]},"counterparty":{"oneOf":[{"type":"object","properties":{"band":{"type":"string","enum":["clear","review","hold","unscored"]},"days":{"type":"integer"}},"required":["band","days"],"additionalProperties":false},{"type":"null"}]}},"required":["entity","counterparty"],"additionalProperties":false},"ladder":{"$ref":"#/components/schemas/LimitLadder"}},"required":["hits","hard","bands","rescreen","ladder"],"additionalProperties":false,"title":"LimitEvaluation"},"DetectorEligibility":{"type":"object","properties":{"detector":{"type":"string"},"personDecisions":{"type":"integer","description":"n"},"matched":{"type":"integer","description":"Draft recommendation == person's decision."},"overturned":{"type":"integer"},"matchRate":{"type":["number","null"],"description":"null when n = 0."},"hardSignalClosedByPerson":{"type":"integer"},"grantedAlready":{"type":"boolean"},"baseline":{"type":"boolean","description":"Already auto-closable in policy v1."},"neverAutomate":{"type":"boolean"},"eligible":{"type":"boolean"},"reason":{"type":"string"}},"required":["detector","personDecisions","matched","overturned","matchRate","hardSignalClosedByPerson","grantedAlready","baseline","neverAutomate","eligible","reason"],"additionalProperties":false,"title":"DetectorEligibility"},"ProgramPolicyBody":{"type":"object","properties":{"tier":{"type":"string","enum":["supervised","standard","trusted"]},"autoClosableDetectors":{"type":"array","items":{"type":"string"}},"grantedAt":{"type":"string","format":"date-time"},"basis":{"type":"object","additionalProperties":true,"description":"What justified it, or the demotion."},"previousVersion":{"type":["string","null"]}},"required":["tier","autoClosableDetectors","grantedAt","basis","previousVersion"],"additionalProperties":false,"title":"ProgramPolicyBody"},"ProgramAutonomy":{"type":"object","properties":{"programId":{"type":"string","format":"uuid"},"tier":{"type":"string","enum":["supervised","standard","trusted"]},"version":{"type":["string","null"],"description":"Active program version; null = policy v1 only."},"granted":{"type":"array","items":{"type":"string"}},"effective":{"$ref":"#/components/schemas/PolicyBody"},"eligibility":{"type":"array","items":{"$ref":"#/components/schemas/DetectorEligibility"}},"lastChangeAt":{"type":["string","null"],"format":"date-time"},"nextChangeAllowedAt":{"type":["string","null"],"format":"date-time","description":"Cadence: one widening per week."},"history":{"type":"array","items":{"type":"object","properties":{"version":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"body":{"$ref":"#/components/schemas/ProgramPolicyBody"},"active":{"type":"boolean"}},"required":["version","createdAt","body","active"],"additionalProperties":false}}},"required":["programId","tier","version","granted","effective","eligibility","lastChangeAt","nextChangeAllowedAt","history"],"additionalProperties":false,"title":"ProgramAutonomy"},"RuleCondition":{"type":"object","properties":{"field":{"type":"string","enum":["amountCents","rail","direction","type","status","counterpartyCountry","cardDecisionReason","entity.verificationStatus","entity.pepStatus","entity.isHighRisk","entity.requiresScreening","entity.screeningAgeDays","entity.count30d","velocity24hCount","velocity24hSumCents","entity.priorTransfers","entity.priorReturns","entity.achDebits60d","entity.unauthorizedReturns60d","structuringWindowCount","counterparty.firstTime","counterparty.n","counterparty.priorReturns","counterparty.priorUnauthorizedReturns","program.n90d","program.medianCents","program.p95Cents","program.dailyAvgCents30d","program.declaredMonthlyVolumeCents","program.unauthorizedReturnRate60d","declared.countries","typology.roundTrip","typology.layeringHops","typology.dormantDays","typology.justBelowThreshold"],"description":"A fixed vocabulary over the same features the scorer uses."},"op":{"type":"string","enum":["gt","gte","lt","lte","eq","neq","in","not_in","between","is_null","not_null"]},"value":{"description":"Scalar (string, number, boolean, null) or an array of scalars for in / not_in / between; omitted for is_null / not_null.","oneOf":[{"type":["string","number","boolean","null"]},{"type":"array","items":{"type":["string","number","boolean","null"]}}]}},"required":["field","op"],"additionalProperties":false,"title":"RuleCondition"},"Rule":{"type":"object","properties":{"id":{"type":"string","description":"Stable id, lowercase.","pattern":"^[a-z0-9][a-z0-9-]{1,40}$"},"name":{"type":"string","minLength":1,"maxLength":120},"scope":{"type":"string","enum":["transfer","entity","counterparty","program"],"description":"Default transfer; only transfer-scoped rules fire today."},"programId":{"type":["string","null"],"format":"uuid","description":"null = every program on the tenant."},"when":{"type":"array","items":{"$ref":"#/components/schemas/RuleCondition"},"description":"AND of conditions; at least one."},"action":{"type":"string","enum":["alert","hold","review","log"]},"severity":{"type":"string","enum":["low","medium","high"]},"status":{"type":"string","enum":["draft","testing","active","paused","archived"]},"note":{"type":"string","maxLength":500}},"required":["id","name","scope","programId","when","action","severity","status"],"additionalProperties":false,"title":"Rule"},"RuleDraft":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,40}$"},"name":{"type":"string","minLength":1,"maxLength":120},"scope":{"type":"string","enum":["transfer","entity","counterparty","program"]},"programId":{"type":["string","null"],"format":"uuid"},"when":{"type":"array","items":{"$ref":"#/components/schemas/RuleCondition"}},"action":{"type":"string","enum":["alert","hold","review","log"]},"severity":{"type":"string","enum":["low","medium","high"]},"note":{"type":"string","maxLength":500}},"required":["id","name","when"],"additionalProperties":false,"description":"Status is always written as draft; an active rule must be paused before editing (409).","title":"RuleDraft"},"Backtest":{"type":"object","properties":{"key":{"type":"string"},"ruleId":{"type":"string"},"at":{"type":"string","format":"date-time"},"days":{"type":"integer"},"scanned":{"type":"integer","description":"Transfers replayed (capped at 1000)."},"sampled":{"type":"boolean","description":"True when the cap truncated the window."},"matched":{"type":"integer"},"matchedShare":{"type":["number","null"]},"wouldHold":{"type":"integer"},"wouldAlert":{"type":"integer"},"overlapWithExistingAlerts":{"type":"integer"},"byBand":{"type":"object","additionalProperties":{"type":"integer"}},"sampleTransferIds":{"type":"array","items":{"type":"string"}}},"required":["key","ruleId","at","days","scanned","sampled","matched","matchedShare","wouldHold","wouldAlert","overlapWithExistingAlerts","byBand","sampleTransferIds"],"additionalProperties":false,"title":"Backtest"},"RulesVersion":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"string"},"rules":{"type":"array","items":{"$ref":"#/components/schemas/Rule"}},"createdAt":{"type":"string","format":"date-time"},"active":{"type":"boolean"},"note":{"type":"string"}},"required":["id","version","rules","createdAt","active"],"additionalProperties":false,"title":"RulesVersion"},"Measured":{"type":"object","properties":{"value":{"type":["number","null"],"description":"null when nothing was measured"},"n":{"type":"integer","description":"observations behind the value"}},"required":["value","n"],"additionalProperties":false,"description":"A number with its n; value is null when nothing was measured."},"GovernanceRecord":{"type":"object","properties":{"recordVersion":{"type":"string","description":"governance-v1"},"asOf":{"type":"string","format":"date-time"},"tenant":{"type":"string"},"simulated":{"type":"boolean"},"windowDays":{"type":"integer"},"inventory":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"kind":{"type":"string","enum":["deterministic","llm","rules"]},"decides":{"type":"boolean"},"purpose":{"type":"string"},"inputs":{"type":"string"},"output":{"type":"string"},"whereUsed":{"type":"string"},"validation":{"type":"string"},"evidence":{"type":"string"}},"required":["id","name","version","kind","decides","purpose","inputs","output","whereUsed","validation","evidence"],"additionalProperties":false}},"boundary":{"type":"object","additionalProperties":true,"description":"Policy version, auto-closable detectors and bands, minimum program history, always-reviewed and never-automated detectors, two-approver actions, hold-band rule, bands, autonomy eligibility, and each program's tier, version and granted detectors."},"validation":{"type":"object","additionalProperties":true,"description":"Eval reports for the transaction Skoor, typologies and actor Skoors (cases, band accuracy, precision/recall with n, failures by case id), failuresTotal, and live calibration, hold-band precision/recall and coverage."},"monitoring":{"$ref":"#/components/schemas/Metrics"},"changeControl":{"type":"object","additionalProperties":true,"description":"Stored policy versions (scope, version, active, createdAt), rule versions and the activation gate, the hash-chain report, and how the schema changes."},"oversight":{"type":"object","additionalProperties":true,"description":"Attribution rule, decision clocks, decisions in window (total, by a person, under policy, overturned drafts, overturn rate with n), second-approver actions, the language model's role, model and whether it is configured."},"data":{"type":"object","additionalProperties":true,"description":"Event coverage with the event types seen, the null rule, the no-fabrication test, retention."},"mapping":{"type":"array","items":{"type":"object","properties":{"framework":{"type":"string","enum":["SR 11-7","NIST AI RMF"]},"section":{"type":"string"},"requirement":{"type":"string"},"whereInSkoor":{"type":"string"},"evidence":{"type":"string"}},"required":["framework","section","requirement","whereInSkoor","evidence"],"additionalProperties":false}}},"required":["recordVersion","asOf","tenant","simulated","windowDays","inventory","boundary","validation","monitoring","changeControl","oversight","data","mapping"],"additionalProperties":false},"Metrics":{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"windowDays":{"type":"integer"},"calibration":{"type":"object","properties":{"clear":{"$ref":"#/components/schemas/Measured"},"review":{"$ref":"#/components/schemas/Measured"},"hold":{"$ref":"#/components/schemas/Measured"},"unscored":{"$ref":"#/components/schemas/Measured"}},"required":["clear","review","hold","unscored"],"additionalProperties":false,"description":"Per band: labelled transfers that went bad ÷ labelled transfers."},"coverage":{"type":"object","properties":{"transfers":{"$ref":"#/components/schemas/Measured"},"programs":{"$ref":"#/components/schemas/Measured"}},"required":["transfers","programs"],"additionalProperties":false},"holdBand":{"type":"object","properties":{"precision":{"$ref":"#/components/schemas/Measured"},"recall":{"$ref":"#/components/schemas/Measured"},"tp":{"type":"integer"},"fp":{"type":"integer"},"fn":{"type":"integer"}},"required":["precision","recall","tp","fp","fn"],"additionalProperties":false},"autoClosed":{"$ref":"#/components/schemas/Measured"},"overturned":{"$ref":"#/components/schemas/Measured"},"timeToDecisionHours":{"type":"object","properties":{"automated":{"type":"object","properties":{"median":{"type":["number","null"]},"p90":{"type":["number","null"]},"n":{"type":"integer"}},"required":["median","p90","n"],"additionalProperties":false,"description":"Closed alerts routed automated."},"reviewed":{"type":"object","properties":{"median":{"type":["number","null"]},"p90":{"type":["number","null"]},"n":{"type":"integer"}},"required":["median","p90","n"],"additionalProperties":false,"description":"Closed alerts routed reviewed."}},"required":["automated","reviewed"],"additionalProperties":false},"approvedWithoutWaiting":{"type":"object","properties":{"count":{"type":"integer"},"cents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."},"n":{"type":"integer","description":"Scored transfers."}},"required":["count","cents","n"],"additionalProperties":false,"description":"Clear-band transfers that never raised an alert."},"eventCoverage":{"type":"object","properties":{"value":{"type":["number","null"]},"n":{"type":"integer"},"seen":{"type":"array","items":{"type":"string"}}},"required":["value","n","seen"],"additionalProperties":false,"description":"Event families understood ÷ families known."}},"required":["asOf","windowDays","calibration","coverage","holdBand","autoClosed","overturned","timeToDecisionHours","approvedWithoutWaiting","eventCoverage"],"additionalProperties":false,"title":"Metrics"},"ChainReport":{"type":"object","properties":{"ok":{"type":"boolean"},"checked":{"type":"integer"},"firstBreakAt":{"oneOf":[{"type":"object","properties":{"table":{"type":"string","enum":["dispositions","actions"]},"id":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"}},"required":["table","id","createdAt"],"additionalProperties":false},{"type":"null"}]},"chains":{"type":"object","properties":{"dispositions":{"type":"object","properties":{"ok":{"type":"boolean"},"checked":{"type":"integer"}},"required":["ok","checked"],"additionalProperties":false},"actions":{"type":"object","properties":{"ok":{"type":"boolean"},"checked":{"type":"integer"}},"required":["ok","checked"],"additionalProperties":false}},"required":["dispositions","actions"],"additionalProperties":false}},"required":["ok","checked","firstBreakAt","chains"],"additionalProperties":false,"title":"ChainReport"},"SkoorPoint":{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"skoor":{"type":["integer","null"]},"band":{"type":"string"},"n":{"type":"integer"},"confidence":{"type":["number","null"]},"version":{"type":"string"}},"required":["asOf","skoor","band","n","confidence","version"],"additionalProperties":false,"title":"SkoorPoint"},"BandTransition":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"from":{"type":"string"},"to":{"type":"string"},"skoor":{"type":["integer","null"]},"n":{"type":"integer"}},"required":["at","from","to","skoor","n"],"additionalProperties":false,"title":"BandTransition"},"TransfersSummary":{"type":"object","properties":{"windowDays":{"type":"integer"},"count":{"type":"integer"},"volumeCents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."},"returns":{"type":"object","properties":{"any":{"type":"integer"},"unauthorized":{"type":"integer"}},"required":["any","unauthorized"],"additionalProperties":false},"scored":{"type":"integer"},"holdShare":{"type":["number","null"]},"reviewShare":{"type":["number","null"]}},"required":["windowDays","count","volumeCents","returns","scored","holdShare","reviewShare"],"additionalProperties":false,"title":"TransfersSummary"},"SkoorSignal":{"type":"object","properties":{"code":{"type":"string"},"weight":{"type":"number"},"hard":{"type":"boolean"},"detail":{"type":"string"}},"required":["code","weight"],"additionalProperties":false,"title":"SkoorSignal"},"ActorReport":{"type":"object","properties":{"kind":{"const":"actor"},"type":{"type":"string","enum":["entity","counterparty","agent"]},"id":{"type":"string"},"name":{"type":["string","null"]},"programId":{"type":"string","format":"uuid"},"generatedAt":{"type":"string","format":"date-time"},"current":{"oneOf":[{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"skoor":{"type":["integer","null"]},"band":{"type":"string"},"n":{"type":"integer"},"confidence":{"type":["number","null"]},"version":{"type":"string"},"hardSignal":{"type":"boolean"},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"}},"signals":{"type":"array","items":{"$ref":"#/components/schemas/SkoorSignal"}},"inputs":{"type":"object","additionalProperties":true}},"required":["asOf","skoor","band","n","confidence","version","hardSignal","reasons","signals","inputs"],"additionalProperties":false},{"type":"null"}]},"history":{"type":"array","items":{"$ref":"#/components/schemas/SkoorPoint"}},"transitions":{"type":"array","items":{"$ref":"#/components/schemas/BandTransition"}},"signalTrends":{"oneOf":[{"type":"object","properties":{"appeared":{"type":"array","items":{"$ref":"#/components/schemas/SkoorSignal"}},"disappeared":{"type":"array","items":{"$ref":"#/components/schemas/SkoorSignal"}},"changed":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"from":{"type":"number"},"to":{"type":"number"}},"required":["code","from","to"],"additionalProperties":false}},"comparedTo":{"type":["string","null"]}},"required":["appeared","disappeared","changed","comparedTo"],"additionalProperties":false},{"type":"null"}]},"transfers":{"$ref":"#/components/schemas/TransfersSummary"},"alerts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"detector":{"type":"string"},"status":{"type":"string"},"route":{"type":["string","null"]},"openedAt":{"type":"string","format":"date-time"},"summary":{"type":"string"}},"required":["id","detector","status","route","openedAt","summary"],"additionalProperties":false}},"dispositions":{"type":"array","items":{"type":"object","properties":{"alertId":{"type":"string","format":"uuid"},"decidedBy":{"type":"string"},"decision":{"type":"string"},"overturnsDraft":{"type":["boolean","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["alertId","decidedBy","decision","overturnsDraft","createdAt"],"additionalProperties":false}}},"required":["kind","type","id","name","programId","generatedAt","current","history","transitions","signalTrends","transfers","alerts","dispositions"],"additionalProperties":false,"description":"No percentiles across banks; n and confidence say how much history stands behind the number.","title":"ActorReport"},"ProgramReport":{"type":"object","properties":{"kind":{"const":"program"},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"current":{"oneOf":[{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"programSkoor":{"type":["integer","null"]},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"n":{"type":"integer"},"version":{"type":["string","null"]},"windowDays":{"type":"integer"}},"required":["asOf","programSkoor","band","n","version","windowDays"],"additionalProperties":false},{"type":"null"}]},"history":{"type":"array","items":{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"programSkoor":{"type":["integer","null"]},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"n":{"type":"integer"}},"required":["asOf","programSkoor","band","n"],"additionalProperties":false}},"transitions":{"type":"array","items":{"$ref":"#/components/schemas/BandTransition"}},"kris":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KriValue"}},"kriDeltas":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"from":{"type":["number","null"]},"to":{"type":["number","null"]},"delta":{"type":["number","null"]},"statusFrom":{"type":["string","null"]},"statusTo":{"type":"string"},"n":{"type":"integer"}},"required":["name","from","to","delta","statusFrom","statusTo","n"],"additionalProperties":false}},"rank":{"oneOf":[{"type":"object","properties":{"position":{"type":"integer"},"of":{"type":"integer"},"basis":{"type":"string"}},"required":["position","of","basis"],"additionalProperties":false,"description":"Inside one bank only: \"n of N programs\"."},{"type":"null"}]},"transfers":{"$ref":"#/components/schemas/TransfersSummary"},"alertsByDetector":{"type":"array","items":{"type":"object","properties":{"detector":{"type":"string"},"route":{"type":["string","null"]},"count":{"type":"integer"}},"required":["detector","route","count"],"additionalProperties":false}}},"required":["kind","id","name","generatedAt","current","history","transitions","kris","kriDeltas","rank","transfers","alertsByDetector"],"additionalProperties":false,"title":"ProgramReport"},"WeeklyReport":{"type":"object","properties":{"kind":{"const":"weekly"},"key":{"type":"string","description":"`weekly:<programId>:<week>`"},"programId":{"type":"string","format":"uuid"},"programName":{"type":"string"},"isoWeek":{"type":"string","description":"e.g. 2026-W37"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"generatedAt":{"type":"string","format":"date-time"},"programSkoor":{"type":"object","properties":{"current":{"type":["integer","null"]},"prior":{"type":["integer","null"]},"band":{"type":"string"},"n":{"type":"integer"}},"required":["current","prior","band","n"],"additionalProperties":false},"kris":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":["number","null"]},"prior":{"type":["number","null"]},"delta":{"type":["number","null"]},"status":{"type":"string"},"priorStatus":{"type":["string","null"]},"n":{"type":"integer"},"unit":{"type":"string"},"threshold":{"type":["number","null"]}},"required":["name","value","prior","delta","status","priorStatus","n","unit","threshold"],"additionalProperties":false}},"alerts":{"type":"object","properties":{"total":{"type":"integer"},"byDetector":{"type":"array","items":{"type":"object","properties":{"detector":{"type":"string"},"route":{"type":["string","null"]},"count":{"type":"integer"}},"required":["detector","route","count"],"additionalProperties":false}},"byStatus":{"type":"object","additionalProperties":{"type":"integer"}}},"required":["total","byDetector","byStatus"],"additionalProperties":false},"enteredHold":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"},"skoor":{"type":["integer","null"]},"n":{"type":"integer"},"at":{"type":"string","format":"date-time"}},"required":["type","id","skoor","n","at"],"additionalProperties":false}},"dispositions":{"type":"object","properties":{"total":{"type":"integer"},"byPlatform":{"type":"integer"},"byPerson":{"type":"integer"},"overturned":{"type":"integer"}},"required":["total","byPlatform","byPerson","overturned"],"additionalProperties":false},"timeToDecisionHours":{"type":"object","properties":{"median":{"type":["number","null"]},"p90":{"type":["number","null"]},"n":{"type":"integer"}},"required":["median","p90","n"],"additionalProperties":false,"description":"Closed alerts in the week."},"transfers":{"type":"object","properties":{"count":{"type":"integer"},"volumeCents":{"type":"string","pattern":"^-?\\d+$","description":"Integer cents as a string (bigint-safe)."},"returns":{"type":"object","properties":{"any":{"type":"integer"},"unauthorized":{"type":"integer"}},"required":["any","unauthorized"],"additionalProperties":false},"bands":{"type":"object","additionalProperties":{"type":"integer"}}},"required":["count","volumeCents","returns","bands"],"additionalProperties":false},"summary":{"type":"string","description":"Plain-text summary written from a template, never a model."}},"required":["kind","key","programId","programName","isoWeek","from","to","generatedAt","programSkoor","kris","alerts","enteredHold","dispositions","timeToDecisionHours","transfers","summary"],"additionalProperties":false,"title":"WeeklyReport"},"RecordLine":{"type":"object","properties":{"kind":{"type":"string","enum":["policy","alert","disposition","action"]}},"required":["kind"],"additionalProperties":true,"description":"One JSON object per line. `policy` first ({version, source: tenant|default, from, to}); then every Alert, Disposition and Action in the range, in time order, with all their fields.","title":"RecordLine"},"ProofClaims":{"type":"object","properties":{"iss":{"type":"string","description":"Issuer: the platform URL."},"sub":{"type":"string","description":"`<type>:<id>`"},"tenant":{"type":"string","description":"Tenant slug."},"program":{"type":["string","null"],"description":"Program id."},"skoor":{"type":"integer"},"band":{"type":"string"},"version":{"type":"string"},"n":{"type":"integer"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"type":"string"},"description":"Reason-code ids only."},"simulated":{"const":true,"description":"Present only when the underlying data is simulated."},"iat":{"type":"integer"},"exp":{"type":"integer","description":"24 hours after iat."},"jti":{"type":"string","description":"The actor_skoors row id."}},"required":["iss","sub","tenant","program","skoor","band","version","n","confidence","reasons","iat","exp","jti"],"additionalProperties":false,"title":"ProofClaims"},"ProofVerification":{"type":"object","properties":{"valid":{"type":"boolean"},"reason":{"type":"string","description":"malformed | unsupported alg | unknown kid | bad signature | expired | no skoor in claims | token required"},"claims":{"$ref":"#/components/schemas/ProofClaims"},"expired":{"type":"boolean"}},"required":["valid"],"additionalProperties":false,"title":"ProofVerification"},"Jwks":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"kid":{"type":"string"},"use":{"const":"sig"},"alg":{"const":"EdDSA"}},"required":["kty","crv","x","kid","use","alg"],"additionalProperties":true}}},"required":["keys"],"additionalProperties":false,"title":"Jwks"},"ColumnEvent":{"type":"object","properties":{"id":{"type":"string","description":"Column event id; redeliveries carry the same id and are deduplicated."},"created_at":{"type":"string"},"type":{"type":"string","description":"e.g. ach.outgoing_transfer.returned"},"data":{"type":"object","additionalProperties":true,"description":"The bank's object."}},"required":["id","created_at","type"],"additionalProperties":false,"title":"ColumnEvent"},"ImportResult":{"type":"object","properties":{"programId":{"type":"string","format":"uuid"},"agents":{"type":"integer"},"profiles":{"type":"integer"},"settlementsScanned":{"type":"integer"},"transfers":{"type":"integer"},"counterparties":{"type":"integer"},"agentCounterparties":{"type":"integer"}},"required":["programId","agents","profiles","settlementsScanned","transfers","counterparties","agentCounterparties"],"additionalProperties":false,"title":"ImportResult"},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"mode":{"type":"string","enum":["test","live"],"description":"test_ keys may only touch simulated programs and the sandbox; live_ keys touch Column programs."},"prefix":{"type":"string","description":"First 12 characters of the secret, for display."},"programId":{"type":["string","null"],"format":"uuid","description":"null = every program on the tenant."},"scopes":{"type":"array","items":{"type":"string"},"description":"[] = all scopes the mode allows."},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"revokedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","mode","prefix","programId","scopes","lastUsedAt","revokedAt","createdAt"],"additionalProperties":false,"description":"Only the sha256 of the secret is stored; the secret is shown once at creation.","title":"ApiKey"},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"programId":{"type":["string","null"],"format":"uuid","description":"null = every program on the tenant."},"url":{"type":"string","description":"https only (http://127.0.0.1 and http://localhost in test)."},"events":{"type":"array","items":{"type":"string","enum":["alert.opened","disposition.signed","action.created","action.approved","preflight.scored","transfer.scored","label.recorded","kri.snapshot","ping"]},"description":"[] = every event."},"enabled":{"type":"boolean"},"description":{"type":["string","null"]},"createdBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","programId","url","events","enabled","description","createdBy","createdAt"],"additionalProperties":false,"title":"WebhookEndpoint"},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"endpointId":{"type":"string","format":"uuid"},"event":{"type":"string","enum":["alert.opened","disposition.signed","action.created","action.approved","preflight.scored","transfer.scored","label.recorded","kri.snapshot","ping"]},"eventId":{"type":"string","description":"Stable id of the emitted event, for the receiver's dedupe."},"payload":{"type":"object","additionalProperties":true,"description":"{ id, type, createdAt, tenant, program, data }"},"status":{"type":"string","enum":["pending","delivered","failed","exhausted"]},"attempts":{"type":"integer"},"nextAttemptAt":{"type":"string","format":"date-time","description":"now + min(2^attempts minutes, 6h) after a failure."},"lastStatusCode":{"type":["integer","null"]},"lastError":{"type":["string","null"]},"deliveredAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","endpointId","event","eventId","payload","status","attempts","nextAttemptAt","lastStatusCode","lastError","deliveredAt","createdAt"],"additionalProperties":false,"title":"WebhookDelivery"},"SandboxResult":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"Ids of the Column-shaped events stored (source = simulated)."},"transferId":{"type":"string"},"alertIds":{"type":"array","items":{"type":"string","format":"uuid"}},"skoor":{"oneOf":[{"type":"object","properties":{"value":{"type":["integer","null"]},"band":{"type":"string","enum":["clear","review","hold","unscored"]},"n":{"type":"integer"}},"required":["value","band","n"],"additionalProperties":false},{"type":"null"}]}},"required":["events","alertIds","skoor"],"additionalProperties":false,"title":"SandboxResult"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"The tenant's operator token (`skop_…`) or an API key (`test_…` / `live_…`, planned)."},"operator":{"type":"apiKey","in":"header","name":"X-Operator","description":"Who is acting. Written on every decision, action and pre-flight."}}},"x-skoor-version":"2026-09-17"}