Skip to main content

Onboard an investor through the API

Onboard and verify an investor end to end: participant and wallet, identity registration, KYC review and the claim, then the same steps through the API.

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:

  1. They have a participant, the platform's record for one compliance subject, with a wallet the organisation can act through.
  2. That wallet has an on-chain identity, the contract that holds the investor's claims.
  3. The identity is registered in the organisation's identity registry under a country.
  4. 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:

  1. 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.
  2. When the investor owns a participant the organisation can act through, the portal asks them to pick it. Picking it needs no wallet verification.
  3. 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”.
  4. 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.
  5. 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.
  6. 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

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.

You create the user with dalp.api.party.users.create. When that call settles, you read the participant's custody accounts with dalp.api.participants.custodyAccountsList 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.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 steps 1 to 4.

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 and revokes it for a rejected one. An on-hold alert revokes the claim only when its severity reaches the topic's revocation threshold.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. Your own identity-manager key signs the registration. 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 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 organisation, which GET /api/v2/custody/provider-configurations 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-approval" 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 /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, 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/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. 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/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 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-approval" 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/v2/identities. 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/v2/compliance/providers 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/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 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:

Sumsub event
Claim 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
applicantOnHold
The platform records a monitoring alert and revokes the claim when its severity is at or above the topic's revocation threshold. Below that threshold, nothing changes on chain.

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

Symptom
Cause
What to do
404 DALP-12283
The call named another identity without a compliance provider role
Retry with the role, or omit identityAddress for the caller's own identity
404 DALP-12283
The call omitted identityAddress, and the caller holds neither an active identity nor a pending registration of its own in the system's identity registry
Register the caller's identity first, or name the identity the session is for
404 DALP-12283
The call omitted identityAddress, and the caller holds more than one active identity
Name the identity in identityAddress
404 DALP-12283
A caller with the role named an identity that has no registration in the system's identity registry
Register that identity in step 3, then request the token again
The SDK reports an expired token
The access token passed expiresAt
Request a new token
No event after the investor submits
Sumsub is not sending to the topic's delivery URL, or the signing secret differs between Sumsub and the provider
Copy 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.

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-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/v2/identities 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/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-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.

Call
Role the caller needs
Wallet verification
POST /api/v2/users
Identity manager
When the calling wallet is protected by a code
GET /api/v2/participants/:participantId/custody-accounts
Access follows participant access in the active organisation
Not applicable. A read submits no transaction
POST /api/v2/system/identity-registrations
Identity manager
When the calling wallet is protected by a code
POST /api/v2/compliance/subjects/sdk-access-token
The session alone for the caller's own identity. A compliance provider role for another identity
Not applicable. The call submits no transaction
POST /api/v2/identities
Identity manager when the caller does not own the wallet. The session alone when it does
When 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-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

Code
Status
When it appears
What to do
409
A user with the email already exists, the organisation has no seat left for the user, or the user's custody account ended failed or expired
For a duplicate email, follow Recover a user whose create failed. For a full organisation, free a seat, then repeat the create with a new Idempotency-Key. For an ended custody account, repeat the create with a new Idempotency-Key
409
The chain failed the created user's identity deployment, so no user was kept
Repeat the create with a new Idempotency-Key
503
The user was created but the outcome of its identity deployment is unknown
503
The binding read of the created user's identity never resolved, so no user was kept
Repeat the create with a new Idempotency-Key after a short backoff
404
The organisation has no deployed system on the chain yet
Deploy the system, then repeat the request
404
Neither the identity read nor a registration finds an identity for the wallet
Treat the wallet as having no identity contract and run Recover a wallet with no identity contract, which creates it with POST /api/v2/identities, waits for it to index, then registers
409
An identity contract already exists, another write on the wallet is in flight, or the participant already holds or is creating a registry entry for another wallet
Read the identity's current state before sending the operation again
403
The caller holds none of the roles the route requires for this participant
Retry as an identity manager, or grant the role before retrying
400
A signing request from a session carried no walletVerification block
Add walletVerification with an enrolled method and repeat the request
401
The request named a verification method the account has never enrolled
Enrol the method in wallet security settings, or send one the account already holds
403
The verification credential does not match, or it was already used
Send a current credential, and use a fresh recovery code because a recovery code works once
429
Repeated verification failures locked verification for a cooldown
Wait 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.