Cases
Cases are compliance work items in the API key's environment, opened by a person or automatically by screening, and usually linked to a transfer. For the case lifecycle, types and categories, see Cases.
Access
Section titled “Access”Reading needs the cases:read scope; every change, the cases:write scope. The key's owner's role decides the rest:
| Action | Role permission |
|---|---|
| List and read cases, notes and history | cases:view |
| Open a case | cases:create |
| Change a case, add a note | cases:update |
| Set or clear the assignee | cases:assign |
| Resolve a case | cases:resolve |
Linking a transfer, and seeing a case's transfers, also need transfers:read. A key without cases:read that changes a case gets a receipt, { "id", "caseNumber", "status" }, instead of the case.
A role without pii:view (an analyst) gets a case's free text as ***: its title, description and resolution note, its notes, and history values that are not codes or IDs, with meta.piiMasked: true. It searches by case number only. Staff (assignees, authors) are named to everyone who can read cases.
Endpoints
Section titled “Endpoints”| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/cases |
cases:read |
List cases |
| POST | /api/v2/cases |
cases:write |
Open a case |
| GET | /api/v2/cases/:id |
cases:read |
Get a case |
| PATCH | /api/v2/cases/:id |
cases:write |
Change a case |
| GET | /api/v2/cases/:id/notes |
cases:read |
List a case's notes |
| POST | /api/v2/cases/:id/notes |
cases:write |
Add a note |
| GET | /api/v2/cases/:id/history |
cases:read |
List a case's history |
| POST | /api/v2/cases/:id/resolve |
cases:write |
Resolve a case |
A case of the other environment is 404 CASE_NOT_FOUND.
List Cases
Section titled “List Cases”curl "$COVALENT_URL/api/v2/cases?status=open&assigneeId=me" \ -H "x-api-key: $COVALENT_API_KEY"| Parameter | Description |
|---|---|
status |
open or closed. |
priority |
low, medium or high. |
type |
A case type: compliance_review, sanctions_hit, provider_rejection, manual_escalation, threshold_breach or kyc_failure. |
assigneeId |
A member's ID, or me for the key's owner. |
search |
The case number; also the title and description for a role with pii:view. 1 to 100 characters. |
hasTransfer |
true for cases with a linked transfer, false for cases without one. |
limit |
Items per page, 1 to 200, default 50. |
cursor |
meta.cursor of the previous page. |
Any other parameter or value is 400 INVALID_QUERY. Cases are listed newest first:
{ "id": "case_id", "caseNumber": "CASE-00042", "title": "Review outgoing transfer", "description": null, "type": "manual_escalation", "category": "aml", "priority": "high", "status": "open", "resolution": null, "resolutionNote": null, "assigneeId": "user_id", "assignee": { "id": "user_id", "name": "Jordan Lee", "email": "jordan@example.com" }, "ruleId": null, "transferIds": ["transfer_id"], "source": "manual", "noteCount": 1, "createdById": "user_id", "createdAt": "2026-10-09T10:00:00.000Z", "updatedAt": "2026-10-09T11:00:00.000Z"}Open a Case
Section titled “Open a Case”curl -X POST "$COVALENT_URL/api/v2/cases" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Review outgoing transfer", "type": "manual_escalation", "priority": "high", "transferId": "transfer_id" }'| Field | Required | Description |
|---|---|---|
title |
Yes | 1 to 200 characters. |
type |
Yes | A case type. |
description |
No | At most 2,000 characters. |
category |
No | aml, sanctions, kyc, fraud or threshold. |
priority |
No | low, medium or high. |
transferId |
No | A transfer of the key's environment (422 TRANSFER_NOT_FOUND otherwise). |
ruleId |
No | A compliance rule of the key's environment (422 RULE_NOT_FOUND otherwise). |
assigneeId |
No | An active member whose role can view cases (422 INVALID_ASSIGNEE otherwise). Needs cases:assign. |
No other field is accepted. The answer is 201 with the case in detail (see below). A body that fails validation is 422 VALIDATION_ERROR, with details.issues.
Get a Case
Section titled “Get a Case”curl "$COVALENT_URL/api/v2/cases/case_id" \ -H "x-api-key: $COVALENT_API_KEY"The case as listed, plus:
| Field | Description |
|---|---|
resolvedAt, resolvedById |
When and by whom the case was resolved, or null. |
createdBy, resolvedBy |
Those members, named, or null. |
rule |
The compliance rule that opened the case (id, name, category, action, severity), or null. |
transfers |
The linked transfers (id, externalId, direction, state, complianceStatus, amount, asset, network, createdAt), for a key with transfers:read. transferIds lists them all. |
notes, history |
The 20 latest of each, newest first. The notes and history endpoints page through them all. |
historyCount |
How many history entries the case has. |
Change a Case
Section titled “Change a Case”curl -X PATCH "$COVALENT_URL/api/v2/cases/case_id" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "assigneeId": "user_id", "priority": "medium" }'Send any of title, description, type, category, priority, assigneeId, ruleId and transferId. null clears assigneeId, ruleId or transferId; a new transferId replaces every transfer linked to the case. Setting assigneeId needs cases:assign; any other field, cases:update. The status changes only through resolution.
The answer is the case in detail. A closed case is not changed: 409 CASE_CLOSED. A body with no field is 422 VALIDATION_ERROR.
curl -X POST "$COVALENT_URL/api/v2/cases/case_id/notes" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Called the customer" }'content is 1 to 5,000 characters. The answer is 201 with the note: id, content, authorId, author and createdAt. A closed case still takes notes.
GET /api/v2/cases/:id/notes lists a case's notes, newest first, with limit and cursor.
History
Section titled “History”GET /api/v2/cases/:id/history lists a case's history, newest first, with limit and cursor. Each entry has id, action, field, oldValue, newValue, userId, user and createdAt.
| Action | Meaning |
|---|---|
created, updated, assigned, note_added, resolved |
A change to the case. field, oldValue and newValue say what changed. |
transfer_updated |
Resolving the case moved its held transfer: newValue is the state it reached. |
transfer_not_updated |
Resolving the case could not move its held transfer: newValue is resume_refused or reject_refused. |
str_drafted |
Resolving the case drafted a suspicion report: newValue is the report's ID. Shown only to a key that may read suspicion reports (reports:read and strs:read). |
Resolve a Case
Section titled “Resolve a Case”curl -X POST "$COVALENT_URL/api/v2/cases/case_id/resolve" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "resolution": "rejected", "resolutionNote": "Structured payments" }'| Field | Required | Description |
|---|---|---|
resolution |
Yes | approved or rejected. |
resolutionNote |
No | At most 2,000 characters. |
The case is closed, and the answer is the closed case. If its first linked transfer is held, resolving acts on it after the answer: approval resumes the transfer, and rejection rejects it and drafts a suspicion report when the key may (strs:write). meta.transferAction says what happened:
meta.transferAction |
Meaning |
|---|---|
null |
No linked transfer, or it was not held. |
{ "transferId", "result": "queued" } |
The transfer will be resumed or rejected. The case's history records the outcome. |
{ "transferId", "result": "skipped", "reason": "insufficient_permission" } |
The key may not act on transfers (transfers:write): the transfer stays held. |
{ "transferId", "result": "skipped", "reason": "not_rejectable" } |
Resolved as rejected, but the transfer no longer awaits an officer: it is not rejected, and no report is drafted. |
A case that is already closed is 409 CASE_CLOSED. Of two resolutions sent at once, one closes the case and the other gets 409.
A case opened, changed or resolved through the API or the dashboard sends a case.created, case.updated or case.resolved webhook event. Adding a note sends none.