Skip to main content
Every refusal from /dapi/v1 has the same body. code is a stable lowercase slug — branch on it, and never on message.
Every response — success or failure — carries an X-Request-ID header. Log it on anything you cannot resolve yourself; it is the first thing support asks for.

Field errors

A 422 validation_failed names every field that failed, in wire spelling, with the validator’s own code:
details.location appears when the failure is not in the body — query, path or header. Unknown keys are rejected rather than ignored, so a typo in a field name is a 422 with code: "extra_forbidden", not a silently dropped value.

Status codes

Request and transport

Authentication and signing

Developer keys

A platform session is established by one developer key, and every /dapi/v1 request re-reads it: the key must still exist, the request must arrive from an address inside the key’s allowlist, and the route must be one the key’s scopes cover. All three checks are repeated on POST /dapi/v1/auth/refresh and again immediately before HEVN co-signs, so deleting a key stops the sessions it already minted rather than waiting for them to expire. The last two come from the key-management routes under /api/v1, which take an ordinary owner session and carry the ordinary API’s own error shape. Your integration never calls them: keys are created and deleted in the HEVN app. They are deliberately absent from /dapi/v1.

Acting as a client

Clients

Rails and payins

Payouts

Booking a payout — to a bank account or to a wallet — and the contacts it pays. The refusals that arrive at confirm are in Funding a payment below. Four more slugs are in the published enum and never reach you. contact_requires_quote and direct_transfer_unsupported are both folded into 409 contact_not_payable before the response is written. contact_chain_unsupported and contact_token_unsupported have no raise site at all: a contact off Base, or expecting another token, is routed cross-chain rather than refused, and an account without routing gets 409 payout_not_fundable. Generated clients will carry all four; branch on contact_not_payable.

Contact blockers

GET /dapi/v1/contacts/{contactId}/capabilities answers a blockers[] array rather than a refusal, and 409 contact_not_payable carries the same array as details.blockers. Each entry has a code, a message safe to show an operator, and sometimes the field it is about. These codes are their own vocabulary — they are not error slugs, and they never appear as error.code.

Funding a payment

These arrive at POST /dapi/v1/payouts/{payoutId}/confirm; two of them — quote_not_submitted and payout_not_fundable — also answer the sandbox settlement routes, where they mean the same thing. The decision table for the four that branch is on Payouts.

KYB and documents

Escrow

Handling a refusal in code

Write the envelope into a typed error once, at the bottom of your HTTP layer: read error.code, keep error.details, honour Retry-After on a 429, re-mint the access token once on a 401, and log details.requestId on anything 5xx. Every example in these guides calls that one helper, and it is written out in full on HTTP client.

Next: Limits

Every number that can refuse a request: rate limits, expiry windows, page sizes, sandbox caps.