Skip to main content
Baobyte AML Suite
Documentation contents

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

GET/v1/model-inventory

Scenario and model inventory with active-asset coverage

Required permission: rules.inventory.readlistModelInventory

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 responses

Responses

  • 200Inventory history, deployment coverage and explicit activation policyModelInventoryResponse
GET/v1/model-inventory/coverage

Page fail-visible active scenario and model coverage

Required permission: rules.inventory.readlistModelInventoryCoverage

Stable `(asset_type,asset_key)` keyset coverage. Missing, stale, drifted and unverified active assets are returned explicitly rather than omitted.

Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 coverageModelInventoryCoveragePage
  • 400—
GET/v1/model-inventory/deployments

Page immutable inventory deployment lifecycle events

Required permission: rules.inventory.readlistModelInventoryDeployments

Newest-first keyset history ordered by `(deployed_at,id)`.

Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 eventsModelInventoryDeploymentPage
  • 400—
GET/v1/model-inventory/history

Page immutable scenario and model inventory versions

Required permission: rules.inventory.readlistModelInventoryHistory

Newest-first keyset history ordered by `(created_at,id)`.

Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 historyModelInventoryVersionPage
  • 400—
POST/v1/model-inventory/proposals

Propose an exact artifact-bound inventory version

Required permission: rules.inventory.writeproposeModelInventory

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 responses

Responses

  • 202Inventory proposal awaiting a separate configured checkerApprovalRequest
  • 403—
  • 409—
  • 422—
GET/v1/model-inventory/risk-model-candidates

Page governed risk-model candidates eligible for activation

Required permission: rules.risk_model.writelistRiskModelActivationCandidates

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 responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 candidatesModelInventoryVersionPage
  • 400—
  • 403—
GET/v1/model-inventory/rule-candidates

Page exact monitoring-rule candidates for inventory proposals

Required permission: rules.inventory.writelistRuleInventoryCandidates

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 responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 addressesRuleInventoryCandidatePage
  • 400—
  • 403—
GET/v1/model-validation-evidence

Page finalized model-validation artifacts

Required permission: rules.validation_evidence.readlistModelValidationEvidence
Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 intentsValidationEvidenceArtifactPage
  • 400—
POST/v1/model-validation-evidence

Upload one encrypted immutable validation artifact

Required permission: rules.validation_evidence.uploaduploadModelValidationEvidence

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 responses

Parameters

NameInTypeDescription
Idempotency-Keyrequiredheaderstring—
X-Filenamerequiredheaderstring—

Responses

  • 201Finalized encrypted artifact metadataValidationEvidenceArtifact
  • 409—
  • 413Artifact exceeds 10 MiB
  • 422—
  • 503—
GET/v1/model-validation-evidence/{artifactID}

Read one finalized validation artifact's metadata

Required permission: rules.validation_evidence.readgetModelValidationEvidence
Show parameters and responses

Parameters

NameInTypeDescription
artifactIDrequiredpathstring—

Responses

  • 200Authenticated metadata including the decrypted display filenameValidationEvidenceArtifact
  • 404—
GET/v1/model-validation-evidence/{artifactID}/content

Download authenticated validation evidence bytes

Required permission: rules.validation_evidence.downloaddownloadModelValidationEvidence

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 responses

Parameters

NameInTypeDescription
artifactIDrequiredpathstring—

Responses

  • 200Exact immutable artifact bytesstring
  • 404—
  • 503—
POST/v1/model-validation-evidence/{artifactID}/recover

Recover a finalized artifact whose upload audit did not commit

Required permission: rules.validation_evidence.recoverrecoverModelValidationEvidence

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 responses

Parameters

NameInTypeDescription
artifactIDrequiredpathstring—

Responses

  • 200Exact artifact metadata after durable recovery audit/receiptValidationEvidenceArtifact
  • 400—
  • 404—
  • 422—
  • 503—
GET/v1/model-validation-evidence/pending

Page retryable upload intents not yet inventory-eligible

Required permission: rules.validation_evidence.readlistPendingModelValidationEvidence

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 responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque 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 intentsPendingValidationEvidencePage
  • 400—
POST/v1/risk-models/activation

Propose a change to the customer risk-scoring model

Required permission: rules.risk_model.writeproposeRiskModelActivation

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 responses

Responses

  • 202The approval request awaiting a second pair of eyesApprovalRequest
  • 422—
GET/v1/risk-models/active

The live customer risk-scoring model

Required permission: rules.rule.readgetActiveRiskModel

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 responses

Responses

  • 200The active model versionRiskModel
  • 404No 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.
GET/v1/rule-versions/{versionID}

One rule version, and whether the builder can render it

Required permission: rules.rule.readgetRuleVersion

`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 responses

Parameters

NameInTypeDescription
versionIDrequiredpathstring—

Responses

  • 200The version, with its tree when one can be shownobject
  • 404—
POST/v1/rule-versions/{versionID}/activation

Propose that a rule version go live

Required permission: rules.rule.writeproposeRuleActivation

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 responses

Parameters

NameInTypeDescription
versionIDrequiredpathstring—

Responses

  • 202The approval request awaiting a second pair of eyesApprovalRequest
  • 409—
  • 422—
GET/v1/rule-versions/{versionID}/simulations

Previous simulations of a candidate

Required permission: rules.rule.simulatelistRuleSimulations
Show parameters and responses

Parameters

NameInTypeDescription
versionIDrequiredpathstring—

Responses

  • 200The runs, newest firstobject
POST/v1/rule-versions/{versionID}/simulations

Replay a candidate rule over history without raising alerts

Required permission: rules.rule.simulatesimulateRuleVersion

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 responses

Parameters

NameInTypeDescription
versionIDrequiredpathstring—

Responses

  • 201What the candidate would have doneRuleSimulation
  • 404—
  • 422—
GET/v1/rules

The monitoring rule library and its active versions

Required permission: rules.rule.readlistRules
Show parameters and responses

Responses

  • 200Rules with the version currently in force for eachobject
POST/v1/rules

Create a monitoring rule and its first draft version

Required permission: rules.rule.writecreateRule

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 responses

Responses

  • 201The rule and its first draft versionobject
  • 422—
GET/v1/rules/{ruleKey}/versions

A rule's version history

Required permission: rules.rule.readlistRuleVersions

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 responses

Parameters

NameInTypeDescription
ruleKeyrequiredpathstring—

Responses

  • 200The versions, newest firstRuleVersionPage
POST/v1/rules/{ruleKey}/versions

Draft a new version of a rule

Required permission: rules.rule.writecreateRuleVersion

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 responses

Parameters

NameInTypeDescription
ruleKeyrequiredpathstring—

Responses

  • 201The stored draftRuleVersion
  • 422—
GET/v1/rules/vocabulary

The typed field vocabulary a rule may reference

Required permission: rules.rule.readgetRuleVocabulary

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 responses

Responses

  • 200The vocabulary, grouped by namespaceobject
POST/v1/transactions

Ingest one transaction for real-time monitoring

Required permission: monitoring.transaction.ingestingestTransaction

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 responses

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringThe 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.TransactionIngestResult
  • 201Stored and evaluatedTransactionIngestResult
  • 400—
  • 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.
POST/v1/transactions/batch

Ingest an end-of-day core-banking export

Required permission: monitoring.batch.ingestingestTransactionBatch

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 responses

Parameters

NameInTypeDescription
sourcequerystringThe 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.
filenamequerystringThe export's file name, retained on the reconciliation record.

Responses

  • 200The reconciliation record for this fileIngestBatchResult
  • 400—
  • 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.