SettleMint
API referenceGuides

Onboard an investor through the API

Create a user through the API, resolve the participant's identity wallet, register the identity under a country, and verify the investor with Sumsub.

Overview

Onboarding an investor through the API is a sequence of calls: create the user, resolve the wallet that carries the identity, register the identity under a country, and verify the investor with a know-your-customer provider such as Sumsub. Each write needs a role, and a wallet protected by a code needs a verification proof on the same request. The wallet that carries the identity is not in the create response, and a user that exists with no identity contract takes one extra call before registration.

The sequence covers a participant created on a managed custody provider configuration, where the platform holds the key and signs on the participant's behalf. A bring-your-own-key configuration leaves the participant pending with a null custodyAccountId and a null identity until its owner proves the key at first sign-in, and that path is outside this guide. Custody Provider describes both kinds and how a configuration is chosen.

Creating a user deploys an identity contract for a managed participant, but it never registers it. Registration is a separate on-chain write, and a token reads the registration before it lets the wallet hold value, so a deployed but unregistered identity is not enough. An on-chain identity is the ledger record a regulated token checks, and a wallet without one is not addressable to any rule engine.

The sequence at a glance

Four calls in one order, two side paths that rejoin, and a Sumsub verdict that decides the claim.Step 1 creates the user with POST /api/v2/users. A 200 means the custody account settled; a 202 sends you to poll the returned statusUrl until the state is terminal, then rejoin. Step 2 reads the custody accounts with GET /api/v2/participants/:participantId/custody-accounts. When the wallet has no identity contract you create it with POST /api/v2/identities and wait for the identity read, then rejoin. Step 3 registers the identity with POST /api/v2/system/identity-registrations. Step 4 requests the SDK token with POST /api/v2/compliance/subjects/sdk-access-token, and the investor completes the Sumsub checks. Sumsub's signed verdict then decides the claim: approved issues it, rejected revokes it, and an on-hold monitoring alert can revoke the claim when its severity reaches the topic's revocation threshold.Four calls in one order, and a Sumsub verdict that decides the claim.The same figure as the desktop variant, drawn portrait for phone widths. Step 1 creates the user, and a 202 means you poll the statusUrl first. Step 2 reads the custody accounts, and a wallet with no identity contract gets one created before you continue. Step 3 registers the identity. Step 4 requests the SDK token so the investor can complete the Sumsub checks. The signed verdict issues the claim when approved, revokes it when rejected, and an on-hold monitoring alert can revoke the claim at or above the topic's revocation threshold.

The calls run in one order, and the wallet that carries the identity comes from the custody account rather than from the create response. A create that leaves the custody account settling is polled first, and a wallet with no identity contract takes the create path before registration.

You create the user with dalp.api.party.users.create. When that call settles with the custody account usable, you read the participant's custody accounts with dalp.api.participants.custodyAccountsList and take the identity subject wallet from the account. When the custody account is still settling, you poll the statusUrl the create response returned until the state is terminal, then read the accounts. With a wallet in hand, you register the identity with dalp.api.party.identities.register, and you verify the investor with Sumsub by requesting an SDK access token, then wait for the signed verdict. Only an approved verdict issues the claim the token requires; a rejected verdict revokes it, and an on-hold monitoring alert can revoke it when its severity reaches the topic's revocation threshold. A wallet that has no identity contract at all takes one extra call first, dalp.api.party.identities.create, and you wait for the identity read to answer before registering.

How the systems call each other

One request from you starts work in several systems. The figure shows who calls whom for a managed Fireblocks configuration and a Sumsub verification provider. Each numbered arrow from your backend is one of the steps below.

The backend drives the platform, the platform calls Fireblocks and Sumsub, and a signed verdict closes the loop.Five lifelines: your backend, the platform API, Fireblocks, Sumsub and the investor app. The backend creates the user, reads the custody accounts and registers the identity, each time through the platform API. At create, the platform mints the investor's key in Fireblocks and has it sign. Registration is signed by your own identity-manager key, which reaches Fireblocks only when that key is held there. The backend then requests an SDK access token in one call, during which the platform creates a Sumsub applicant if the identity has none. The backend hands the token to the investor app. The investor submits documents to Sumsub. Sumsub sends a signed verdict webhook to the platform API, which issues the claim for an approved verdict, revokes it for a rejected one, and does nothing on-chain for the other two states.The backend drives the platform, the investor checks with Sumsub, and a signed verdict closes the loop.The same figure as the desktop variant, drawn portrait for phone widths. Your backend creates the user, reads the custody accounts and registers the identity through the platform, which mints the investor's key in Fireblocks; registration is signed by your own identity-manager key. The platform then creates the Sumsub applicant and returns an SDK token. The investor app uses the token to send documents to Sumsub. Sumsub sends a signed verdict to the platform, which issues the claim when approved, revokes it when rejected, and an on-hold monitoring alert can revoke the claim at or above the topic's revocation threshold.

The numbers match the steps below. The investor talks to Sumsub directly, and a returned token is not a verification: a signed Sumsub verdict or on-hold alert determines the claim change.

Registration in step 3 is a write by the identity manager that calls it, so the platform signs it with that caller's own key, not with the investor's Fireblocks configuration. It reaches Fireblocks only when the caller's key is held in a Fireblocks configuration as well. In the default provider-native mode the platform files each transaction with Fireblocks and Fireblocks broadcasts it.

Step 1: create the user

You call POST /api/v2/users with the user's email, an optional name, and the providerConfigurationId of an enabled managed provider configuration in the active organization. Naming the configuration is what creates the participant, mints the key, and deploys the identity contract. Omit it and the user holds no participant, so the rest of this sequence has no wallet to carry. The call needs the identity-manager role.

The create response names no wallet address. Read custodyAccountId and identity from a settled response, and resolve the wallet in step 2.

curl -X POST "https://your-instance.example.com/api/v2/users" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "name": "Amara Okafor",
    "providerConfigurationId": "0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d10",
    "walletVerification": { "verificationType": "PINCODE", "secretVerificationCode": "482913" }
  }'

A settled call answers 200 with the user and its participant:

{
  "data": {
    "id": "0193fa5c-9c31-7a4d-b0e2-3f6c9d1a7b45",
    "name": "Amara Okafor",
    "email": "[email protected]",
    "participantId": "pp_0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d12",
    "activationStatus": "active",
    "custodyAccountId": "0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d11",
    "identity": "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
  },
  "links": { "self": "/v2/users/0193fa5c-9c31-7a4d-b0e2-3f6c9d1a7b45" }
}

A 200 means the custody account settled and is usable. A 202 means the user exists but the custody account is still settling, and the body carries the lifecycle handle instead:

{
  "requestId": "0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b",
  "state": "awaiting-aa-reconciliation",
  "statusUrl": "/api/v2/participants/pp_0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d12/custody-account-requests/0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b"
}

Poll the statusUrl the response returned, and follow it rather than building a URL of your own. The lifecycle request belongs to the participant the call created, so the read is participant-scoped at /api/v2/participants/:participantId/custody-account-requests/:requestId; a /users/me path does not reach it, because the caller is not that participant. Poll until the state is succeeded, failed, or expired. A terminal failed or expired state means the custody account is unusable even though the user row exists, and you must resolve that before continuing.

Both responses accept Idempotency-Key. The identity writes in steps 3 and 5 treat a repeated key as a retry of the original submission. Idempotency and async settlement covers the key's lifetime and the Prefer header that decides whether a write settles inline.

Step 2: resolve the identity wallet

You call GET /api/v2/participants/:participantId/custody-accounts with the participantId from step 1. Each item carries its ledger and network, its lifecycle, its signingAddress, and an optional smartWalletAddress.

Take the identity subject wallet as the smart wallet address when the account has one, and otherwise as the signing address of the account marked primary. That is the address the Console carries through its own identity actions, and it is the address every following call in this sequence must use.

curl "https://your-instance.example.com/api/v2/participants/pp_0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d12/custody-accounts" \
  -H "X-Api-Key: sm_dalp_..."
{
  "data": [
    {
      "custodyAccountId": "0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d11",
      "participantId": "pp_0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d12",
      "ledger": "evm",
      "network": "11155111",
      "accountLifecycle": "active",
      "contextLifecycle": "active",
      "isPrimary": true,
      "signingAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
      "smartWalletAddress": null
    }
  ],
  "links": { "self": "/v2/participants/pp_0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d12/custody-accounts" }
}

Read signingAddress and smartWalletAddress only from an account whose accountLifecycle and contextLifecycle are active. A participant can hold accounts on more than one network, so select the account on the network you are registering on rather than the first row. When the accounts on that network do not agree on one smart wallet address, or none of them is marked primary, the subject wallet is ambiguous; resolve the account selection before you continue, because the platform does not choose one for you.

Step 3: register the identity

You call POST /api/v2/system/identity-registrations with the country to record and the wallet from step 2. The country is an ISO 3166-1 alpha-2 code such as BE, and the platform normalizes it before the write. Send wallet explicitly: omitting it registers the effective wallet of the caller, which is the operator's wallet and not the investor's.

The call needs the identity-manager role. It also accepts walletVerification, which proves the caller's own wallet rather than the wallet being registered.

curl -X POST "https://your-instance.example.com/api/v2/system/identity-registrations" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Idempotency-Key: register-identity-amara-001" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
    "country": "BE",
    "walletVerification": { "verificationType": "PINCODE", "secretVerificationCode": "482913" }
  }'

A settled call answers 200 with the identity, the transaction hashes, and the transaction request behind them:

{
  "data": {
    "id": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "account": { "id": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F", "contractName": null },
    "isContract": false,
    "hasIdentity": true,
    "registered": { "isRegistered": true, "country": "BE" },
    "claims": []
  },
  "meta": {
    "txHashes": ["0x5c73a1e94b0d268fa37e15c8b02d94f6a1e7835c0b96d2f4e83a17c5d602b9f4"],
    "transactionId": "0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b"
  },
  "links": { "self": "/v2/system/identity-registrations" }
}

registered appears only once the registry entry is active. The route also answers 202 with a queue handle when the write is queued rather than settled:

{
  "transactionId": "0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b",
  "status": "QUEUED",
  "statusUrl": "/api/v2/transaction-requests/0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b"
}

Poll that statusUrl until the request reports a terminal state. The call registers the wallet together with its paired address on that network, the smart wallet for a signing address or the signing address for a smart wallet, and submits only the addresses whose registration is not yet active. A repeated registration is safe rather than conflicting: when every one of those addresses is already active, the route answers with the identity and submits no transaction.

Until an identity contract exists for the wallet, the route answers 404 with DALP-9513, whose fix names POST /api/v2/identities for that wallet. Take the recovery path below rather than retrying the same registration.

Step 4: verify the investor with Sumsub

Registration makes the wallet addressable to every token's rule check. It proves nothing about what the participant may hold, so a registered identity that carries no required claim is still refused by a token that requires one. This step collects that claim through Sumsub. Sumsub runs the checks its configured level defines, such as documents and liveness. DALP turns the result into an on-chain claim.

You need a Sumsub compliance provider with an active claim topic, such as knowYourCustomer, before you start. An administrator adds it once in the Console; Compliance providers covers the setup. Your integrator backend passes the configured providerId to the investor app. A backend that looks it up with GET /api/v2/compliance/providers needs an Admin, System manager, or Compliance manager role. The investor's own session does not need those roles to request an SDK token. The call below refuses an identity that has no registration in the system's identity registry, so run it after step 3.

Issue an SDK access token

You call POST /api/v2/compliance/subjects/sdk-access-token to let the investor's app run the Sumsub check without holding Sumsub credentials. Send the providerId and topicName. Omit identityAddress to use the caller's own registered identity. A backend that acts for the investor names the investor's identityAddress. The platform creates the Sumsub applicant first when the identity has none, and binds it to that identity.

curl -X POST "https://your-instance.example.com/api/v2/compliance/subjects/sdk-access-token" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "providerId": "b6f2c0a4-1d83-4f1e-9c47-2a5e8d0b7311",
    "topicName": "knowYourCustomer",
    "identityAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
  }'
{
  "data": { "token": "_act-sbx-example-access-token", "expiresAt": "2026-09-28T16:10:00.000Z", "levelName": "id-and-liveness" },
  "links": { "self": "/api/v2/compliance/subjects/sdk-access-token" }
}

Pass token to the Sumsub WebSDK or MobileSDK in the investor's app. The token is short-lived, so request a new one after expiresAt instead of storing it. Naming an identity other than the caller's own needs a compliance provider role, and a missing role answers 404 with DALP-12283, the scope-denied error, not 403. The same code covers the other scope failures listed under step 4 failures.

Handle verdicts and on-hold alerts

Sumsub reviews the submission and sends a signed webhook to the platform. The platform checks the signature against the webhook secret of the provider. You enter that secret on the provider in the Console. The Console then shows a delivery URL for each topic, and you copy that URL into the Sumsub dashboard so Sumsub sends its webhooks there. Compliance providers covers both. For the provider's claim topic, such as knowYourCustomer, the platform handles Sumsub events as follows:

Sumsub eventClaim on the identity
applicantReviewed with GREEN (approved)The platform issues the claim from the provider's issuer identity
applicantReviewed with RED (rejected)The platform revokes the claim
applicantOnHoldThe platform records a monitoring alert and revokes the claim when its severity is at or above the topic's revocation threshold; below that threshold, no on-chain change

An on-hold alert has severity 50 for reviewRejectType: RETRY, 100 for FINAL, and 70 otherwise. Sumsub emits only approved and rejected verdicts through this integration; an on-hold alert is not an under_review verdict. Subscribe to the compliance.verification-verdict.final event for approved and rejected verdicts. On-hold alerts do not emit this event. Confirm the current claim on the identity before a transfer. A token whose compliance rules require the topic reads the claim when a transfer runs; an earlier approval does not guarantee the claim is still present.

Step 4 failures

SymptomCauseWhat to do
404 DALP-12283The call named another identity without a compliance provider roleRetry with the role, or omit identityAddress for the caller's own identity
404 DALP-12283The call omitted identityAddress, and the caller holds no active identity in the system's identity registryRegister the caller's identity first, or name the identity the session is for
404 DALP-12283The call omitted identityAddress, and the caller holds more than one active identityName the identity in identityAddress
404 DALP-12283A caller with the role named an identity that has no registration in the system's identity registryRegister that identity in step 3, then request the token again
The SDK reports an expired tokenThe access token passed expiresAtRequest a new token
No event after the investor submitsSumsub is not sending to the topic's delivery URL, or the signing secret differs between Sumsub and the providerCopy the delivery URL from the provider's "Webhooks" table into the Sumsub dashboard, and check that both sides use the same secret

Verify a participant covers the remaining work in the Console: confirm the claim on the identity, add an issuer-signed verification by hand when a topic has no provider, and run the token's pre-check before you submit a transfer.

Step 5: recover a wallet with no identity contract

One state leads here: a later GET /api/v2/system/wallets/:wallet/identity for the step 2 wallet answers 404 with DALP-9512. That says the wallet has no identity contract. The user create deploys the identity before it answers, so this state follows a create that answered DALP-12107: the user was kept but the outcome of its identity deployment is unknown, and this call completes it against the chain. A create whose deployment the chain failed answers DALP-12115 and keeps no user, so repeat that create with a new Idempotency-Key instead. A create response whose identity is null is a different case: the user holds no custody key at all (no provider configuration was named, or the configuration is bring-your-own-key), so step 2 has no wallet to resolve and this recovery does not apply.

You call POST /api/v2/identities with the step 2 wallet and walletVerification, then run step 3 once the identity read answers.

curl -X POST "https://your-instance.example.com/api/v2/identities" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Idempotency-Key: create-identity-amara-001" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
    "walletVerification": { "verificationType": "PINCODE", "secretVerificationCode": "482913" }
  }'

A caller that does not own the wallet needs the identity-manager role for this call. An operator onboarding another user does not own that user's wallet, so it needs the role, and a refusal answers DALP-9439.

Wait for the identity read to answer before you register. Reads serve indexed state, so a contract that has just been deployed can still read as missing for a short window, and a registration sent inside that window answers DALP-9513 again. GET /api/v2/system/identity-registration-statuses reports one of NO_SYSTEM, NO_IDENTITY, NOT_REGISTERED, PENDING, or ACTIVE for one wallet, which is one read to branch on instead of combining several.

A create for a wallet that already has an identity contract is refused with DALP-0024. Read the identity's current state rather than repeating the call.

Recover a user whose create failed

A create that failed outright and now answers DALP-0670 on retry is a duplicate email: the organization already holds a user with that address. List the organization's users, locate the record carrying that email, and read its participantId. Then run this same recovery from step 2 with that participant's custody account. The email is unique per organization, so the retry cannot create a second user.

Roles and wallet verification

Every write in the sequence is a mutation, and each one checks the caller before it queues work.

CallRole the caller needsWallet verification
POST /api/v2/usersIdentity managerWhen the calling wallet is protected by a code
GET /api/v2/participants/:participantId/custody-accountsAccess follows participant access in the active organizationNot applicable; a read submits no transaction
POST /api/v2/system/identity-registrationsIdentity managerWhen the calling wallet is protected by a code
POST /api/v2/compliance/subjects/sdk-access-tokenThe session alone for the caller's own identity; a compliance provider role for another identityNot applicable; the call submits no transaction
POST /api/v2/identitiesIdentity manager when the caller does not own the wallet; the session alone when it doesWhen the calling wallet is protected by a code

The SDK-token route is the exception: it answers 404 with DALP-12283 for a caller who names another identity without the role. Every other missing role answers 403 with DALP-9439. walletVerification carries the step-up proof for the caller's own wallet: the method and its secret, such as a pin code, a one-time password, a recovery code, or an identity-provider code.

Errors

CodeStatusWhen it appearsWhat to do
DALP-0670409The email is already a user in the organizationFind the existing user by email and run the recovery in step 5
DALP-12115409The chain failed the created user's identity deployment, so no user was keptRepeat the create with a new Idempotency-Key
DALP-12107503The user was created but the outcome of its identity deployment is unknownRun the recovery in step 5 for the user's wallet
DALP-12103503The user create has not settled inside the synchronous windowRepeat the request with the same Idempotency-Key to wait for it
DALP-9524404The organization has no deployed system on the chain yetDeploy the system, then repeat the request
DALP-9512404The identity read finds no identity for the requested referenceTreat the wallet as having no identity contract and run step 5
DALP-9513404Registration for a wallet with no identity contractCreate the identity first with POST /api/v2/identities, wait for it to index, then register again
DALP-0024409An identity contract already exists, or another write on the wallet is in flightRead the identity's current state before sending the operation again
DALP-9439403The caller's executor holds none of the roles the route requiresRetry as an identity manager, or grant the role before retrying
DALP-12033400A signing request carried no walletVerification blockAdd walletVerification with an enrolled method and repeat the request
DALP-12034401The request named a verification method the account has never enrolledEnroll the method in wallet security settings, or send one the account already holds
DALP-12035403The verification credential does not match, or it was already usedSend a current credential, and use a fresh recovery code because a recovery code works once
DALP-12036429Repeated verification failures locked verification for a cooldownWait the seconds in data.retryAfterSeconds, then verify again

DALP error index lists every code with its machine name, category, and retry flag. Read and handle DALP errors covers the envelope these codes arrive in.

Next steps

  • Idempotency and async settlement covers the Idempotency-Key and Prefer headers on these writes, and the status URL a queued write returns.
  • Verify a participant completes the eligibility work a registered identity still needs.
  • Custody Provider explains what a managed configuration holds, and what a bring-your-own-key participant still has to prove.

On this page