API reference
Monitoring
26 operations in this area of the contract. Every one declares the permission it requires.
Contract version 0.2.0All areas
Operations
/v1/model-inventoryScenario and model inventory with active-asset coverage
Returns immutable approved inventory history and one coverage row for every active monitoring rule and risk model. A legacy active artifact without a pin is returned as `missing`; it is never omitted or described as clean. `evidence_integrity_verified` is derived from exact same-tenant finalized encrypted artifacts, never from caller metadata. It proves artifact integrity and authenticity, not the substantive independence or quality of the institution's validation assertion.
Show parameters and responsesHide parameters and responses
Responses
200Inventory history, deployment coverage and explicit activation policyModelInventoryResponse
/v1/model-inventory/coveragePage fail-visible active scenario and model coverage
Stable `(asset_type,asset_key)` keyset coverage. Missing, stale, drifted and unverified active assets are returned explicitly rather than omitted.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Resumable active-asset coverageModelInventoryCoveragePage400—
/v1/model-inventory/deploymentsPage immutable inventory deployment lifecycle events
Newest-first keyset history ordered by `(deployed_at,id)`.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Resumable activated and deactivated deployment eventsModelInventoryDeploymentPage400—
/v1/model-inventory/historyPage immutable scenario and model inventory versions
Newest-first keyset history ordered by `(created_at,id)`.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Resumable immutable inventory historyModelInventoryVersionPage400—
/v1/model-inventory/proposalsPropose an exact artifact-bound inventory version
Creates a maker-checker request; it does not create or attach an inventory row. The approved payload is bound to an exact rule-version digest or canonical risk-model definition. Activation remains a separate governed decision and fails closed until a current approved inventory version permits it. The declared inventory-write permission is necessary but not sufficient: monitoring-rule proposals also require `rules.rule.read`, while risk-model proposals also require `rules.risk_model.write` because their payload contains an executable model definition.
Show parameters and responsesHide parameters and responses
Responses
202Inventory proposal awaiting a separate configured checkerApprovalRequest403—409—422—
/v1/model-inventory/risk-model-candidatesPage governed risk-model candidates eligible for activation
Returns current, production-policy inventory candidates backed by finalized encrypted evidence artifacts. In addition to the declared risk-model-write permission, callers must hold `rules.inventory.read` and `rules.rule.read`; lacking either is refused before any database read. Activation reloads the exact definition server-side by inventory id.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Newest-first keyset page of governed activation candidatesModelInventoryVersionPage400—403—
/v1/model-inventory/rule-candidatesPage exact monitoring-rule candidates for inventory proposals
Returns bounded selector metadata only; the proposal service reloads and digests the exact immutable rule version in its tenant transaction. In addition to the declared inventory-write permission, callers must hold `rules.inventory.read` and `rules.rule.read`; lacking either is refused before any database read.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Newest-first keyset page of inventory-eligible rule-version addressesRuleInventoryCandidatePage400—403—
/v1/model-validation-evidencePage finalized model-validation artifacts
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Newest-first keyset page; never includes pending intentsValidationEvidenceArtifactPage400—
/v1/model-validation-evidenceUpload one encrypted immutable validation artifact
Streams a raw body of at most 10 MiB. `Idempotency-Key` is a caller-owned UUIDv7; exact retries verify filename, MIME type, plaintext digest and the write-once object. Filenames are accepted only in `X-Filename`, never in a URL. JSON and text are structurally validated; PDF fails closed unless a real malware scanner is configured.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Idempotency-Keyrequired | header | string | — |
X-Filenamerequired | header | string | — |
Responses
201Finalized encrypted artifact metadataValidationEvidenceArtifact409—413Artifact exceeds 10 MiB422—503—
/v1/model-validation-evidence/{artifactID}Read one finalized validation artifact's metadata
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
artifactIDrequired | path | string | — |
Responses
200Authenticated metadata including the decrypted display filenameValidationEvidenceArtifact404—
/v1/model-validation-evidence/{artifactID}/contentDownload authenticated validation evidence bytes
Rechecks ciphertext digest/size, artifact-bound AEAD, plaintext digest and size before appending access evidence. Audit failure withholds the response. Returned as an attachment with no-store and nosniff headers.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
artifactIDrequired | path | string | — |
Responses
200Exact immutable artifact bytesstring404—503—
/v1/model-validation-evidence/{artifactID}/recoverRecover a finalized artifact whose upload audit did not commit
Separately permissioned two-phase operational recovery. It first re-authenticates the stored object (ciphertext size/digest, artifact-bound AEAD, plaintext size/digest) and commits the recovery audit event. A writer-only second transaction then finalizes the immutable eligibility receipt. If that writer transaction is unavailable, the intent remains fail-visible and retryable rather than being reported as finalized. Recovery retains the immutable original uploader and metadata; it cannot create or replace bytes or repair a failed integrity check.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
artifactIDrequired | path | string | — |
Responses
200Exact artifact metadata after durable recovery audit/receiptValidationEvidenceArtifact400—404—422—503—
/v1/model-validation-evidence/pendingPage retryable upload intents not yet inventory-eligible
Makes object-store/process and finalization/audit crash windows operationally visible. Rows remain ineligible until exact bytes are finalized and a durable audit receipt exists.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas. |
Responses
200Newest-first pending/stalled intentsPendingValidationEvidencePage400—
/v1/risk-models/activationPropose a change to the customer risk-scoring model
Four-eyes, like a rule change and for a sharper reason: an assessment pins the model version it was scored under, so activating a model has no effect on past ratings and total effect on every future one.
Input is an approved `inventory_version_id`. Its immutable candidate definition is loaded server-side and copied into the digest-bound approval payload, so an operator never pastes governed JSON a second time and an intervening inventory cannot be selected.
Show parameters and responsesHide parameters and responses
Responses
202The approval request awaiting a second pair of eyesApprovalRequest422—
/v1/risk-models/activeThe live customer risk-scoring model
As it was snapshotted at activation, not as configuration reads today. An assessment pins the model version it was scored under, so serving today's configuration here would misdescribe every rating computed before the last change.
Show parameters and responsesHide parameters and responses
Responses
200The active model versionRiskModel404No model has been activated. Fresh bootstrap intentionally reaches this state: signed but unvalidated pack metadata is projected as a blocked candidate, and a live model appears only after artifact-backed inventory and maker-checker approval.
/v1/rule-versions/{versionID}One rule version, and whether the builder can render it
`editable` is ADR-0028's refusal made visible. An expression using a construct the condition tree cannot hold — a macro, a function call, arithmetic nested more than one level — comes back with `editable: false` and a reason, and the builder shows the expression read-only rather than rendering an approximation it would then save back over the author's rule.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
versionIDrequired | path | string | — |
Responses
200The version, with its tree when one can be shownobject404—
/v1/rule-versions/{versionID}/activationPropose that a rule version go live
Proposes. It does not activate.
Architectural mandate 8 requires four-eyes on rule changes, and the activation happens inside the APPROVING transaction (ADR-0008) — so approving and applying either both happen or neither does. An approval recorded against a change that failed to apply would be a lie in the audit trail.
The rationale is required. A checker approving "activate rule version 7" with nothing else in front of them is a rubber stamp with an audit trail.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
versionIDrequired | path | string | — |
Responses
202The approval request awaiting a second pair of eyesApprovalRequest409—422—
/v1/rule-versions/{versionID}/simulationsPrevious simulations of a candidate
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
versionIDrequired | path | string | — |
Responses
200The runs, newest firstobject
/v1/rule-versions/{versionID}/simulationsReplay a candidate rule over history without raising alerts
The endpoint the rule builder is built around (ADR-0016).
A builder without a simulator lets an administrator activate a rule across a live book with no idea whether it will fire on nothing or on everything. Both are catastrophic and only one is visible: a rule that fires on nothing looks exactly like a clean book.
It is the SAME EVALUATOR the ingest path runs, with window aggregates from the same query — ADR-0016 requires that, because an approximate simulator tells a compliance officer a comfortable lie about the alert volume they are about to create.
It writes NO ALERTS. The only row it creates is the record of the question having been asked, which is what an examiner is looking for when they ask whether a rule was tested before it went live.
POST rather than GET because it scans the ledger, writes that record, and moves a draft to `simulated`. It is not a safe, repeatable read.
What it does not reproduce, stated because a silent difference is the failure mode: correlation and de-duplication (so `alerts_would_raise` counts firings and the number of triage-queue rows is lower — `distinct_subjects` is the honest lower bound), alert scoring and priority, and the per-segment enable switch.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
versionIDrequired | path | string | — |
Responses
201What the candidate would have doneRuleSimulation404—422—
/v1/rulesThe monitoring rule library and its active versions
Show parameters and responsesHide parameters and responses
Responses
200Rules with the version currently in force for eachobject
/v1/rulesCreate a monitoring rule and its first draft version
One request, because a rule with no version is not a rule: it is a name in a table the engine will never load, listed in the library, apparently installed and doing nothing.
The version created is a DRAFT. Simulate it before proposing it for activation.
Show parameters and responsesHide parameters and responses
Responses
201The rule and its first draft versionobject422—
/v1/rules/{ruleKey}/versionsA rule's version history
What is live, what was retired, and what somebody drafted and never activated — each with its condition tree, so the builder can open any of them.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
ruleKeyrequired | path | string | — |
Responses
200The versions, newest firstRuleVersionPage
/v1/rules/{ruleKey}/versionsDraft a new version of a rule
Always a DRAFT. Nothing on this path can put a rule live: activation supersedes the version an institution's alerts already reference and is a four-eyes change (ADR-0008).
Supply either a `condition_tree`, which is what the builder posts, or an `expression`, which is what an expert posts. Supplying both is allowed and checked — the tree is recompiled and a pair that disagree is refused, because a stored rule that shows one thing and runs another is worse than either alone.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
ruleKeyrequired | path | string | — |
Responses
201The stored draftRuleVersion422—
/v1/rules/vocabularyThe typed field vocabulary a rule may reference
What the rule builder's field picker is drawn from (ADR-0028).
Served from the same table the expression validator refuses against and the evaluator binds at run time, so a field the form offers is necessarily a field a rule may name. Two hand-kept lists — one for the picker, one for the check — is how a builder comes to offer a field that fails on save.
Thresholds are NOT in this catalogue. They are referenced as `params.<name>`, resolved per customer segment from the rule's configuration document, and are deliberately never literals in the expression (ADR-0016). Every statically named parameter and its CEL type are checked against the effective threshold document before proposal and activation; see the `note` in the response.
Show parameters and responsesHide parameters and responses
Responses
200The vocabulary, grouped by namespaceobject
/v1/transactionsIngest one transaction for real-time monitoring
The API-based real-time monitoring path of BRD §8.3, for PSPs and payment rails. The transaction is stored and the active monitoring rules are evaluated against it before this call returns; any alerts raised are in the response.
**This is not a payment authorization endpoint.** It returns what happened — stored or already stored, evaluated or not, which alerts fired — and no `decision` field. Open question Q7 defaults to a synchronous decision endpoint with a per-tenant timeout and an explicit per-tenant fail-open / fail-closed policy that "must be the client's documented, signed choice, surfaced in the admin console rather than buried in a config file". None of that exists yet, so this endpoint does not imply it. A caller that blocks its payment rail on this response is making a decision this platform has not agreed to and cannot honour an SLA for.
**Idempotency.** `Idempotency-Key` and `external_ref` are one identity, not two: send either, or both with the same value. Idempotency is enforced by a unique database constraint on (tenant, source, external_ref, booking_ts), not by an application check — checking for existence before inserting is a race, and at payment-rail volumes it is a race that gets lost (ADR-0014). A replay is a `200` with `duplicate: true`, no re-evaluation and no second alert.
**Financial-event lifecycle.** An ordinary transaction defaults to `event_type: posted`. A reversal, correction, cancellation or chargeback is another immutable monitored transaction which names the exact prior `(original_source, original_external_ref, original_booking_ts)` identity and supplies a reason. The original is never updated. Corrections may form a linear chain; terminal events may not be superseded, concurrent branches are refused, and every successor must have a strictly later booking timestamp. Monitoring aggregates expose the effective state without erasing raw-event provenance. A correction is re-evaluated for automatic CTR using its corrected amount; terminal events retain/link any prior automatic CTR in an explicit amendment, withdrawal or cancellation-review disposition. Those dispositions are workflow evidence, not claims that a regulator received an amendment.
**Latency.** The handler carries a fixed 2s budget (BRD §9). A request that exceeds it is rolled back and answered `503`; retrying is safe and is what the idempotency identity is for. The budget is not per-tenant configurable — that is the Q7 work.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string | The caller's own reference for this operation. ADR-0012 requires it on mutating endpoints because payment-adjacent callers retry and a duplicate filing is a regulatory incident. On the ingest endpoints it is not a separate identity: it supplies `external_ref` when the body omits it, and the uniqueness of (tenant, source, external_ref, booking_ts) in the database is what makes the retry harmless (ADR-0014). Sending both with different values is refused rather than resolved. |
Responses
200Already stored. An idempotent replay: nothing was written, no rules were re-run, and the alerts raised the first time are not repeated.TransactionIngestResult201Stored and evaluatedTransactionIngestResult400—403—409The idempotency identity was reused with different content, or the original already has a lifecycle successor.422—503Evaluation exceeded the real-time budget, or a lifecycle event's automatic CTR impact could not be recorded. The whole transaction was rolled back; retrying with the same external_ref is safe.
/v1/transactions/batchIngest an end-of-day core-banking export
The file/batch ingestion path of BRD §8.3. The body is the CSV export itself (`curl --data-binary @export.csv`), not a multipart form: the caller is a scheduled job, not a browser.
Malformed rows are quarantined with their line number and parse error rather than dropped or made fatal, and the response is the per-file reconciliation record an operations team needs to prove an export was consumed exactly once (ADR-0014).
Lifecycle-capable exports may add `event_type`, `original_source`, `original_external_ref`, `original_booking_ts` and `lifecycle_reason` columns. Missing `event_type` remains compatible and means `posted`; every non-posted row requires the other four columns and a strictly later booking timestamp.
The reconciliation row commits before parsing. Parsed failures and canonically ordered transaction rows then commit in bounded chunks, with the counters updated in the same transaction as each chunk. A failure therefore leaves both the successfully processed prefix and a durable failed reconciliation record. Re-sending the same bytes with the same `source` is safe: committed rows conflict on their idempotency identity and are counted as duplicates before processing continues into the uncommitted suffix.
`resumable: true` describes replay-based recovery; it does not claim the HTTP upload itself can restart at an arbitrary byte offset. `X-Baobyte-Batch-ID` is returned on both success and post-creation errors so an operator can reconcile the durable attempt.
**This operation takes no `Idempotency-Key` header.** It declared one until stream B4 removed it, and `ingestBatch` has never read it: the documented parameter had no effect, which is worse than no parameter at all — a caller could set it, believe the file was protected against a double send, and be relying on nothing. Per-FILE replay protection would mean recording the key on `app.ingest_batch` and returning the original `batch_id` on a re-send; that is not built. What IS built is per-ROW idempotency, which makes a duplicate send harmless in the way that matters: re-posting the same export inserts nothing and counts every row as a duplicate. It produces a second `ingest_batch` row, so the reconciliation trail shows two attempts — which is the truth about what happened.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
source | query | string | The integration label recorded on every row of this file, and part of each row's idempotency identity. A re-sent export must carry the same one. |
filename | query | string | The export's file name, retained on the reconciliation record. |
Responses
200The reconciliation record for this fileIngestBatchResult400—403—413The export exceeds the endpoint's size limit. Split it; the parts are individually idempotent.415The body was not a CSV export. To ingest a single transaction as JSON, POST /v1/transactions.422The file's header could not be read, so the whole export is unusable. Nothing was ingested — and because the batch is one transaction, the reconciliation row for the attempt was rolled back with it.