SettleMint
Errors

Error handling

Handle DALP API failures with stable error identifiers, retry decisions, and support-ready diagnostics.

When your integration hits a DALP API error, each response carries stable fields: a DALP-#### identifier, HTTP status, retry flag, and remediation copy. Check those fields to decide whether to fix the request, retry with backoff, or surface the next step to a user.

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."
  }
}
FieldTypeDescription
error.idstringStable DALP error identifier. Use it for support and logging.
error.categorystringError class such as auth, permission, or dependency.
error.statusnumberHTTP status code.
error.retryablebooleanWhether retrying can make sense after the cause is resolved.
error.messagestringShort human-readable summary.
error.whystringWhy the request failed.
error.fixstringRecommended remediation.
error.detailsobject?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 error events send it under the event payload's error field.

Route-specific errors can include extra public fields such as data.dalpCode, data.retryable, and data.suggestedAction. Read those data fields before choosing a retry path. Start with the errors overview when you need to choose between API identifiers, smart contract reverts, and handling guidance. The Platform API error reference lists each current DALP-#### identifier with its HTTP status, retryability and recommended remediation.

Use the typed fields in this order:

  1. Branch on id for programmatic handling and support triage.
  2. Read retryable before retrying. true means retrying can make sense after the cause is resolved; it does not mean retry the same request in a loop.
  3. Show message as the short user-facing summary.
  4. Use why to explain the failed condition.
  5. Use fix as the next step for the operator, administrator, or end user.
  6. Preserve details.requestId in 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 codeStatusRetry?Response
BAD_REQUEST400NoFix request payload
UNAUTHORIZED401NoReauthenticate
FORBIDDEN403NoCheck role permissions
NOT_ONBOARDED403NoComplete user onboarding
SYSTEM_NOT_CREATED403NoInitialize platform first
USER_NOT_AUTHORIZED403NoRequest required role
NOT_FOUND404NoVerify resource exists
CONFLICT409NoResolve state conflict
RESOURCE_ALREADY_EXISTS409NoUse existing resource
INPUT_VALIDATION_FAILED422NoFix validation errors
TOKEN_PRECHECKS_INVALID_ADDRESS_VALID_ETHEREUM_0X_PREFIXED_HEX_CHARACTERS400NoProvide a valid 0x-prefixed Ethereum address for token and holder pre-checks
TOKEN_INTERFACE_NOT_SUPPORTED422NoUse compatible token contract
CONTRACT_ERROR422CheckRead data.retryable and data.dalpCode; fix non-retryable contract errors before retrying
LUNA_MOFN_QUORUM_EXPIRED408YesActivate the Luna partition, then resubmit the transaction
LUNA_MOFN_QUORUM_CLASSIFICATION_FAILED409NoInspect the Luna partition state directly before retrying
INTERNAL_SERVER_ERROR500YesRetry with exponential backoff; contact support if persistent
INDEXER_REINDEXING503YesOpt-in only; honor Retry-After and re-issue the onboarding deploy
CONFIRMATION_TIMEOUT504NoCheck transaction status before retrying

Read generated 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:

FieldHow to use it
definedtrue 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.
codeBranch on this stable machine-readable code, such as INPUT_VALIDATION_FAILED or TOKEN_INTERFACE_NOT_SUPPORTED. Do not branch on message text.
statusMatch it to the HTTP response status. Use it for coarse retry handling before checking the exact code.
messageDisplay or log the short diagnostic copy. The message can change as public copy improves.
dataRead 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 clients should branch on stable identifiers, not message text. Use the Platform API error reference for the full DALP-#### registry.

Retry decision flowchart

Use this flowchart to decide whether to retry a failed request. The three branch points are: HTTP status class, a retryable CONTRACT_ERROR, and a blockchain revert.

Rendering diagram...

Retry rules:

  • For 4xx errors, do not retry 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, then resolve the required condition. For Thales Luna quorum expiry, activate the partition before resubmitting.
  • For 5xx errors, retry with exponential backoff unless the failure is a blockchain revert.
  • For blockchain reverts, check the revert reason. The same transaction reverts again until the underlying issue changes.

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

The platform rejected the request because authentication is missing or invalid. Your API key may be expired, revoked, or malformed.

  • Verify the API key includes the sm_dalp_ prefix.
  • Check the key has not been deleted in the API Keys page.
  • Confirm your X-Api-Key header is set correctly.

Authorization errors (403)

FORBIDDEN

The authenticated actor lacks permission for this operation.

  • Review the actor's assigned roles.
  • Check if the operation requires admin or system-level permissions.
  • See Platform setup for role management.

NOT_ONBOARDED

The user has not completed the onboarding process.

  • Direct the user to complete onboarding in the Console.
  • Onboarding includes profile setup and wallet configuration.
  • See User onboarding for the complete flow.

SYSTEM_NOT_CREATED

The platform has not been initialized. This error appears on a fresh deployment before the first admin completes setup.

USER_NOT_AUTHORIZED

The actor lacks the specific role required for this token operation. For oRPC routes, data.requiredRoles lists the roles the operation needs. The response can also include the public registry object under data.dapiError. See Asset admin roles to assign the correct role.

Resource errors (404, 409)

NOT_FOUND

The platform cannot find the requested resource.

  • Verify the resource ID or address is correct.
  • Check whether the resource was deleted.
  • Confirm the API path is correct: the base URL requires the /api suffix.

CONFLICT

The operation conflicts with the current resource state.

  • Check whether another operation is in progress.
  • Verify the resource state has not changed since your last read.

RESOURCE_ALREADY_EXISTS

Your request tried to create a resource that already exists.

  • Query for the existing resource instead of creating a new one.
  • Use a unique identifier if you intend to create a separate resource.

Workflow and custody approval conflicts

Workflow conflict

HTTP 409 from a workflow-backed operation means the current resource state does not allow the 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.

Refresh the resource, deployment, or transaction status before submitting another write. Do not retry with a different idempotency key. For custody-backed signing, wait for the active approval or quorum step to complete. Retry only after the required 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 conditionWhat it meansWhat to do
Expired idempotency keyThe 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 workflowThe 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 selectionThe 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 matchedDALP 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 key only when all of these values match:

  • token creation instruction
  • signer wallet selection
  • chain
  • 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-Key used by the client.
  • The route called, for example POST /api/v2/tokens or legacy POST /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:

SituationSafe 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 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 lacks the required interface. Check data.requiredInterfaces for the list of missing interfaces, such as ERC20 or IYieldSchedule.

  • Verify the token address is correct.
  • Confirm the token type supports the requested operation.

Server errors (5xx)

Server errors indicate temporary platform problems. You can resolve most by retrying with exponential backoff.

General server errors (500)

INTERNAL_SERVER_ERROR

The platform encountered an unexpected server error.

  • Retry with exponential backoff: 1s, 2s, 4s.
  • Stop after 3 attempts.
  • If failures persist, contact support with your request details.

Service unavailable (503)

INDEXER_REINDEXING

The blockchain indexer is rebuilding its dataset in a zero-downtime reindex. An onboarding deploy would read stale or partial state until the reindex catches up.

This error is opt-in and off by default. The following onboarding deploy endpoints return it only when the request carries the Prefer: dalp-fail-on-indexer-reindexing directive: POST /api/v2/organizations/deploy, POST /api/v2/invitations/deploy, and POST /api/v2/invitations/accept.

Prefer: dalp-fail-on-indexer-reindexing

Without the directive the gate is off: the deploy is submitted and the durable workflow waits out the reindex, so a client that does not opt in never sees a 503 from this condition.

When an opted-in request is rejected during a reindex, the response is 503 with a Retry-After header and a positive integer data.retryAfterSeconds:

HTTP/1.1 503 Service Unavailable
Retry-After: 30
  • retryable is true. Wait for Retry-After (or data.retryAfterSeconds), then re-issue the same deploy request. The server re-evaluates readiness live each time.
  • The estimate is coarse and monotone non-increasing; do not treat it as a precise countdown.
  • The Console opts in and renders a "catching up" screen that polls automatically. Non-UI consumers that opt in should honor Retry-After with a bounded give-up. Clients that do not send the directive are unaffected.

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.

  • The data.transactionHash field 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 platform returns the revert information in the error details. Revert reasons come directly from smart contract custom errors.

Fix the underlying issue before resubmitting. The same transaction reverts until the root cause changes.

Common revert reasons

These errors occur frequently during normal operations and typically require a user correction or data fix.

Balance and supply errors

ErrorDescriptionResolution
InsufficientTokenBalanceAccount lacks sufficient token balanceQuery balance before retrying; ensure user has enough tokens
ExceededCapMinting would exceed the token's supply capReduce mint amount or increase cap (if authorized)
InsufficientCollateralInsufficient collateral backing for mint operationAdd more collateral before minting

Permission and authorization errors

ErrorDescriptionResolution
AccessControlUnauthorizedAccountAccount lacks the required role for this operationGrant the required role to the account
TransferNotCompliantTransfer failed compliance checksVerify both parties meet compliance requirements
MintNotCompliantMint operation failed compliance checksVerify recipient meets compliance requirements
RecipientNotVerifiedRecipient doesn't meet identity verification requirementsComplete identity verification for recipient
ApprovalRequiredTransfer requires pre-approval from authorized partyRequest transfer approval before executing

Token state errors

ErrorDescriptionResolution
TokenPausedToken operations are paused by adminWait for admin to unpause the token
SenderAddressFrozenSender's address is frozenContact admin to unfreeze the address
RecipientAddressFrozenRecipient's address is frozenContact admin to unfreeze the address

Identity and compliance errors

ErrorDescriptionResolution
IdentityNotRegisteredUser's identity is not registered in the systemRegister identity through onboarding flow
IdentityAlreadyRegisteredIdentity already exists for this userUse existing identity instead of creating new
ComplianceCheckFailedGeneric compliance check failureReview compliance requirements for the operation

Validation errors

ErrorDescriptionResolution
ZeroAddressNotAllowedZero address provided where non-zero requiredProvide a valid non-zero address
LengthMismatchArray lengths don't match in batch operationsEnsure all arrays have equal length
InvalidDecimalsToken decimals value is invalid (typically >18)Use valid decimals value (0-18)

Deployment workflow errors

Long-running deployment workflows can fail after DALP accepts the initial request. REST deployment endpoints return a 422 CONTRACT_ERROR envelope when they expose a deployment workflow failure to the client.

Deployment SSE streams expose the same public contract-error data for failed deployment steps. The envelope supports 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_ERROR with data.dalpCode and data.correlationId when 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 decoded wireError.

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 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.


Compliance failure reasons

When a mint or transfer fails compliance, the contract reverts without naming the cause. DALP fills that gap: a failed mint (DALP-1110) or a failed transfer (DALP-1096) can carry a data.args object that classifies why the operation was blocked, so you can act on the failure instead of showing a generic "does not meet the compliance requirements" message.

The classification is in data.args.reason, a stable three-value field:

data.args.reasonMeaningTypical next step
identity-not-registeredThe party's wallet is not registered in the token's identity registry and cannot hold the token.Register the party's identity in the token's identity registry, then retry.
identity-not-verifiedThe party is registered but is missing required identity claims (unissued, revoked, or expired).Have the required claims issued by a trusted issuer, then retry.
module-blockedA compliance module rejected the operation, for example a country, supply, or investor-limit rule.Review the token's active compliance modules to find the constraint, then retry.
{
  "code": "CONTRACT_ERROR",
  "status": 422,
  "message": "The recipient 0x1234…abcd is not registered in the ACME identity registry and cannot receive tokens.",
  "data": {
    "dalpCode": "DALP-1110",
    "message": "The recipient 0x1234…abcd is not registered in the ACME identity registry and cannot receive tokens.",
    "retryable": false,
    "selector": "0x9b3b575b",
    "solidityError": "MintNotCompliant()",
    "args": {
      "reason": "identity-not-registered",
      "party": "recipient",
      "wallet": "0x1234…abcd"
    }
  }
}

When the blocking module can be identified, data.message names that module and explains the constraint, and data.args adds the module context. The data.args.reason field still reports module-blocked here. Client logic therefore keeps the same three values to branch on, while the message text becomes more specific.

Branch on data.args.reason for programmatic handling and use data.message for the user-facing summary. Do not parse the message text for control flow.

function nextComplianceStep(args: { reason: string; party?: string }) {
  switch (args.reason) {
    case "identity-not-registered":
      return "register-identity";
    case "identity-not-verified":
      return "issue-required-claims";
    case "module-blocked":
      return "review-compliance-modules";
    default:
      return "review-compliance-requirements";
  }
}

Two properties matter for integration:

  • Enrichment is best effort. Not every compliance failure carries data.args. Classification is skipped in some cases, such as when it cannot complete in time or for large batch transfers. The response then falls back to the generic message under the same dalpCode. Always handle that fallback case, and key your control flow off data.dalpCode first. Treat data.args as present-when-available, not guaranteed.
  • Detailed transfer diagnostics require an eligibility-read role. On the transfer path, granular per-address compliance detail is returned only to callers with that role, the same access required to read participant compliance eligibility. A caller without it receives the generic compliance error. Mint is already role-restricted and returns the fullest detail to its authorized callers, including the named blocking module when it can be identified. Forced transfer classifies only the identity-not-registered case; its other compliance failures stay on the generic message.

Retry strategies

For 5xx errors that are not blockchain reverts, retry with exponential backoff. Add jitter, a random 0 to 500ms offset, to prevent simultaneous client retries from hitting the service together.

AttemptWait time
11 second
22 seconds
34 seconds
4+Fail

For operations that submit blockchain transactions, the API polls for confirmation automatically. If CONFIRMATION_TIMEOUT occurs, do not retry the original request. Instead, query GET /api/transaction/{transactionHash} to check whether the transaction succeeded, reverted, or was never submitted. The hash is available in the X-Transaction-Hash response header or the error response. Submit a new request only after confirming the original transaction failed.

Some API calls submit more than one blockchain transaction. Raw HTTP responses emit one X-Transaction-Hash header per hash. Typed clients should use meta.txHashes as the canonical list, since repeated headers can be harder to consume consistently. Treat each hash as a separate transaction to poll. For parallel submission paths, treat list order as arbitrary and assume a timeout may apply to any hash in the set.

See Transaction tracking for detailed polling patterns and response interpretation.


Best practices

Logging and timeouts

Include the error code, request path, and relevant IDs in your logs so you can debug failures in production. Set client timeouts appropriate for the operation type: 10 to 30 seconds for reads, 60 to 90 seconds for writes. Blockchain transactions require the longer window.

Retry discipline

Skip retries for 4xx responses unless the error 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 step, approval, or external state change is complete. Surface non-retryable client errors to your users or correct them in code.

If a specific endpoint fails 5 or more times in a minute, pause requests to that endpoint temporarily to prevent cascading failures and let the service recover.

Idempotency

For financial operations, design your integration to handle duplicate responses safely. Network failures can cause a successful request to appear failed, leading to retries on already-completed operations.


Next steps

On this page