Customers
Customers are your end users: the parent records of wallets, and the originators of outgoing transfers. Each customer belongs to the API key's environment, and gets a Veriscope keypair when it is created; only the public key is ever returned.
Endpoints
Section titled “Endpoints”| 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 |
Erase a customer's personal data |
| PUT | /api/v2/customers/:id/travel-rule-identity |
customers:write |
Set the customer's attested Travel Rule identity |
A customer of the other environment is not found: 404 CUSTOMER_NOT_FOUND.
List Customers
Section titled “List Customers”curl "$COVALENT_URL/api/v2/customers?limit=50" \ -H "x-api-key: $COVALENT_API_KEY"Query parameters:
| Parameter | Description |
|---|---|
includeDeactivated |
true adds deactivated and erased customers; false, the default, lists active customers only. |
search |
Part of the externalId. A key whose owner's role has pii:view also searches names. |
limit |
Items per page, 1 to 100, default 50. |
cursor |
meta.cursor of the previous page. |
Any other parameter or value is 400 INVALID_QUERY. status is not a parameter of this list.
{ "data": [ { "id": "customer_id", "externalId": "cust_123", "name": "Jane Doe", "publicKey": "02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f", "environment": "live", "walletCount": 1, "createdAt": "2026-10-08T16:00:00.000Z", "updatedAt": "2026-10-08T16:00:00.000Z", "deactivatedAt": null, "erasedAt": null } ], "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z", "cursor": null, "hasMore": false, "total": 1, "limit": 50 }}| Field | Description |
|---|---|
id |
The customer's ID. |
externalId |
Your reference for the customer, or null. |
name |
The customer's name, or null. Masked to initials (J*** D***) for a role without pii:view, with meta.piiMasked: true. |
publicKey |
The customer's Veriscope public key, or null. |
environment |
test or live: the key's. |
walletCount |
How many wallets are registered to the customer. |
deactivatedAt |
When the customer was deactivated, or null while active. |
erasedAt |
When the customer's personal data was erased, or null. |
Create Customer
Section titled “Create Customer”curl -X POST "$COVALENT_URL/api/v2/customers" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "externalId": "cust_123", "name": "Jane Doe", "metadata": { "segment": "retail" } }'| Field | Required | Description |
|---|---|---|
externalId |
No | Your reference: text of at most 255 characters, unique in the environment. |
name |
No | Text of at most 255 characters. |
metadata |
No | Any JSON. |
The answer is 201 with the customer itself:
{ "data": { "id": "customer_id", "externalId": "cust_123", "name": "Jane Doe", "publicKey": "02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f", "environment": "live", "createdAt": "2026-10-09T12:00:00.000Z" }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}The private key is encrypted and stored, and never returned.
An externalId already used in the environment is refused, whether its customer is active or deactivated:
{ "error": "Customer with externalId \"cust_123\" already exists in live environment", "code": "EXTERNAL_ID_IN_USE", "details": { "customerId": "customer_id", "deactivated": false }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}A body that is not a JSON object, or an externalId or name that is not text of at most 255 characters, is 400 VALIDATION_ERROR.
Deactivate Customer
Section titled “Deactivate Customer”curl -X POST "$COVALENT_URL/api/v2/customers/customer_id/deactivate" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "Customer account closed" }'The body may be empty. A reason is kept to its first 1,000 characters.
{ "data": { "id": "customer_id", "deactivatedAt": "2026-10-09T12:00:00.000Z", "deactivatedBy": "user_id", "deactivationReason": "Customer account closed" }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}Deactivation is reversible. The customer keeps its data and transfer history, but it leaves the default list, cannot originate a transfer and cannot take a new wallet. A customer that is already deactivated is answered as it is, with id and deactivatedAt only.
Reactivate Customer
Section titled “Reactivate Customer”curl -X POST "$COVALENT_URL/api/v2/customers/customer_id/reactivate" \ -H "x-api-key: $COVALENT_API_KEY"The answer is { "id": "customer_id", "deactivatedAt": null }, also for a customer that was already active. An erased customer cannot be reactivated: 409 CUSTOMER_ERASED.
Erase Customer
Section titled “Erase Customer”curl -X POST "$COVALENT_URL/api/v2/customers/customer_id/erase" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "Verified erasure request" }'Erasure answers a data subject's request (GDPR Article 17). It needs the customers:erase scope and is irreversible:
reasonis required (400 REASON_REQUIREDwithout it), and kept to 1,000 characters.- The customer's personal data (name, reference, keys, metadata) is nulled in place.
- The customer is deactivated, and its transfers stay linked to it.
{ "data": { "id": "customer_id", "erasedAt": "2026-10-09T12:00:00.000Z", "erasedBy": "user_id", "deactivatedAt": "2026-10-09T12:00:00.000Z" }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}A customer that is already erased answers 200 with its id, its erasedAt and alreadyErased: true.
Erasure is refused with 409 while an obligation holds the records:
| Code | When | Details |
|---|---|---|
LEGAL_HOLD |
A legal hold or a statutory retention obligation covers the customer's records. The answer does not say which. | None |
IN_FLIGHT |
The customer has transfers that have not reached a final state. Wait for them to settle. | activeTransferIds |
{ "error": "Cannot erase customer with 2 in-flight transfer(s). Wait for them to settle or cancel them first.", "code": "IN_FLIGHT", "details": { "activeTransferIds": ["transfer_1", "transfer_2"] }, "meta": { "apiVersion": 2, "timestamp": "2026-10-09T12:00:00.000Z" }}Travel Rule Identity
Section titled “Travel Rule Identity”A customer's Travel Rule identity is the IVMS101 data your KYC or KYB process has attested. Covalent sends it to counterparties on the customer's behalf:
- as the beneficiary of an incoming transfer, through Notabene and Global Travel Rule; CodeVASP checks the beneficiary names the originator sent against it;
- as the originator of an outgoing transfer sent through Notabene, Global Travel Rule or Sygna Bridge.
Without a verified identity, that data is not sent, and the transfer cannot complete its exchange through that provider. A customer's name is not evidence of identity, and is never sent in its place.
curl -X PUT "$COVALENT_URL/api/v2/customers/customer_id/travel-rule-identity" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "verified", "persons": [ { "naturalPerson": { "name": { "nameIdentifier": [ { "primaryIdentifier": "Doe", "secondaryIdentifier": "Jane", "nameIdentifierType": "LEGL" } ] }, "countryOfResidence": "US" } } ] }'| Field | Required | Description |
|---|---|---|
status |
Yes | verified, unverified or blocked. Only a verified identity is sent to counterparties. |
persons |
Yes | IVMS101 persons, 1 to 20: one natural person, or a legal person followed by its natural-person representatives. |
No other field is accepted. The answer is { "id": "customer_id", "status": "verified" }. The identity is encrypted before it is stored and is never returned; the audit log records its status only.
- The customer must be active: a deactivated or erased customer is
404 CUSTOMER_NOT_FOUND. - An invalid identity is
400 VALIDATION_ERROR, withdetails.issuesnaming each problem. - A customer whose
metadatais not a JSON object cannot hold an identity:409 METADATA_NOT_OBJECT. - A concurrent change to the customer's metadata is
409 CONFLICT: retry. - Only this endpoint sets an identity. A
travelRuleIdentitykey sent in a customer'smetadatais not an attestation: the customer has no usable identity until this endpoint sets one.
For every customer error code, see Status Codes.