Overview
Onboarding an investor through the API prepares them to hold a regulated token, in 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. A write sent from a signed-in session also carries walletVerification, the step-up proof such as a PIN code, when the caller's wallet is protected by a code. A write sent with an API key needs no proof, because creating the key took one, so anyone who holds the key can sign as its holder until the key is disabled or expires. To create many participants at once, Bulk participant import takes up to 500 rows in one background batch and opens a registration request for each row that passes, which an identity manager then accepts.
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, as From first sign-in to a verified holder describes. Let an investor choose custody covers the investor who picks between a managed account and their own wallet after sign-up. 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. A token's compliance rules cannot check a wallet that has none, so the token refuses that wallet.
From first sign-in to a verified holder
An investor can hold a regulated token once four things are true:
- They have a participant, the platform's record for one compliance subject, with a wallet the organisation can act through.
- That wallet has an on-chain identity, the contract that holds the investor's claims.
- The identity is registered in the organisation's identity registry under a country.
- The identity carries every verification, or claim, that the token's compliance rules require, added by an issuer the token trusts.
The KYC profile review comes before the fourth condition, the claims. It is the organisation's own record of who the investor is, and it decides whether an issuer adds the KYC claim. Six stages follow an investor through the Console and the Investor portal, and the numbered steps from Step 1 run the same work through the Platform API.
Stage 1. The person gets a participant and a wallet. There are two ways in.
- An identity manager creates the user. In the Console this is “Create or invite user” with “Send invitation email” off, described on Users. Through the API it is Step 1. The platform creates the login, a person participant, a wallet on the organisation's managed custody provider, and the identity contract, in one request. An API request names the provider configuration it uses.
- The organisation owner invites the person by email, and the investor sets up their own wallet. After sign-in, the Investor portal opens the “Wallet setup” wizard until the participant the investor acts as is one the organisation can act through and holds a wallet the organisation can act through. For an embedded or browser wallet, the wizard also stays open until that wallet has its identity contract and, in an organisation that uses smart wallets, its smart wallet.
In the “Wallet setup” wizard, the portal decides what the investor sees in this order:
- A participant that still waits for its key proof comes first, when the organisation offers an embedded or browser wallet to prove it with. The portal returns the investor to “Connect and sign” for that same participant.
- When the investor owns a participant the organisation can act through, the portal asks them to pick it. Picking it needs no wallet verification.
- Otherwise, when the organisation offers investors more than one custody provider, the investor picks one on “Wallet type”: “Embedded wallet”, “Browser wallet” or “Managed account”.
- On “Your wallet”, “Create wallet” makes a managed account that the organisation holds. “Connect and sign”, for an embedded or browser wallet, signs a message that proves the investor controls the key. Each create or connect asks for the investor's wallet verification first. A managed account gets its identity contract in the same step.
- Until the proof succeeds, the participant is pending and cannot act in any organisation. A declined signature leaves it pending and the investor can sign again. A pending participant not proven within 30 days stops appearing.
- For an embedded or browser wallet, the portal creates the identity contract once the key is proven, and the investor's wallet signs the identity factory's authorisation for it. When the investor's wallet verification is a one-time code that creating the participant already used, the portal asks for a new one. In an organisation that uses smart wallets, the portal then deploys the smart wallet, and the investor's wallet signs the account factory's authorisation for it.
The portal says “This organisation has not chosen an investor wallet yet” when the investor owns no participant to reuse and the organisation offers no wallet they can use: no provider at all, or, for a participant waiting for its key proof, no embedded or browser wallet. An administrator designates one in Custody.
Stage 2. An identity manager registers the identity. A new identity has no registration, so the user's page reads “Identity registration: pending”. The identity manager registers it under a country from the user's page with “Register identity”, from Entities with “Register existing identity” or by accepting a registration request, or through the API in Step 3. Registration covers every active wallet of the participant on the network. Country allow and block lists read the country it records.
Stage 3. The investor completes the KYC profile. The investor opens Profile in the Investor portal, fills in first name, last name, country, national ID and the fields the organisation's profile sections add, uploads supporting documents of up to 25 MiB each, and selects “Submit for review”. Each save adds a version, and each organisation keeps its own profile for the investor.
Stage 4. A reviewer decides the profile. An identity manager or verification issuer opens the user's “KYC” tab in the Console and selects “Approve”, “Reject” or “Request update”. The person who submitted a version cannot decide it, and neither can an identity manager who corrected one of its fields, so the review is maker-checker: a correction waits for a different reviewer. A platform administrator who submitted or corrected the version while acting as the investor cannot decide it either. When the platform sends mail and the investor has an email address, the investor receives the outcome by email. A requested update shows as an action in the Investor portal. Users describes the review.
Stage 5. An issuer adds the KYC claim. Approving the profile adds no KYC claim. When the identity is registered and the approved profile has a country, the platform adds a residence claim for that country, issued by the organisation's identity. The KYC claim a token checks comes from one of two places. A verification issuer adds it by hand, which the user's checklist prompts with “Issue KYC verification” and Verify a participant describes. Or a compliance provider such as Sumsub issues it when its signed verdict approves the investor, which Step 4 covers. Either way, the issuer must be listed in Trusted issuers for the topic, or the token ignores the claim.
Stage 6. The token checks at transfer time. The investor's wallet can receive the token when it passes that token's compliance rules: the required claims, and any country, list or limit rules the token has. The check runs when the transfer runs, so a claim revoked later stops the next transfer. The user's “Global eligibility” panel shows the organisation-level verdict, and “Recheck now” runs it live.
The sequence at a glance
You create the user with dalp.. When that call settles, you read the participant's custody accounts with dalp. and take the identity subject wallet from the account. When the call outlasts the synchronous wait, you poll the statusUrl the create response returned until the create settles, then read the accounts. With a wallet in hand, you register the identity with dalp., 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., 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 steps 1 to 4.
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/ with the user's email, an optional name, and the providerConfigurationId of an enabled managed provider configuration in the active organisation, which GET /api/ lists for the request's network. The configuration is required, and naming it is what creates the participant, mints the key, and deploys the identity contract, so the rest of this sequence has a 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" \
-H "Idempotency-Key: create-user-amara-001" \
-H "Prefer: wait=55" \
-d '{
"email": "[email protected]",
"name": "Amara Okafor",
"providerConfigurationId": "0198f2f0-6d0b-7f86-8f24-c5e1ef9f9d10"
}'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" }
}The request sends Prefer: wait=55, so the platform holds the answer for up to 55 seconds while the create settles. Without a Prefer header the route answers 202 at once. A 200 means the user and its custody account settled. If user creation exceeds the wait you asked for, the call answers 202 with a durable handle. The Location header and statusUrl name the same authenticated status resource:
{
"requestId": "c02402c036a8e8e85bcf7d80208a3281",
"state": "running",
"statusUrl": "/api/v2/user-create-requests/c02402c036a8e8e85bcf7d80208a3281"
}Poll that URL with the same X-Api-Key, and with the same X-Chain-Id header when the create call sent it. A 202 with state: "running" means creation is still in progress. A 200 returns the created user in data. A failed create returns its named error, including an identity deployment failure. Keep the original handle. Do not submit another create while it is pending. When the organisation's approval policy holds the create for a second user's approval, the 202 carries approvalId, status: "pending- and a statusUrl that reads the approval instead, and the create has not run.
The create waits for the user's custody account to settle before it answers, so the same 202 also covers a custody account that is still settling. A custody account that ends failed or expired fails the create with a 409 and keeps no user. When the user exists and its custody account is still settling, the route answers a 202 that carries a custody 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 /. 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, and you must resolve that before continuing.
The create call, the registration in step 3 and the identity create in Recover a wallet with no identity contract accept Idempotency-Key and 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/ 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. The response below is an excerpt with the fields this step reads. Each item also carries the organisation context, the provider configuration with its capabilities, the account's eligibility and execution health, wallet ids and timestamps, which the v2 API explorer lists in full.
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/ 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 normalises 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"
}'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. When the organisation's approval policy holds the registration for a second user's approval, the 202 carries approvalId, status: "pending- and a statusUrl that reads the approval instead. When the wallet belongs to one participant of the organisation, the call registers every wallet that participant holds and has not archived, together with its smart wallet on that network, and submits only the addresses whose registration is not active. A repeated registration is safe: 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-14223, whose fix says to create or register the identity first, which for this wallet means POST /api/. Do not retry the same registration. Follow Recover a wallet with no identity contract.
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, the named verification a token checks for, such as knowYourCustomer, before you start. A permission manager, system manager or compliance manager 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/ needs the “Permission manager”, “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/ 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 in 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:
An on-hold alert has severity 50 for review, 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
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.
Recover a wallet with no identity contract
One state leads here: a later GET /api/ for the step 2 wallet answers 404 with DALP-14223. 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 network. 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 that this recovery does not cover.
You call POST /api/ with the step 2 wallet, plus walletVerification from a signed-in session, 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"
}'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 403 with DALP-14228.
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-14223 again. GET /api/ 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-14227. So is a create for a participant whose registry entry exists or is being created for another wallet, and so are the participant, user and multisig creates that hit the same case. A user create keeps the user it made. 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-14226 on retry is a duplicate email: a user with that address already exists. List the organisation'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 across the platform, so the retry cannot create a second user. When the list shows no such user, the address belongs to a user outside this organisation, and you need a different address.
Roles and wallet verification
Each write in the sequence checks the caller before it queues work.
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-14228. 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. A request sent with an API key skips the proof, but a cooling-off period after a wallet security change still refuses it with WALLET_VERIFICATION_HELD.
Errors
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-KeyandPreferheaders 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.