/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
A422 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 atconfirm 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 atPOST /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: readerror.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.