Error handling
Handle API errors with retry strategies, error code references, and clear failure handling for DALP integrations.
DALP API errors include a public error object with a stable DALP-#### identifier, HTTP status, retry guidance, and remediation copy. Use that object to decide whether to fix the request, retry with backoff, or show users a clear next step.
Error response format
Direct REST errors return the public error object under error:
{
"error": {
"id": "DALP-0006",
"category": "permission",
"status": 403,
"retryable": false,
"message": "User does not have the required role to execute this action.",
"why": "The actor lacks at least one role required by the token or system contract.",
"fix": "Grant the required role or retry with an authorized actor."
}
}| Field | Type | Description |
|---|---|---|
error.id | string | Stable DALP error identifier. Use it for support and logging. |
error.category | string | Error class such as auth, permission, or dependency. |
error.status | number | HTTP status code. |
error.retryable | boolean | Whether retrying can make sense after the cause is resolved. |
error.message | string | Short human-readable summary. |
error.why | string | Why the request failed. |
error.fix | string | Recommended remediation. |
error.details | object? | Optional route-specific details. |
Other transports carry the same public object in transport-specific locations:
- oRPC REST errors attach it under
data.dapiError. - oRPC JSON-RPC errors wrap it under
error.data.dapiError. - Deployment stream
errorevents send it under the event payload'serrorfield.
Some route-specific errors, such as contract execution failures, also preserve public fields such as data.dalpCode, data.retryable, and data.suggestedAction. Keep reading the route-specific data fields documented later in this guide. The DAPI error reference lists every current DALP-#### identifier with its status, retryability, and remediation guidance.
Use the typed fields in this order:
- Branch on
idfor programmatic handling and support triage. - Read
retryablebefore retrying.truemeans retrying can make sense after the cause is resolved; it does not mean retry the same request in a loop. - Show
messageas the short user-facing summary. - Use
whyto explain the failed condition. - Use
fixas the next action for the operator, administrator, or end user. - Preserve
details.requestIdin logs and support tickets when it is present.
Do not parse message, why, or fix for control flow. Public copy can improve over time. Use id, status, category, and retryable for client logic.
function classifyDapiError(error: {
id: string;
retryable: boolean;
message: string;
why: string;
fix: string;
details?: { requestId?: string };
}) {
return {
code: error.id,
retry: error.retryable ? "retry-after-fix" : "do-not-retry",
userMessage: `${error.message} ${error.fix}`,
supportReference: error.details?.requestId,
};
}Quick reference
| Legacy code | Status | Retry? | Action |
|---|---|---|---|
BAD_REQUEST | 400 | No | Fix request payload |
UNAUTHORIZED | 401 | No | Reauthenticate |
FORBIDDEN | 403 | No | Check role permissions |
NOT_ONBOARDED | 403 | No | Complete user onboarding |
SYSTEM_NOT_CREATED | 403 | No | Initialize platform first |
USER_NOT_AUTHORIZED | 403 | No | Request required role |
NOT_FOUND | 404 | No | Verify resource exists |
CONFLICT | 409 | No | Resolve state conflict |
RESOURCE_ALREADY_EXISTS | 409 | No | Use existing resource |
INPUT_VALIDATION_FAILED | 422 | No | Fix validation errors |
TOKEN_PRECHECKS_INVALID_ADDRESS_VALID_ETHEREUM_0X_PREFIXED_HEX_CHARACTERS | 400 | No | Provide a valid 0x-prefixed Ethereum address for token and holder pre-checks |
TOKEN_INTERFACE_NOT_SUPPORTED | 422 | No | Use compatible token contract |
CONTRACT_ERROR | 422 | Check | Read data.retryable and data.dalpCode; fix non-retryable contract errors before retrying |
LUNA_MOFN_QUORUM_EXPIRED | 408 | Yes | Activate the Luna partition, then resubmit the transaction |
LUNA_MOFN_QUORUM_CLASSIFICATION_FAILED | 409 | No | Inspect the Luna partition state directly before retrying |
INTERNAL_SERVER_ERROR | 500 | Yes | Retry with exponential backoff; contact support if persistent |
CONFIRMATION_TIMEOUT | 504 | No | Check transaction status before retrying |
Reading OpenAPI error envelopes
The generated OpenAPI document groups known route errors into one response envelope per HTTP status. For example, a route with multiple 422 failures shows one 422 DALP error response schema instead of a separate schema variant for every default message.
Use the envelope fields this way when you generate clients or inspect the API reference:
| Field | How to use it |
|---|---|
defined | true means the code is listed in DALP's public error registry. Treat an unrecognised code as an unexpected integration failure and log the full response. |
code | Branch on this stable machine-readable code, such as INPUT_VALIDATION_FAILED or TOKEN_INTERFACE_NOT_SUPPORTED. Do not branch on message text. |
status | Match it to the HTTP response status. Use it for coarse retry handling before checking the exact code. |
message | Display or log the short diagnostic copy. The message can change as public copy improves. |
data | Read route-specific details when the schema documents them. The OpenAPI schema uses oneOf inside data only for error codes that carry extra data. |
This shape is an OpenAPI documentation envelope. Direct DALP REST errors still return the public DALP object under error. Generated-client code should prefer stable identifiers over message text and use the DAPI error reference for the public DALP-#### registry.
Retry decision flowchart
Use this flowchart to determine whether to retry a failed request:
Key principles:
- 4xx errors: Do not retry, except for retryable
CONTRACT_ERRORresponses orLUNA_MOFN_QUORUM_EXPIRED. For retryableCONTRACT_ERRORresponses, inspectdata.dalpCodeand anydata.suggestedAction, then resolve the required condition. For Luna quorum expiry, activate the partition before resubmitting. - 5xx errors: Retry with exponential backoff, unless it's a blockchain revert.
- Blockchain reverts: Check the revert reason, retrying won't help if the underlying issue persists.
Client errors (4xx)
Client errors usually indicate problems with the request itself. Retrying the same request will produce the same error unless the response is a retryable CONTRACT_ERROR or LUNA_MOFN_QUORUM_EXPIRED. Fix the underlying issue before retrying non-retryable client errors.
Authentication errors (401)
UNAUTHORIZED
Authentication is missing or invalid. The API key may be expired, revoked, or malformed.
- Verify the API key includes the
sm_dalp_prefix - Check the key hasn't been deleted in the API Keys page
- Confirm the
X-Api-Keyheader is set correctly
Authorization errors (403)
FORBIDDEN
The authenticated user lacks permission for this operation.
- Review the user's assigned roles
- Check if the operation requires admin or system-level permissions
- See Platform setup for role management
NOT_ONBOARDED
The user hasn't completed the onboarding process.
- Direct the user to complete onboarding in the platform UI
- Onboarding includes profile setup and wallet configuration
- See User onboarding for the complete flow
SYSTEM_NOT_CREATED
The DALP platform hasn't been initialized. This occurs when accessing a fresh deployment before the first admin completes setup.
- Follow the First admin setup guide
USER_NOT_AUTHORIZED
The user lacks the specific role required for this token operation.
For oRPC routes, data.requiredRoles can list the roles required by the operation. The same response can also include the public registry object under data.dapiError.
- Check Asset admin roles for role assignment
Resource errors (404, 409)
NOT_FOUND
The requested resource doesn't exist.
- Verify the resource ID or address is correct
- Check if the resource was deleted
- Confirm the API path is correct (include
/apisuffix in base URL)
CONFLICT
The operation conflicts with the current system state.
- Check if another operation is in progress
- Verify the resource state hasn't changed since your last read
RESOURCE_ALREADY_EXISTS
Attempted to create a resource that already exists.
- Query for the existing resource instead of creating
- Use a unique identifier if creating a new resource
Workflow and custody approval conflicts
Workflow conflict
A workflow-backed operation returned HTTP 409 because the current workflow or resource state does not allow this call yet. Custody-backed signing is one subcase: the request may be waiting for a policy approval, a nonce reservation, or a Luna m-of-n quorum workflow.
- Refresh the resource, deployment, or transaction status before submitting another write
- Do not retry immediately with a different idempotency key
- For custody-backed signing, wait for the active approval or quorum step to finish
- Retry only after the required workflow, approval, or resource state has changed
Token creation idempotency conflicts
Token creation is a workflow-backed write. POST /api/v2/tokens and the legacy POST /token/create route can return HTTP 202 Accepted when DALP accepts the instruction but the creation workflow is still running. Treat that response as the normal pending path: poll the returned statusUrl and do not submit another create request with a new idempotency key.
HTTP 409 Conflict is reserved for idempotency states that DALP cannot safely attach to the current request.
| 409 condition | What it means | What to do |
|---|---|---|
| Expired idempotency key | The original transaction request is outside the 24-hour idempotency window. | Confirm the earlier request did not create the token before sending a new token creation request with a new key. |
| Cancelled workflow | The transaction request attached to the key was cancelled. | Use a new key only after confirming the cancelled request did not create the token. |
| Different wallet selection | The retry uses the same key but a different accountWalletId, smartWalletAddress, or forceEoa selection. | Keep the original wallet selection for a retry. Use a new key only for a deliberate new instruction. |
| Existing request cannot be matched | DALP detected an idempotency-key collision but could not recover the existing transaction request for safe attach. | Stop retrying and contact support with the diagnostics below. |
Production integrations should send an Idempotency-Key on every token creation request and store it with the client-side instruction. Reuse that same key only for the same token creation instruction, signer wallet selection, chain, and executor mode. If the token payload changes, create a new client-side instruction and a new idempotency key; DALP does not use the token payload itself as the deduplication boundary.
Share these fields with SettleMint support when a token creation conflict remains unclear:
- the HTTP status and public error code from the response
- the request ID or trace ID returned by the API gateway
- the
Idempotency-Keyused by the client - the route called, for example
POST /api/v2/tokensor legacyPOST /token/create - the token
type, selected template ID when present, and the participant or wallet used to sign - the transaction status URL or transaction request identifier when the response includes one
- the custody provider or HSM approval reference shown in the operator console, if the signer requires external approval
Use customer-facing copy that separates pending workflow states from idempotency conflicts:
| Situation | Safe wording |
|---|---|
| Custody approval pending | "Token creation is waiting for signer approval. Poll the transaction status link and keep the original idempotency key recorded." |
| Workflow still running | "DALP is still processing the earlier token creation request. Check the transaction status before submitting another create request." |
| Idempotency conflict | "DALP could not attach this retry to the earlier token creation request. Confirm the earlier result before starting a new token creation." |
| Payload correction needed | "The request must be corrected before retrying. Fix the validation or prerequisite error and send a new request only when the instruction changes." |
LUNA_MOFN_QUORUM_EXPIRED
A Thales Luna 7 partition was waiting for m-of-n approval and the configured signing window expired before the operator quorum approved it.
- Activate the partition on the HSM
- Resubmit the transaction after approval
- If this happens repeatedly, review the configured signing window with your platform operator
LUNA_MOFN_QUORUM_CLASSIFICATION_FAILED
DALP saw the Luna partition report as activated, but the signing call still returned m-of-n pending.
- Inspect the partition state on the HSM directly
- Retry only after the operator quorum is actually active
- Contact support with the request ID if the HSM state and API response disagree
Token pre-check address errors (400)
TOKEN_PRECHECKS_INVALID_ADDRESS_VALID_ETHEREUM_0X_PREFIXED_HEX_CHARACTERS
Transfer and burn pre-checks validate the token address and each holder address before reading indexed balances. The API returns this error when the request contains an invalid 0x-prefixed Ethereum address.
- Fix the token or holder address in the request before retrying
- Do not retry the same payload with backoff
- After fixing the address, handle any balance, freeze, or indexing state errors separately
Validation errors (422)
INPUT_VALIDATION_FAILED
Request data failed schema validation. For oRPC routes, data.errors can list the exact invalid fields. The same response can also include the public registry object under data.dapiError.
{
"code": "INPUT_VALIDATION_FAILED",
"status": 422,
"message": "Input validation failed",
"data": {
"errors": ["amount: Expected positive number", "recipient: Invalid Ethereum address"],
"dapiError": {
"id": "DALP-0080",
"category": "client",
"status": 422,
"retryable": false,
"message": "Input validation failed",
"why": "The request body or parameters did not match the API contract.",
"fix": "Check the request fields against the API documentation and retry with valid values."
}
}
}Review each data.errors entry and fix the corresponding field in your request.
TOKEN_INTERFACE_NOT_SUPPORTED
The token contract at the specified address doesn't implement the required interface.
The data.requiredInterfaces field lists the missing interfaces (e.g., ERC20, IYieldSchedule).
- Verify you're using the correct token address
- Check if the token type supports the requested operation
Server errors (5xx)
Server errors indicate temporary problems. Most can be resolved by retrying with exponential backoff.
General server errors (500)
INTERNAL_SERVER_ERROR
An unexpected error occurred on the server.
- Retry with exponential backoff (1s, 2s, 4s delays)
- Maximum 3 retry attempts
- If errors persist, contact support with the request details
Timeout errors (504)
CONFIRMATION_TIMEOUT
A blockchain transaction was submitted but confirmation timed out. The transaction may still succeed, do not retry the original API call.
Do not retry the original request, this may create duplicate transactions. Check the transaction status first to determine whether to retry.
- The
data.transactionHashfield in the error contains the transaction hash - Query
GET /api/transaction/{transactionHash}to check if the transaction succeeded, reverted, or was never submitted - See Transaction tracking for the full timeout recovery flow
Blockchain transaction errors
When a blockchain transaction reverts, the API returns the revert information in the error details. Revert reasons come directly from smart contract custom errors.
Important: Blockchain reverts should not be retried without fixing the underlying issue. The same transaction will revert again.
Common revert reasons
These errors occur frequently during normal operations and typically require user action or data correction.
Balance and supply errors
| Error | Description | Resolution |
|---|---|---|
InsufficientTokenBalance | Account lacks sufficient token balance | Query balance before retrying; ensure user has enough tokens |
ExceededCap | Minting would exceed the token's supply cap | Reduce mint amount or increase cap (if authorized) |
InsufficientCollateral | Insufficient collateral backing for mint operation | Add more collateral before minting |
Permission and authorization errors
| Error | Description | Resolution |
|---|---|---|
AccessControlUnauthorizedAccount | Account lacks the required role for this operation | Grant the required role to the account |
TransferNotCompliant | Transfer failed compliance checks | Verify both parties meet compliance requirements |
MintNotCompliant | Mint operation failed compliance checks | Verify recipient meets compliance requirements |
RecipientNotVerified | Recipient doesn't meet identity verification requirements | Complete identity verification for recipient |
ApprovalRequired | Transfer requires pre-approval from authorized party | Request transfer approval before executing |
Token state errors
| Error | Description | Resolution |
|---|---|---|
TokenPaused | Token operations are paused by admin | Wait for admin to unpause the token |
SenderAddressFrozen | Sender's address is frozen | Contact admin to unfreeze the address |
RecipientAddressFrozen | Recipient's address is frozen | Contact admin to unfreeze the address |
Identity and compliance errors
| Error | Description | Resolution |
|---|---|---|
IdentityNotRegistered | User's identity is not registered in the system | Register identity through onboarding flow |
IdentityAlreadyRegistered | Identity already exists for this user | Use existing identity instead of creating new |
ComplianceCheckFailed | Generic compliance check failure | Review compliance requirements for the operation |
Validation errors
| Error | Description | Resolution |
|---|---|---|
ZeroAddressNotAllowed | Zero address provided where non-zero required | Provide a valid non-zero address |
LengthMismatch | Array lengths don't match in batch operations | Ensure all arrays have equal length |
InvalidDecimals | Token decimals value is invalid (typically >18) | Use valid decimals value (0-18) |
Deployment workflow errors
Long-running deployment workflows can fail after the initial request has been accepted. REST deployment endpoints return a 422 CONTRACT_ERROR envelope when they need to expose a deployment workflow failure to the client. Deployment SSE streams expose the same public contract-error data when they report failed deployment steps. The envelope is designed for client handling and user-facing support flows:
{
"code": "CONTRACT_ERROR",
"status": 422,
"message": "Contract operation failed",
"data": {
"dalpCode": "DALP-WORKFLOW-FAILED",
"message": "The deployment workflow failed before it could finish.",
"retryable": true,
"selector": "0x00000000",
"solidityError": "WorkflowFailed()",
"correlationId": "deployment-id"
}
}This is one deployment CONTRACT_ERROR surface exposed through both deployment transports:
- REST deployment endpoints: return
422 CONTRACT_ERRORwithdata.dalpCodeanddata.correlationIdwhen a deployment workflow failure must be projected to the caller. - SSE deployment streams: report the same deployment error surface in failed deployment completions under
complete.failedSteps; each failed step can include a decodedwireError.
Use data.dalpCode for REST programmatic handling and data.correlationId when asking support to investigate a failed deployment.
Each SSE failed step's wireError uses the same public CONTRACT_ERROR.data shape shown above. The older failedSteps[].error field is legacy text and mirrors the sanitized public wireError.message; new clients should read failedSteps[].wireError for structured handling.
The deployment error payload intentionally contains public troubleshooting fields only. It does not expose raw provider responses, RPC URLs, stack traces, contract call context, or nested cause-chain diagnostics.
Operators can still investigate those diagnostics through logs and traces. Client-facing API responses only include the public envelope.
Typed platform errors, such as feature-gating or state conflicts, keep their normal error codes instead of being converted into CONTRACT_ERROR. Handle those errors according to the quick reference above.
For REST responses, base retry behavior on the public fields in data: data.retryable, data.dalpCode, and data.suggestedAction when present. For SSE deployment stream failures, read those values from the failed-step payload: complete.failedSteps[].wireError.retryable, complete.failedSteps[].wireError.dalpCode, and complete.failedSteps[].wireError.suggestedAction when present.
Retry strategies
Exponential backoff
For 5xx errors (except blockchain reverts), use exponential backoff:
| Attempt | Wait time |
|---|---|
| 1 | 1 second |
| 2 | 2 seconds |
| 3 | 4 seconds |
| 4+ | Fail |
Add jitter (random 0-500ms) to prevent thundering herd problems when multiple clients retry simultaneously.
Transaction confirmation
For operations that submit blockchain transactions:
- The API polls for confirmation automatically
- If
CONFIRMATION_TIMEOUToccurs, do not retry the original request - Query
GET /api/transaction/{transactionHash}to check if the transaction was processed - The transaction hash (available in
X-Transaction-Hashresponse header or error response) can be used to verify on-chain status - Only submit a new request after confirming the original transaction failed
Multi-transaction operations: Some API calls submit more than one blockchain transaction. Raw HTTP responses emit one X-Transaction-Hash header per transaction hash. Typed clients should use meta.txHashes as the canonical list because repeated headers can be harder to consume consistently. Treat each hash as a transaction to poll. For parallel submission paths, do not infer completion from list order or assume a timeout applies only to the last hash.
See Transaction tracking for detailed polling patterns and response interpretation.
Best practices
Log errors with context
Include the error code, request path, and relevant IDs in your logs. This enables faster debugging when issues occur in production.
Set reasonable timeouts
Configure client timeouts appropriate for the operation type:
- Read operations: 10-30 seconds
- Write operations: 60-90 seconds (blockchain transactions take time)
Fail fast on client errors
Don't retry 4xx errors unless the response is a retryable CONTRACT_ERROR or LUNA_MOFN_QUORUM_EXPIRED. For retryable CONTRACT_ERROR responses, inspect data.dalpCode and any data.suggestedAction. Retry only after the required user action, approval, or external state change is complete. Surface non-retryable client errors to users or fix them programmatically.
Circuit breaker for repeated failures
If a specific endpoint fails repeatedly (5+ times in a minute), pause requests to that endpoint temporarily. This prevents cascading failures and allows the service to recover.
Idempotency for critical operations
For financial operations, ensure your integration can handle duplicate responses safely. Network issues may cause a successful request to appear failed, leading to retry attempts on already-completed operations.
Next steps
- Getting started – Set up API authentication
- Token lifecycle – Understand operation flows
- API reference – Complete endpoint documentation