API Reference
The Covalent REST API is served under /api/v2 on your company's host. Every endpoint authenticates with an API key in the x-api-key header, and every answer, success or failure, comes in one JSON envelope.
Base URL
Section titled “Base URL”Covalent serves each company from its own host, <company>.<app-domain>: the address your dashboard opens at. The host chooses the company. A request to a host that names no company is 404 NOT_FOUND.
The examples in these pages read the host and the key from two shell variables:
export COVALENT_URL="https://<company>.<app-domain>"export COVALENT_API_KEY="covalence_pk_..."
curl "$COVALENT_URL/api/v2/customers" \ -H "x-api-key: $COVALENT_API_KEY"The API is for server-to-server calls. It sends no CORS headers, so browsers cannot call it from another origin.
Response Envelope
Section titled “Response Envelope”A success carries the resource in data, and meta:
{ "data": { "id": "wallet_id", "deleted": true }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}meta.apiVersion is always 2, and meta.timestamp is when the answer was made. An endpoint adds its own fields to meta after these two: a list adds its page, a transfer action the state it left, and an answer with masked personal data piiMasked: true.
data is the resource itself, not wrapped in its name: a wallet is data, not data.wallet. The one exception is creating or changing a webhook subscription, which answers data.webhook, beside data.secret when a new signing secret is issued.
A failure carries a message, a machine-readable code, details where there are any, and the same meta:
{ "error": "Transfer not found: transfer_id", "code": "TRANSFER_NOT_FOUND", "details": { "resource": "transfer", "id": "transfer_id" }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}erroris a sentence for people. Match oncode.detailsis present only where it adds something: the issues found in a query or body, the transfers that block an erasure, the actions an endpoint takes.- The body has no retry flag. A failure that a later retry may fix (
429, and some503) carries aRetry-Afterheader with the seconds to wait.
The only answer that is not JSON is a successful report download, which is the file itself. See Reports.
For every status and code, see Status Codes.
Headers
Section titled “Headers”| Header | Sent with | Meaning |
|---|---|---|
api-version: 2 |
Every answer, file downloads included | The API version. |
cache-control: no-store |
Every answer | Answers are never cached: some carry a signing secret. |
Retry-After |
429, and a 503 that a retry may fix |
Seconds to wait before retrying. |
Allow |
405, and OPTIONS |
The methods the path takes. |
x-ratelimit-* |
A 429 from a rate limit |
See Rate Limits. |
x-content-sha256 |
A report download | The file's SHA-256, in hex. |
Every answer also carries security headers: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Strict-Transport-Security, and Content-Security-Policy: default-src 'none'; frame-ancestors 'none'.
Requests
Section titled “Requests”- Send JSON bodies with
Content-Type: application/json. A body that is not JSON is400 INVALID_JSON. An empty body reads as{}. - A body is at most 64 KiB (65,536 bytes). A larger one is
413 BODY_TOO_LARGE. - No text may contain the NUL character (U+0000), in the path, the query or the body:
400 VALIDATION_ERROR. - A query parameter sent twice is read by its first value.
- A path the API does not have is
404 NOT_FOUND. A method a path does not take is405 METHOD_NOT_ALLOWED, withAllow.OPTIONSanswers204withAllow.HEADis answered asGETwithout the body, except on a report download, where it is405. - A
POST,PUT,PATCHorDELETEthat carries the dashboard's session cookie is refused with403 CSRF_VALIDATION_FAILED, whatever key it carries.
Environments
Section titled “Environments”Each API key belongs to one environment, test or live, chosen when the key is created. A request reads and writes that environment only: a record of the other environment is not found, and the answer does not say that it exists. No header or parameter chooses the environment.
Thresholds are the exception: one set applies to both environments. Changes that affect both environments (enabling a provider or an integration, changing a threshold) need a live key, else 403 LIVE_KEY_REQUIRED.
Personal Data
Section titled “Personal Data”What a key sees follows its owner's role. A role without the pii:view permission (an analyst) gets personal data masked: customer names as initials, free text as ***, screening vendors' raw responses as null. Such an answer carries meta.piiMasked: true. Searches cannot reveal masked text: such a role searches customers by externalId and cases by case number only.
Lists and Paging
Section titled “Lists and Paging”Lists answer one page at a time. Ask for a page size with limit, and for the next page with cursor:
curl "$COVALENT_URL/api/v2/transfers?limit=2" \ -H "x-api-key: $COVALENT_API_KEY"{ "data": [ { "id": "transfer_2", "state": "queued" }, { "id": "transfer_1", "state": "completed" } ], "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z", "cursor": "c1.1791540000000.transfer_1", "hasMore": true, "total": 7, "limit": 2 }}| Field | Meaning |
|---|---|
meta.cursor |
Send it as ?cursor= for the next page. null on the last page. |
meta.hasMore |
Whether another page follows. |
meta.total |
How many items match the filters, across all pages. |
meta.limit |
The page size used. |
- A cursor is opaque. Send
meta.cursoras you received it, URL-encoded. Most lists' cursors have the formc1.<milliseconds>.<id>: they name a position, so paging neither skips nor repeats an item, even while items are added. - Only a
meta.cursorthat the same list handed out is a cursor. On every list but/thresholds, whose cursor is a threshold's ID, an item's ID is not a cursor: it is refused with400 INVALID_CURSOR. A value that is not shaped like a cursor at all may be refused with400 INVALID_QUERYinstead. limitmust be a whole number from 1 to the list's maximum. Anything else (abc,0,-5,7.9, a number over the maximum) is400 INVALID_QUERY, anddetails.issuesnames each problem. The VASP search alone reads alimitover its maximum as the maximum.- A list refuses a parameter it does not take (
?offset=10, a misspelt filter) and a filter value it does not know:400 INVALID_QUERY. The rules, thresholds, VASP search and asset lists ignore parameters they do not take. - An empty value (
?cursor=,?limit=) is the same as none.
| List | Order | Default limit |
Maximum | Paging |
|---|---|---|---|---|
/transfers, /customers, /wallets |
Newest first | 50 | 100 | cursor |
/rules |
Newest first | 50 | 100 | cursor |
/cases, /cases/:id/notes, /cases/:id/history |
Newest first | 50 | 200 | cursor |
/reports |
Newest first | 50 | 200 | cursor |
/webhooks, /webhooks/:id/deliveries |
Newest first | 50 | 200 | cursor |
/activities, /audit-logs |
Newest first | 50 | 200 | cursor |
/thresholds |
Jurisdiction, then type | 50 | 100 | cursor (the last threshold's ID) |
/vasps/search |
Name | 20 | 50 (a larger limit is read as 50) |
cursor |
/assets |
Catalog order | 50 | 200 | offset |
Endpoints
Section titled “Endpoints”Each endpoint needs the scope shown on the API key, and the key's owner needs the matching role permission. See Authentication.
Customers and Wallets
Section titled “Customers and Wallets”| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/customers |
customers:read |
List customers |
| POST | /api/v2/customers |
customers:write |
Create a customer |
| POST | /api/v2/customers/:id/deactivate |
customers:write |
Deactivate a customer |
| POST | /api/v2/customers/:id/reactivate |
customers:write |
Reactivate a customer |
| POST | /api/v2/customers/:id/erase |
customers:erase |
Irreversibly erase a customer's personal data |
| PUT | /api/v2/customers/:id/travel-rule-identity |
customers:write |
Set the customer's attested Travel Rule identity |
| GET | /api/v2/wallets |
wallets:read |
List wallets |
| POST | /api/v2/wallets |
wallets:write |
Register a wallet |
| GET | /api/v2/wallets/:id |
wallets:read |
Get a wallet |
| PATCH | /api/v2/wallets/:id |
wallets:write |
Change a wallet's label |
| DELETE | /api/v2/wallets/:id |
wallets:write |
Remove a wallet |
| POST | /api/v2/wallets/verify |
wallets:verify |
Check whether an address is a registered wallet |
Transfers
Section titled “Transfers”| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/transfers |
transfers:read |
List transfers |
| POST | /api/v2/transfers |
transfers:write |
Create an outgoing transfer |
| GET | /api/v2/transfers/:id |
transfers:read |
Get a transfer |
| PATCH | /api/v2/transfers/:id |
transfers:write |
Accept, reject or retry a transfer |
| GET | /api/v2/transfers/:id/information-requests |
transfers:read |
Read a transfer's information requests |
| POST | /api/v2/transfers/:id/information-requests |
transfers:write |
Act on a transfer's information requests |
| GET | /api/v2/vasps/search |
transfers:read |
Search counterparty VASPs |
| GET | /api/v2/assets |
transfers:read |
List the asset catalog |
Compliance
Section titled “Compliance”| 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 |
| GET | /api/v2/report-types |
reports:read, strs:read |
Report types by kind and jurisdiction |
| GET | /api/v2/reports |
reports:read, strs:read |
List reports |
| GET | /api/v2/reports/:id |
reports:read, strs:read |
Get a report |
| GET | /api/v2/reports/:id/download |
reports:read, strs:read, transfers:read |
Download a report as a PDF |
| GET | /api/v2/rules |
rules:read |
List compliance rules |
| POST | /api/v2/rules |
rules:write |
Create a rule |
| GET | /api/v2/rules/:id |
rules:read |
Get a rule |
| PATCH | /api/v2/rules/:id |
rules:write |
Change a rule |
| DELETE | /api/v2/rules/:id |
rules:write |
Deactivate a rule |
| GET | /api/v2/thresholds |
thresholds:read |
List regulatory thresholds |
| POST | /api/v2/thresholds |
thresholds:write |
Create a threshold (live key) |
| GET | /api/v2/thresholds/:id |
thresholds:read |
Get a threshold |
| PATCH | /api/v2/thresholds/:id |
thresholds:write |
Change a threshold (live key) |
Connections and Webhooks
Section titled “Connections and Webhooks”| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/providers |
providers:read |
List Travel Rule providers and their settings |
| GET | /api/v2/providers/:name |
providers:read |
Get a provider |
| PUT | /api/v2/providers/:name |
providers:write |
Save a provider's credentials |
| PATCH | /api/v2/providers/:name |
providers:write |
Enable or disable a provider (live key) |
| POST | /api/v2/providers/:name/test |
providers:write |
Test a provider's connection |
| GET | /api/v2/integrations |
integrations:read |
List screening integrations and their settings |
| GET | /api/v2/integrations/:name |
integrations:read |
Get an integration |
| PUT | /api/v2/integrations/:name |
integrations:write |
Save an integration's credentials |
| PATCH | /api/v2/integrations/:name |
integrations:write |
Enable or disable an integration (live key) |
| POST | /api/v2/integrations/:name/test |
integrations:write |
Test an integration's connection |
| GET | /api/v2/webhooks |
webhooks:read |
List webhook subscriptions |
| POST | /api/v2/webhooks |
webhooks:write |
Create a subscription |
| GET | /api/v2/webhooks/:id |
webhooks:read |
Get a subscription, its stats and latest deliveries |
| PATCH | /api/v2/webhooks/:id |
webhooks:write |
Change a subscription or rotate its secret |
| POST | /api/v2/webhooks/:id/test |
webhooks:write |
Send a test event |
| GET | /api/v2/webhooks/:id/deliveries |
webhooks:read |
List a subscription's deliveries |
| POST | /api/v2/webhooks/:id/deliveries/:deliveryId/retry |
webhooks:write |
Send a failed delivery again |
Activity and Audit
Section titled “Activity and Audit”| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /api/v2/activities |
activity:read |
Read the activity feed |
| GET | /api/v2/audit-logs |
audit:read |
Read the audit log |
Dashboard Only
Section titled “Dashboard Only”These are done in the dashboard, not through the API: creating and revoking API keys, managing team members, promoting test rules to live, drafting, editing, submitting and filing reports, cancelling a queued transfer, and adding or revoking wallet ownership proofs.