Skip to main content

Case Management API

Officer-facing endpoints for managing compliance cases throughout their lifecycle. All endpoints are under the /api prefix.

Endpoints Summary

MethodPathPurpose
GET/api/countriesList supported countries
GET/api/templatesList workflow templates
POST/api/casesCreate a new compliance case
GET/api/casesList all cases
GET/api/cases/{workflow_id}Get full case detail
GET/api/cases/{workflow_id}/ai-briefGet AI-generated case brief
GET/api/cases/{workflow_id}/auditGet audit log
POST/api/cases/{workflow_id}/decisionSubmit officer decision
POST/api/cases/{workflow_id}/decision/second-approvalConfirm four-eyes second approval (ADR-0070)
POST/api/cases/{workflow_id}/regenerate-tasksRe-run task generation
POST/api/cases/{workflow_id}/mcc-decisionSubmit MCC classification decision
POST/api/cases/{workflow_id}/reclassify-mccRe-run MCC classification
GET/api/cases/{workflow_id}/documentsList case documents
GET/api/cases/{workflow_id}/documents/downloadDownload a document
GET/api/cases/{workflow_id}/evidenceGet evidence chain
GET/api/cases/{workflow_id}/company-profileGet company profile
GET/api/cases/{workflow_id}/reportDownload PDF compliance report
GET/api/cases/{workflow_id}/responsesGet customer follow-up responses
GET/api/cases/{workflow_id}/case-pack.zipDownload tamper-evident regulator case pack (ADR-0069)
GET/api/cases/{workflow_id}/documents/evidence-request.pdfDownload the evidence-request letter as PDF
GET/api/cases/{workflow_id}/documents/mlro-memo.pdfDownload the MLRO memo as PDF
GET/api/cases/{workflow_id}/documents/source-appendix.pdfDownload the source appendix as PDF
POST/api/cases/{workflow_id}/decision-memorandumDraft an Officer Decision Memorandum
PUT/api/cases/{workflow_id}/decision-memorandum/{memo_id}/draftUpdate a memo draft (justification / concerns)
POST/api/cases/{workflow_id}/decision-memorandum/{memo_id}/suggest-justificationAI-suggest the §8 justification
POST/api/cases/{workflow_id}/decision-memorandum/{memo_id}/signSign / finalize a memo
GET/api/cases/{workflow_id}/decision-memorandum/{memo_id}Get a memo (JSON default, HTML, or ?format=pdf)
GET/api/cases/{workflow_id}/decision-memorandaList memos for a case
POST/api/cases/{case_id}/sarRaise a SAR/STR (draft + goAML artifact)
GET/api/cases/{case_id}/sarGet the SAR(s) for a case
POST/api/cases/{case_id}/sar/{sar_id}/submit-for-mlroSubmit a SAR for MLRO review
POST/api/cases/{case_id}/sar/{sar_id}/assessmentRecord the SAR/STR reportability assessment
POST/api/cases/{case_id}/sar/{sar_id}/mlro-approveMLRO approves the SAR
POST/api/cases/{case_id}/sar/{sar_id}/mlro-rejectMLRO rejects the SAR
POST/api/cases/{case_id}/sar/{sar_id}/record-submissionRecord filing/submission to the FIU
POST/api/cases/{case_id}/sar/{sar_id}/acknowledgeRecord the FIU acknowledgement
DELETE/api/cases/{workflow_id}Delete case (cleanup)

List Countries

GET /api/countries

Returns the list of supported EEA countries for case creation.

Response 200

[
{ "code": "BE", "name": "Belgium" },
{ "code": "NL", "name": "Netherlands" },
{ "code": "DE", "name": "Germany" }
]

List Templates

GET /api/templates

Returns available workflow templates that define document requirements and questions.

Response 200

[
{
"id": "psp_merchant_onboarding",
"name": "PSP Merchant Onboarding",
"description": "Standard KYB onboarding for payment service provider merchants",
"document_count": 5,
"question_count": 3
}
]

Create Case

POST /api/cases

Creates a new compliance case, starts a Temporal workflow, and returns the portal URL for the customer.

At creation time, the system runs concurrent pre-enrichment lookups (VIES, NorthData, Crunchbase, website validation) with a 10-second global timeout. Results are stored in the company profile and passed to the workflow.

Request Body

{
"company_name": "Acme Trading BVBA",
"company_registration_number": "0456789012",
"vat_number": "BE0456789012",
"country": "BE",
"template_id": "psp_merchant_onboarding",
"website_url": "https://acme-trading.be",
"additional_data": {}
}
FieldTypeRequiredDescription
company_namestringYesLegal company name
company_registration_numberstringNoNational registration number. At least one of this or vat_number is required.
vat_numberstringNoVAT number (e.g., BE0456789012). For Belgian companies, the registration number is derived from this if not provided.
countrystringYesISO 3166-1 alpha-2 country code. Must be a supported EEA country.
template_idstringNoWorkflow template ID. Defaults to psp_merchant_onboarding.
website_urlstringNoCompany website URL. Validated and normalized (https:// prepended if missing).
additional_dataobjectNoArbitrary metadata passed to the workflow.

Response 201

{
"case_id": "case_a1b2c3d4e5f6",
"workflow_id": "wf_f6e5d4c3b2a1",
"status": "AWAITING_DOCUMENTS",
"portal_token": "pt_X7kB2mNpQrVsWzAc1dEfGhJu8yLo9",
"portal_url": "http://localhost:3001/portal/pt_X7kB2mNpQrVsWzAc1dEfGhJu8yLo9",
"vies_enrichment": {
"vies_valid": true,
"vies_name": "ACME TRADING",
"vies_address": "RUE DE LA LOI 1, 1000 BRUXELLES"
},
"northdata_enrichment": {
"found": true,
"company_name": "Acme Trading BVBA",
"directors": ["Jan Peeters"]
},
"crunchbase_enrichment": null,
"website_validation": {
"url": "https://acme-trading.be",
"accessible": true,
"status_code": 200
},
"created_at": "2026-02-24T10:30:00Z"
}

:::info Portal Token Format The portal_token is generated with secrets.token_urlsafe(24) (192-bit entropy) and prefixed with pt_. The resulting token is 35 characters: pt_ + 32 URL-safe base64 characters. This format replaced the previous 64-bit truncated UUID used in earlier versions. :::

info

Pre-enrichment results (vies_enrichment, northdata_enrichment, crunchbase_enrichment, website_validation) are best-effort. Any that fail or timeout are returned as null.


List Cases

GET /api/cases

Returns all compliance cases with real-time status from Temporal.

Query Parameters

ParameterTypeDescription
statusstringFilter by case status (e.g., REVIEW_PENDING, APPROVED)

Response 200

{
"cases": [
{
"case_id": "case_a1b2c3d4e5f6",
"workflow_id": "wf_f6e5d4c3b2a1",
"company_name": "Acme Trading BVBA",
"country": "BE",
"status": "REVIEW_PENDING",
"current_iteration": 1,
"created_at": "2026-02-24T10:30:00Z",
"updated_at": "2026-02-24T11:45:00Z",
"latest_risk_score": 0.42
}
],
"total": 1,
"page": 1,
"per_page": 50
}

The status field reflects the live Temporal workflow state, not the database snapshot. If Temporal is unreachable, the database status is used as fallback.


Get Case Detail

GET /api/cases/{workflow_id}

Returns the full case record including workflow state, investigation results, follow-up tasks, MCC classification, and audit log.

Response 200

{
"case_id": "case_a1b2c3d4e5f6",
"workflow_id": "wf_f6e5d4c3b2a1",
"status": "REVIEW_PENDING",
"company_name": "Acme Trading BVBA",
"company_registration_number": "0456789012",
"country": "BE",
"template_id": "psp_merchant_onboarding",
"current_iteration": 1,
"max_iterations": 5,
"portal_token": "pt_X7kB2mNpQrVsWzAc1dEfGhJu8yLo9",
"portal_url": "http://localhost:3001/portal/pt_X7kB2mNpQrVsWzAc1dEfGhJu8yLo9",
"documents": [],
"follow_up_tasks": [],
"investigation_results": [
{
"iteration": 1,
"risk_score": 0.42,
"findings": [],
"discrepancies": [],
"summary": "Investigation complete."
}
],
"generated_tasks": [],
"mcc_classification": {
"primary_recommendation": {
"mcc_code": "5411",
"description": "Grocery Stores, Supermarkets"
},
"risk_tier": "low",
"risk_flags": []
},
"validation_results": [],
"audit_log": [
{
"event": "CASE_CREATED",
"timestamp": "2026-02-24T10:30:00Z"
}
],
"created_at": "2026-02-24T10:30:00Z",
"updated_at": "2026-02-24T11:45:00Z"
}

Get AI Brief

GET /api/cases/{workflow_id}/ai-brief

Returns a deterministic (non-LLM) summary of the case's risk profile, trend analysis, key signals, and recommended action.

Response 200

{
"summary": "Acme Trading BVBA presents LOW risk (0.25) after initial investigation. All checks passed with no discrepancies.",
"risk_trend": "STABLE",
"risk_scores": [0.25],
"key_signals": [
"3 findings, 0 unresolved discrepancies"
],
"recommended_action": "approve",
"generated_at": "2026-02-24T12:00:00Z"
}
FieldValues
risk_trendIMPROVING, STABLE, WORSENING, INSUFFICIENT_DATA
recommended_actionapprove, follow_up, escalate

Submit Officer Decision

POST /api/cases/{workflow_id}/decision

Submits the compliance officer's decision on a case. This sends a Temporal signal to the workflow.

Request Body

{
"decision": "follow_up",
"reason": "Address discrepancy needs clarification",
"follow_up_tasks": [
{
"description": "Please provide a utility bill confirming your registered address",
"response_type": "document",
"document_type": "utility_bill",
"required": true
}
]
}
FieldTypeRequiredDescription
decisionstringYesOne of: approve, approve_with_restrictions, reject, follow_up, escalate
reasonstringNoOfficer's justification for the decision
follow_up_tasksarrayNoRequired when decision is follow_up. List of tasks for the customer.

Follow-up task fields:

FieldTypeDescription
descriptionstringTask description shown to customer
response_typestringtext, document, or yes_no
document_typestringDocument category (for document response type)
requiredbooleanWhether the task is mandatory

Response 200

{
"status": "FOLLOW_UP_REQUIRED",
"message": "Follow-up request sent to customer portal"
}

Decision Gates

The decision is not ungated. Before the officer_decision signal is sent, the endpoint runs a set of fail-closed checks. Each returns HTTP 409 (or parks the case) rather than letting a non-compliant decision through:

GateTriggerBehaviorADR
Maker-checker / four-eyesHigh-risk decision or a fail-closed-gate override, with maker_checker_enabled (default on)Case parks in PENDING_SECOND_APPROVAL; a second, different officer must confirm via POST /decision/second-approval. Both actors are audited.0070
SAR-first customer-contactreject or follow_up (both are customer contact) while a criminal/enforcement/sanctions trigger is present and no completed, signed SAR/STR assessment exists409no officer override (AMLR Art. 73 tipping-off). The only unlock is a completed MLRO SAR/STR assessment; escalate to the MLRO is the intended path.0071
Open-UBO-discrepancyApproving with an unresolved UBO discrepancy409 unless the officer supplies an explicit, audited override_open_discrepancies reason (which itself routes through four-eyes).0059
Dissolved-entityApproving a terminal-status (dissolved) entityBlocked; proceeding requires an audited override justification.0065

Submit Second Approval

POST /api/cases/{workflow_id}/decision/second-approval

Confirms (or rejects) a decision that the maker-checker gate parked in PENDING_SECOND_APPROVAL. The approving officer must be a different actor from the maker (four-eyes, ADR-0070). A valid approve fires the original decision; a reject returns the case for rework. Both actors are recorded in the immutable audit trail.


Regenerate Tasks

POST /api/cases/{workflow_id}/regenerate-tasks

Re-runs the AI task generation agent on the current investigation results. Only available when status is REVIEW_PENDING.

Response 200

{
"tasks": [
{
"description": "Confirm the discrepancy in registered address",
"response_type": "text",
"required": true,
"justification": "KBO shows different address than submitted documents"
}
],
"reasoning": "Address discrepancy detected between official registry and uploaded documents."
}

Submit MCC Decision

POST /api/cases/{workflow_id}/mcc-decision

Officer submits their decision on the AI-generated MCC (Merchant Category Code) classification.

Request Body

{
"action": "accept",
"notes": "MCC classification looks correct"
}
FieldTypeValuesDescription
actionstringaccept, reject, overrideDecision on the MCC classification
override_mccstring--New MCC code (required when action is override)
notesstring--Officer notes

Reclassify MCC

POST /api/cases/{workflow_id}/reclassify-mcc

Re-runs the MCC classification agent with optional officer feedback. Only available when status is REVIEW_PENDING.

Request Body

{
"notes": "This company primarily sells electronics, not groceries"
}

List Documents

GET /api/cases/{workflow_id}/documents

Returns all documents uploaded for this case across all iterations, sourced from MinIO.

Query Parameters

ParameterTypeDescription
sourcestringFilter by customer (uploaded files only) or all (includes system-generated)

Download Document

GET /api/cases/{workflow_id}/documents/download?key={minio_key}

Downloads a specific document from MinIO storage.

Query Parameters

ParameterTypeDescription
keystringThe MinIO object key (returned in document listings)

Response: Binary file content with appropriate Content-Type header.


Get Evidence Chain

GET /api/cases/{workflow_id}/evidence

Returns the evidence chain for the case, including Belgian evidence records with source URLs, timestamps, SHA-256 hashes, and raw data.

Response 200

{
"evidence": [
{
"id": "ev_abc123",
"source": "KBO/BCE",
"source_url": "https://kbopub.economie.fgov.be/...",
"data_hash": "sha256:a1b2c3...",
"collected_at": "2026-02-24T10:31:00Z",
"raw_data": { }
}
],
"bundle_hash": "sha256:d4e5f6..."
}

Get Company Profile

GET /api/cases/{workflow_id}/company-profile

Returns the aggregated company profile with sourced facts from all enrichment sources. Each fact tracks its source, timestamp, and evidence hash. Discrepancies between sources are flagged.

Response 200

{
"case_id": "case_a1b2c3d4e5f6",
"facts": {
"company_name": [
{ "value": "Acme Trading BVBA", "source": "officer_input", "timestamp": "2026-02-24T10:30:00Z" },
{ "value": "ACME TRADING", "source": "EU VIES", "timestamp": "2026-02-24T10:30:01Z", "evidence_hash": "sha256:..." }
]
},
"discrepancies": [],
"evidence_refs": [
{ "source": "EU VIES", "evidence_hash": "sha256:...", "storage": "minio:case_abc/evidence/vies.json" }
]
}

Download PDF Report

GET /api/cases/{workflow_id}/report

Generates and downloads a PDF compliance report. Only available for cases with status APPROVED or REJECTED.

Response: application/pdf binary content with Content-Disposition header.

Error 400: If the case is not in a terminal status.


Regulator Case Pack

GET /api/cases/{workflow_id}/case-pack.zip

Builds and streams the one-click, regulator-ready case pack as a ZIP (application/zip, ADR-0069). The pack bundles the compliance artifacts for the case and carries any honest data gaps forward rather than hiding them.


Compliance Document PDFs

Each renders a single compliance artifact as application/pdf:

PathDocumentSAR-first gated?
/documents/evidence-request.pdfCustomer-facing EDD evidence-request letterYes — customer surface; returns 409 when a criminal/enforcement/sanctions trigger is present and the SAR/STR assessment is not complete (AMLR Art. 73).
/documents/mlro-memo.pdfInternal MLRO escalation memoNo (internal/officer use)
/documents/source-appendix.pdfPrimary-source citation appendixNo (regulator/officer use)

Decision Memorandum

The Officer Decision Memorandum captures the reasoned basis for a decision as a signed, PDF-renderable record.

MethodPathPurpose
POST/cases/{workflow_id}/decision-memorandumDraft a memo
PUT/cases/{workflow_id}/decision-memorandum/{memo_id}/draftSet the §8 justification and optional dissenting concerns
POST/cases/{workflow_id}/decision-memorandum/{memo_id}/suggest-justificationAI-suggest the §8 justification
POST/cases/{workflow_id}/decision-memorandum/{memo_id}/signFinalize (sign) the memo
GET/cases/{workflow_id}/decision-memorandum/{memo_id}Fetch a memo — JSON by default, HTML via Accept: text/html, or PDF via ?format=pdf (or Accept: application/pdf)
GET/cases/{workflow_id}/decision-memorandaList memos for the case

SAR/STR Lifecycle

Suspicious Activity / Transaction Report endpoints. The lifecycle is fail-closed and no-skip (draft → pending MLRO → approved → submitted → acknowledged) and enforces an MLRO four-eyes gate on filing (ADR-0071). See SAR/STR Lifecycle for the full state machine.

:::note Path parameter These routes take the case id as {case_id} (the value returned as case_id on case objects), not the {workflow_id} label used by the other case endpoints above. :::

MethodPathPurpose
POST/cases/{case_id}/sarRaise a SAR → draft + generated goAML report artifact
GET/cases/{case_id}/sarGet the SAR(s) for the case
POST/cases/{case_id}/sar/{sar_id}/submit-for-mlroSubmit the SAR for MLRO review
POST/cases/{case_id}/sar/{sar_id}/assessmentRecord the SAR/STR reportability assessment
POST/cases/{case_id}/sar/{sar_id}/mlro-approveMLRO approves the SAR (four-eyes)
POST/cases/{case_id}/sar/{sar_id}/mlro-rejectMLRO rejects the SAR
POST/cases/{case_id}/sar/{sar_id}/record-submissionRecord filing/submission to the FIU
POST/cases/{case_id}/sar/{sar_id}/acknowledgeRecord the FIU acknowledgement

Get Customer Responses

GET /api/cases/{workflow_id}/responses

Returns customer responses to follow-up tasks, sourced from the task_responses.json file in MinIO.


Delete Case

DELETE /api/cases/{workflow_id}

Deletes a case and all associated data. This:

  1. Terminates the Temporal workflow
  2. Purges all MinIO objects under the case prefix
  3. Deletes all database records (audit events, MCC classifications, PEPPOL verifications, Belgian evidence, agent executions, and the case itself)
warning

This is a destructive operation intended for development and testing. There is no undo.

Response 200

{
"status": "deleted",
"workflow_id": "wf_f6e5d4c3b2a1"
}