Skip to main content
Baobyte AML Suite
Documentation contents

API reference

Cases

13 operations in this area of the contract. Every one declares the permission it requires.

Contract version 0.2.0All areas

Operations

GET/v1/case-queues

Configured case-routing queues

Required permission: cases.assignment.writelistCaseQueues
Show parameters and responses

Parameters

NameInTypeDescription
include_retiredqueryboolean—

Responses

  • 200Tenant case queuesCaseQueue[]
POST/v1/case-queues

Configure an organisational case queue

Required permission: cases.routing.managecreateCaseQueue
Show parameters and responses

Responses

  • 201Queue createdCaseQueue
  • 400—
  • 409—
PATCH/v1/case-queues/{queueID}

Update a live case queue and its priority routes

Required permission: cases.routing.manageupdateCaseQueue
Show parameters and responses

Parameters

NameInTypeDescription
queueIDrequiredpathstring—

Responses

  • 200Queue updatedCaseQueue
  • 404—
  • 409—
GET/v1/case-queues/{queueID}/eligible-assignees

Active tenant users eligible for a queue

Required permission: cases.assignment.writelistEligibleCaseAssignees
Show parameters and responses

Parameters

NameInTypeDescription
queueIDrequiredpathstring—

Responses

  • 200Current eligible users; email addresses are not returned.EligibleCaseAssignee[]
  • 404—
POST/v1/case-queues/{queueID}/retire

Retire a case queue without deleting assignment history

Required permission: cases.routing.manageretireCaseQueue
Show parameters and responses

Parameters

NameInTypeDescription
queueIDrequiredpathstring—

Responses

  • 200Retired queueCaseQueue
  • 404—
GET/v1/cases

Investigation cases

Required permission: cases.case.readlistCases

A metadata-safe caseload register. Each row contains only its opaque id, reference, lifecycle state, priority and opened/SLA/closed times. It never returns the title, narrative summary, subject/customer identity, names, outcome, assignee or actor ids. Those investigation details require `cases.case_content.read` on the case workspace.

Show parameters and responses

Parameters

NameInTypeDescription
statusqueryopen | investigating | pending_approval | closed_no_action | closed_reported | reopenedAn EXACT match on `case_record.status`, not a category. This list said `[open, in_progress, pending_approval, closed]` until a test walked the request bodies and query parameters as well as the schemas: `in_progress` and `closed` are not values the column has ever held, so a UI dropdown built from this contract had two options that returned an empty page forever and read as "no cases".
party_idquerystringNarrows the case list to the cases raised against one party, matched on the case's own subject rather than looked up per row. This is what a customer profile asks for; without it the only way to show one customer's case history is to read the whole case list and filter client-side, which silently goes wrong at the first page boundary. A value that is not a UUID is a 400, not an ignored filter.
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

  • 200CasesCasePage
POST/v1/cases

Escalate alerts or screening hits into a case

Required permission: cases.case.writeopenCase

The case subject is taken from the evidence rather than from the request body, so an analyst cannot open a case against the wrong customer by mistyping. Alerts lead when both kinds are present — an alert names an account and a transaction as well as a party — and a screening hit supplies the subject when there is no alert to take it from.

`alert_ids` and `screening_hit_ids` may name at most 200 records in total. Correlation groups are expanded before any write and the expanded combined set must remain within that boundary; overflow is a 422 and no case is created.

Show parameters and responses

Responses

  • 201The caseCase
  • 400—
  • 422—
GET/v1/cases/{caseID}

The case workspace

Required permission: cases.case_content.readgetCase

One response distinguishes the immutable evidence linked to this case from the wider history of the same subject. `alerts`, `screening_hits`, `attachments` and `transactions` remain linked filing provenance. Each linked collection is bounded at 200 with an explicit `truncated: true`/`service_cannot_resume` marker; it is never a silent partial array. The four broader subject collections (alerts, transactions, screening runs and screening hits) are independently keyset-resumable investigation context with independent read permissions and explicit coverage. Paging one collection never moves any other collection, and transaction cursors are valid only with the exact half-open window echoed by the response.

Linked `transactions` deliberately remains a bounded, explicitly truncated filing- provenance view. `subject_transaction_history` is the resumable traversal path for the subject's activity inside the selected window. Its database work is a bounded k-way account merge; customers with more than 200 accounts get `unavailable_account_limit` and no misleading partial activity.

A broader-history key is ABSENT when its permission is absent. When permission is present but the case has no usable subject/customer/account, the key is present with an `unavailable_*` or `available_no_accounts` coverage value. An empty `available` list therefore means the query really ran and found no rows; it never means "not allowed".

Show parameters and responses

Parameters

NameInTypeDescription
caseIDrequiredpathstring—
notes_limitqueryintegerPage size for the `notes` sub-collection of the case workspace, capped at 200.
notes_cursorquerystringOpaque keyset cursor for the `notes` sub-collection, taken from `notes.next_cursor` on a previous response. Prefixed rather than reusing `cursor` because this response pages TWO children independently. One shared `cursor` would apply a position taken from the timeline to the notes, and the notes would come back from the wrong place under the right key — wrong data presented as right, which is worse than an error. Following it re-reads the case header and the linked alerts as well. That is the stated cost of paging a workspace through its own route instead of adding a second door onto encrypted case notes, whose decryption is a logged PII access.
events_limitqueryintegerPage size for the `events` sub-collection of the case workspace, capped at 200.
events_cursorquerystringOpaque keyset cursor for the `events` (timeline) sub-collection, taken from `events.next_cursor` on a previous response.
subject_alerts_limitqueryintegerPage size for broader subject alerts, capped at 200.
subject_alerts_cursorquerystringOpaque `(opened_at,id)` keyset cursor from `subject_alert_history.next_cursor`. It moves only the alert history; every other case-workspace cursor is independent.
subject_transactions_limitqueryintegerPage size for broader subject transactions, capped at 200.
subject_transactions_cursorquerystringOpaque `(booking_ts,id)` keyset cursor from `subject_transaction_history.next_cursor`. Resubmit the exact echoed `subject_transactions_from` and `subject_transactions_to` values with it.
subject_transactions_fromquerystringInclusive RFC3339 start of the broader subject transaction window. Defaults to one calendar year before `subject_transactions_to`; the window may not exceed 366 days.
subject_transactions_toquerystringExclusive RFC3339 end of the broader subject transaction window. Defaults to request time. The response echoes both exact instants so the bounded coverage is reproducible.
subject_screening_runs_limitqueryintegerPage size for broader subject screening runs, capped at 200.
subject_screening_runs_cursorquerystringOpaque `(requested_at,id)` keyset cursor from `subject_screening_history.runs.next_cursor`.
subject_screening_hits_limitqueryintegerPage size for broader subject screening hits, capped at 200.
subject_screening_hits_cursorquerystringOpaque `(created_at,id)` keyset cursor from `subject_screening_history.hits.next_cursor`.

Responses

  • 200The workspaceCaseDetail
  • 400—
  • 403—
  • 404—
POST/v1/cases/{caseID}/assignment

Route a case to a queue or eligible investigator

Required permission: cases.assignment.writeassignCase

The queue snapshots its owning organisational unit onto the case. If an assignee is supplied they must currently be active, belong to that exact unit and hold at least one eligible role configured on the queue. Omitting the assignee leaves work owned by the queue without inventing an individual owner.

Show parameters and responses

Parameters

NameInTypeDescription
caseIDrequiredpathstring—

Responses

  • 200Current assignment snapshotCaseAssignment
  • 404—
  • 409—
POST/v1/cases/{caseID}/attachments

Attach immutable investigation evidence

Required permission: cases.evidence.writeuploadCaseAttachment

Encrypts the bytes and original filename under the case subject's DEK before writing to the configured write-once object store. The object key contains no case, person, filename or user data. Reusing the UUID `Idempotency-Key` for the same attachment is safe; reusing it for different bytes or metadata is refused.

Legacy `?filename=` query metadata is refused even when `X-Filename` is also present, because silently ignoring it would leave the sensitive value in upstream access logs.

Show parameters and responses

Parameters

NameInTypeDescription
caseIDrequiredpathstring—
Idempotency-Keyrequiredheaderstring—
X-FilenamerequiredheaderstringSensitive display metadata encrypted before storage and never used as an object path. It MUST be redacted from gateway/application logs and MUST NOT be copied into a URL or query string, which access infrastructure commonly records before the handler can redact it. The value must be valid UTF-8, at most 255 bytes, and contain no control character or `/` or `\` path separator.

Responses

  • 201Immutable encrypted evidence metadataCaseAttachment
  • 400—
  • 404—
  • 409—
  • 413Attachment exceeds the 20 MiB evidence limit.
  • 415The declared type is unsupported or does not match the bytes.
  • 422—
GET/v1/cases/{caseID}/attachments/{attachmentID}/content

Download and audit immutable case evidence

Required permission: cases.evidence.readdownloadCaseAttachment

Loads the tenant-scoped metadata first, decrypts the opaque object reference and bytes, verifies the stored plaintext SHA-256, and records both PII access and download audit events. The response is always an attachment with `no-store` and `nosniff` headers.

Show parameters and responses

Parameters

NameInTypeDescription
caseIDrequiredpathstring—
attachmentIDrequiredpathstring—

Responses

  • 200Original verified evidence bytes
  • 404—
POST/v1/cases/{caseID}/close

Propose closing a case

Required permission: cases.case.closeproposeCaseClosure

This does not close the case. It records a maker-checker proposal and moves the case to `pending_approval`; a different, role-authorised user approves it through `/v1/approvals/{approvalID}/approve`, and the closure is applied inside that approval's transaction.

The two-step flow is the control. Until Phase 2 the closer named their own approver in this request body, with no foreign key and no check that the named person had agreed to anything — the four-eyes rule was self-attested. The proposal payload is digest-bound, so editing the outcome after review invalidates the approval rather than riding on it.

Which roles may approve a case closure is `workflow.approvals` configuration, not code (BRD §7.2).

Show parameters and responses

Parameters

NameInTypeDescription
caseIDrequiredpathstring—

Responses

  • 202The approval request awaiting a second pair of eyesApprovalRequest
  • 400—
  • 409The case is already closed, or a closure proposal is already pending on it.
  • 422—
POST/v1/cases/{caseID}/notes

Record an investigation note

Required permission: cases.case.writeaddCaseNote

Notes are encrypted at rest under the subject's key and are append-only. An investigation record that can be edited afterwards is not evidence of anything.

Show parameters and responses

Parameters

NameInTypeDescription
caseIDrequiredpathstring—

Responses

  • 201The noteCaseNote
  • 400—
  • 422—