Read and handle DALP errors
Read the DALP-#### id, category, and retryable flag on every error response, branch your integration on those fields, and never parse the message text.
Overview
DALP error responses include a stable identifier, a reason, a recovery step, and a retry flag. Use those fields to distinguish a request you must correct from a temporary failure. HTTP status alone does not identify the cause.
Versioned REST routes use this envelope: a stable code, an HTTP status, a headline message, and a data object carrying id, why, fix, retryable, and category. The same registry backs JSON-RPC error responses and Server-Sent Events streams, framed for each transport, so the fields you branch on stay the same across every way your integration talks to DALP.
{
"code": "USER_EMAIL_ALREADY_EXISTS",
"status": 409,
"message": "A user with this email already exists.",
"data": {
"id": "DALP-0670",
"why": "Each platform user must have a unique email address.",
"fix": "Use a different email or open the existing user in User management.",
"retryable": false,
"category": "client",
"requestId": "req_2c8f1a4e0b7d4e11"
}
}Fields to branch on
| Field | Use it to... |
|---|---|
data.id | Identify the exact error, using its stable identifier. Look it up in the DALP error index. |
data.category | Route the error to the right handling path: client, auth, permission, domain, contract, dependency, operational, vendor-boundary, or unknown. |
status | Match your HTTP client's error handling; always 400 through 599. |
data.retryable | Decide whether a retry can succeed once you resolve the cause. true never means send the same request in a loop; it means a retry can succeed after the underlying condition clears, such as a dependency recovering. |
data.requestId | Attach to a support ticket. Not every response carries one. |
Never branch on message or data.why. Both are copy DALP can reword between releases; only data.id, data.category, and status are the stable contract.
Decide what to do
Reading these fields resolves to one of three decisions. Retry, when data.retryable is true and the condition the error names has cleared. Fix the request, when data.retryable is false and data.fix names a step you control. Escalate, which no field marks directly: it is what remains once neither of the first two applies.
data.category groups errors by class, client, auth, permission, domain, contract, dependency, operational, vendor-boundary, or unknown, for coarse handling and support routing. It is not a retry signal by itself: two errors in the same category can differ on data.retryable. Decide retry or fix from data.retryable and data.fix, and use data.category to route logging, alerting, or a support ticket to the right team.
A data.retryable of false does not always mean never resend. A few errors set it to false while their data.fix copy still recommends trying the request once more by hand. The flag tells an automated retry loop whether to re-drive the request on its own; it does not settle whether a person should try again. Read data.fix before turning the flag into a blanket policy.
A data.retryable of true does not mean the SDK retries automatically or that the route is safe to replay. Apply a bounded retry policy only where the route documents replay safety. For an uncertain write, inspect the original instruction's state before creating another attempt.
Use the stable error id when handling a specific failure. Categories support broad routing and do not replace per-code recovery rules.
Which failures deserve a person
The registry marks an entry as an expected client error when a caller routinely and legitimately triggers it. A compliance-blocked transfer and a validation rejection both qualify. That marking sets the entry's default severity to informational rather than error, and every failure carries that severity as a field alongside its code wherever it is logged. Build an alert on the severity field rather than on the presence of a log line, so a routine compliance rejection does not page anyone while the refusal is still counted and still visible on a dashboard.
Two different identifiers matter here, and they answer different questions. The error code names the kind of failure. It stays stable across releases and matches the string the registry, this reference, and the logs all use, so triage searches on it. The request identifier names one specific call, and a response carries it where the route can supply one. Record both with a ticket: the code finds the class of failure, and the request identifier finds the exact request inside it. Include the error code, request identifier when present, and the affected resource in a support request.
Where to look up a code
- DALP error index lists every
DALP-####id with its machine name, category, HTTP status, retryable flag, and a link to the page that explains it. - Platform API error reference documents REST and workflow errors by domain.
- Smart contract error code reference documents on-chain revert reasons for compliance, settlement, and system contracts.
These three pages are generated from the same registry your integration receives errors from; this guide does not restate their tables.
Contract errors carry extra fields
An error raised by a reverted smart contract call adds dalpCode, the reverting function selector, and the Solidity solidityError name alongside the standard fields. Use these to correlate a DALP error with the on-chain revert your transaction explorer shows.
You do not receive a bare Solidity error, whichever way the revert reached the platform. A smart contract failure can arrive through four different routes. A revert can appear while estimating gas or broadcasting. A revert can be decoded from a transaction receipt. The bundler can report an account-abstraction execution failure. A raw revert can arrive with no matching interface available to decode its arguments. Each route ends in the same lookup: take the revert's four-byte selector, resolve it against one contract error catalog, and build one payload from the entry it finds. The same on-chain failure therefore surfaces the same identifier and the same copy on all four routes.
A catalog entry's copy can carry placeholders that decoded Solidity arguments fill in, so a message can name the actual wallet or the actual module. When a placeholder cannot be resolved, because decoding failed or the argument is absent, the platform strips the placeholder instead of printing a raw token or raising, and the message degrades to its general form. Unresolved placeholders are omitted from the public message.
Compliance reverts can classify themselves
For a mint or a transfer blocked by compliance, the error can carry an arguments object with a reason field with four stable values. The party's identity is not registered. The identity is registered but is missing required claims. A compliance module rejected the operation. Or the module needed a price feed that has gone stale. Branch on those four. They are the stable surface; the message that accompanies them gets more specific over time and may name the blocking module. Those four values also point at four different owners. An unregistered identity is an onboarding task. A missing claim belongs to a trusted issuer. A module rejection is a policy question about the token's rules rather than about the investor. A stale price feed is an infrastructure question for whoever operates the feed.
Two caveats keep that from becoming a trap. Classification is best effort, so not every compliance failure carries the arguments object; key your control flow off the error code first and treat the extra detail as present when available. And a missing arguments object has two possible causes that look identical on the wire: the decoding of the revert arguments failed, or you lack the permission described next. Treat absence as normal rather than as a malformed response.
Disclosure is controlled rather than open. On the transfer path the platform checks the eligibility-read permission, the same access needed to read a participant's compliance eligibility directly, before it decides how much to say. A caller holding it receives the classification. A caller without it receives the general refusal, carrying no reason field and no per-address detail. The check runs server-side while the response is built, so the classification improves remediation without turning an error response into a way to probe another party's compliance state. Mint is already restricted to authorized callers and returns the fuller detail to them.
Present errors to a user
Show message as the headline, data.why as the explanation, and data.fix as the next step. Do not build your own copy from data.id alone; the registry's why and fix are written for the person seeing the error, and DALP updates that copy without changing the id.
Next steps
- Idempotency and async settlement covers the
409 Conflictresponses an idempotent write can return. - Authentication and API keys covers
DALP-0002andDALP-0036, the authentication and scope errors.
Pagination
Page DALP list endpoints with JSON:API sort, page, and filter query parameters, read the response links object, and stay inside the 200-item page-size cap.
Versioning and deprecation
Breaking API changes ship only on a new /api version, and a deprecated operation answers with Deprecation and Sunset headers for at least 90 days.