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}/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
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, 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"
}

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.


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"
}