KYC version submission
Create, edit, submit, and read back a versioned KYC profile, including the review-outcome trail, through the DALP Platform API, SDK, and CLI.
KYC data on DALP is versioned. A user's identity submission lives in a KYC profile version that moves through a fixed lifecycle: you create a draft, edit it, attach documents, and submit it for review. Submission locks the version and hands it to a reviewer, who approves it, rejects it, or requests changes.
Each step is a single Platform API call against the user or version ID: read the profile, list versions, create a draft, read or update a draft, and submit it. The reviewer decisions that follow submission live in KYC reviewer version actions. The operator walkthrough in the Console lives in Provide KYC data.
Version lifecycle
A version holds one status at a time, and each status allows a fixed set of operations. The happy path runs in order: create a draft, edit it, attach documents, then submit.
| Status | What it means | What you can do |
|---|---|---|
draft | The version is being prepared. | Edit fields, attach or remove documents, and submit. |
under_review | The version is in the review workflow. | Read the submission. Editing is locked until review ends. |
approved | A reviewer accepted the version. | Read the approved data. Create a new draft to make changes. |
rejected | A reviewer declined the version. | Read the rejection reason and create a corrected draft. |
A user keeps at most one open draft. A single-version read returns canEdit,
canSubmit, and canReview flags so your integration can drive the interface from
the platform's view of the current state instead of inferring it. The profile read
reports the user's overall position one level up, through the computed status
field described below.
Read the profile
Read the profile to find the user's overall KYC standing, approved version, latest version, and whether an update is pending. Start here when you build a KYC screen for a user or decide whether a participant may proceed with a KYC-gated operation.
const profile = await client.user.kyc.profile.read({
params: { userId: "usr_01hzt7n4investor0001" },
});Direct HTTP integrations call GET /api/v2/kyc-profiles/{userId}.
The status field is the profile's computed standing across its version history.
Read it to decide whether a participant has cleared KYC without walking the version
list yourself.
| Status | What it means |
|---|---|
incomplete | The user has only a draft, or no version at all. |
submitted | A version has been submitted and is awaiting review. |
under_review | A version is actively being reviewed. |
approved | An approved version is in effect; the user has cleared KYC. |
rejected | The latest version was rejected. |
update_required | A reviewer requested changes; the user must resubmit. |
Alongside status, the response carries hasPendingUpdate, the approvedVersion
pointer, the latestVersion pointer, an openActionRequestsCount, and the
profile's createdAt and updatedAt timestamps. The approvedVersion and
latestVersion objects are null until the user has a version in that state.
{
"data": {
"id": "kyc_01hzt7n4profile00001",
"userId": "usr_01hzt7n4investor0001",
"status": "approved",
"hasPendingUpdate": false,
"approvedVersion": {
"id": "kycv_01hzt7n4approvedversion",
"versionNumber": 2,
"firstName": "Maria",
"lastName": "Santos",
"contentHash": null,
"approvedAt": "2026-05-24T11:02:47.880Z",
"approvedBy": "usr_01hzt7n4reviewer0001"
},
"latestVersion": {
"id": "kycv_01hzt7n4approvedversion",
"versionNumber": 2,
"status": "approved",
"isDraft": false,
"createdAt": "2026-05-24T09:58:11.204Z"
},
"openActionRequestsCount": 0,
"createdAt": "2026-05-24T09:45:33.112Z",
"updatedAt": "2026-05-24T11:02:47.880Z"
},
"links": {
"self": "/api/v2/kyc-profiles/usr_01hzt7n4investor0001"
}
}List versions
List a user's versions to show their submission history. The list filters by status, paginates, and reports which version is approved and which is current, so a review queue can show one user's submissions without extra calls.
const versions = await client.user.kyc.versions.list({
params: { userId: "usr_01hzt7n4investor0001" },
query: { status: { inArray: "under_review,approved" }, limit: 10 },
});Direct HTTP integrations call GET /api/v2/kyc-profiles/{userId}/versions. The
endpoint uses the collection envelope: data holds the page of versions, meta
reports the total count and facet breakdowns, and links carries pagination links.
The response sorts by versionNumber ascending by default and supports sorting and
filtering on versionNumber, status, createdAt, submittedAt, and
reviewedAt. Page through results with page[offset] and page[limit].
Each version row includes isDraft, isUnderReview, isApproved, and isCurrent
flags, so an interface can label the draft in progress, the submission under review,
and the approved version currently in effect without recomputing status.
The meta.facets block reports a count for each status value across the current
filtered set, so a review queue can show how many versions sit in each status
without a second call. The counts reflect the active filters.
Create a draft
Create a draft to start a new submission. A draft clones its starting values from
the user's approved version by default, or from the latest version. When a user
has no version yet, supply the starting values through initialData.
const draft = await client.user.kyc.versions.create({
params: { userId: "usr_01hzt7n4investor0001" },
body: {
cloneFrom: "approved",
initialData: {
firstName: "Maria",
lastName: "Santos",
country: "PT",
},
},
});Direct HTTP integrations call POST /api/v2/kyc-profiles/{userId}/versions. The
initialData object carries the identity fields the user supplies. A date of birth,
when provided, must place the user at age 18 or older. The new draft returns with
status set to draft and the next versionNumber.
{
"data": {
"id": "kycv_01hzt7n4newdraft0001",
"versionNumber": 2,
"status": "draft",
"userId": "usr_01hzt7n4investor0001",
"createdAt": "2026-05-24T09:58:11.204Z",
"createdBy": "usr_01hzt7n4investor0001"
}
}After the draft exists, attach supporting files with the
KYC document uploads API.
Documents can be added or removed only while the version stays in draft.
Update a draft
Update a draft to correct identity fields before submission. Only a draft version
accepts edits. Once a version leaves draft, create a new draft to make further
changes.
const updated = await client.user.kyc.version.update({
params: { versionId: "kycv_01hzt7n4newdraft0001" },
body: {
residencyStatus: "resident",
},
});Direct HTTP integrations call PATCH /api/v2/kyc-profile-versions/{versionId}.
Read a version
Read a single version to show its full state and workflow metadata. The response
carries the identity fields, the version status, the attached documentsCount,
and the canEdit, canSubmit, and canReview flags so your integration can drive
the interface from the platform's view of the current state.
const version = await client.user.kyc.version.read({
params: { versionId: "kycv_01hzt7n4newdraft0001" },
});Direct HTTP integrations call GET /api/v2/kyc-profile-versions/{versionId}.
Read back the review trail
The same response shows who acted on the version at each stage, so an audit or review-queue integration can read back a finished decision without inferring it. The platform fills these fields in as the version moves through the workflow:
| Field | When the platform sets it | What it holds |
|---|---|---|
createdBy | On draft creation | The user who created the draft version. |
submittedAt | On submission | The time the version entered review. |
submittedBy | On submission | The user who submitted the version for review. |
reviewedAt | On a reviewer decision | The time a reviewer approved, rejected, or requested changes. |
reviewedBy | On the same decision | The reviewer who made the call. |
reviewOutcome | On the same decision | The outcome: approved, rejected, or changes_requested. |
reviewNotes | On a reviewer decision | The note or reason saved alongside the outcome. For changes_requested, the platform stores the required reason here when the reviewer does not add an explicit note. |
rejectionReason | On a rejection | The reason given when a reviewer rejects the version. |
A field stays null until the version reaches the stage that sets it. A draft has
no submission or review fields yet; an approved or rejected version carries the
full trail. Read the version after a reviewer acts to confirm the outcome and who
signed off.
{
"data": {
"id": "kycv_01hzt7n4submittedversion",
"userId": "usr_01hzt7n4investor0001",
"versionNumber": 2,
"status": "approved",
"submittedAt": "2026-05-24T09:58:11.204Z",
"submittedBy": "usr_01hzt7n4investor0001",
"reviewedAt": "2026-05-24T11:02:47.880Z",
"reviewedBy": "usr_01hzt7n4reviewer0001",
"reviewOutcome": "approved",
"reviewNotes": "ID and proof of address verified against the form fields.",
"rejectionReason": null,
"documentsCount": 2,
"canEdit": false,
"canSubmit": false,
"canReview": false
}
}For the decisions that set these fields, see
KYC reviewer version actions.
To scan outcomes across every version of a profile, list the versions and sort by
reviewedAt; the list returns each version's reviewOutcome and review actor and
timestamp, but read a single version when you need its reviewNotes or
rejectionReason.
Submit for review
Submit a draft to send it into the review workflow. Submission moves the version to
under_review, records who submitted it and when, and locks the version against
further edits. Documents are optional at the platform level, though your
organization may require specific documents before a reviewer approves the profile.
const submitted = await client.user.kyc.version.submit({
params: { versionId: "kycv_01hzt7n4newdraft0001" },
});Direct HTTP integrations call POST /api/v2/kyc-profile-versions/{versionId}/submissions.
{
"data": {
"id": "kycv_01hzt7n4newdraft0001",
"status": "under_review",
"submittedBy": "usr_01hzt7n4investor0001"
}
}After submission, a reviewer acts on the version. See KYC reviewer version actions for the approve, reject, and request-update decisions and their outcomes.
State transitions
| Step | From | To | Side effect |
|---|---|---|---|
| Create | none | draft | A new draft version is created with the next version number. |
| Update | draft | draft | Identity fields on the draft are changed. |
| Submit | draft | under_review | The version is locked and the profile enters review. |
CLI equivalents
The DALP CLI exposes the same submitter steps for operator scripts. Reach for it when a back-office job is easier to run outside the SDK:
| Task | CLI command |
|---|---|
| Read profile | kyc profile |
| List versions | kyc versions |
| Create draft | kyc version-create |
| Read version | kyc version-read |
| Update draft | kyc version-update |
| Submit | kyc version-submit |
Validation and error handling
KYC submission returns two classes of error, and your retry logic should treat them differently. A terminal error means the request or the version state is wrong, so a blind retry fails the same way. An operational write failure means validation passed but an internal write did not complete. These are marked non-retryable, so do not resend the same call automatically.
Terminal request errors
Treat these as terminal unless the response says otherwise. Fix the request or the version status before you retry, and store the request ID from the API response when you escalate a repeated platform error.
| Error | What DALP observed | Caller response |
|---|---|---|
DALP-0391 | Submit targeted a version that is not in draft. | No change is made. Only a draft version can be submitted. |
DALP-0392 | Update targeted a version that is not in draft. | No change is made. Create a new draft to make further changes. |
DALP-0413 | Create requested a clone but the user has no version to clone from. | Provide initialData for a user who is submitting for the first time. |
DALP-0416 | Profile read or create found no KYC profile for the user. | Confirm the user ID, or create the first draft to start the profile. |
DALP-0418 | Create referenced a source version that does not exist. | Confirm cloneFromVersionId, or provide initialData instead. |
DALP-0421 | Version read targeted an ID that does not match a KYC version. | No change is made. Confirm the version ID. |
Operational write failures
These return HTTP 500 after the request passed validation: DALP completed the state-transition check, then a database write returned no result. The error envelope marks these as non-retryable, so do not resend the same call automatically. Escalate with the request ID and the version or user ID rather than changing the payload. For an audit trail, each code names the exact write that did not complete, so a failed onboarding step is traceable to a single operation.
| Error | Operation | What DALP observed | Caller response |
|---|---|---|---|
DALP-0400 | Create | The profile container was confirmed, but the locked profile row needed for version creation could not be read back. | Escalate with the request ID and the user ID. The profile is unchanged. |
DALP-0401 | Create | The draft version data was prepared, but the insert that creates the version returned no result. | Escalate with the request ID and the user ID. No draft was created. |
DALP-0408 | Update | The version was confirmed as a draft and the field changes were prepared, but the update write returned no result. | Escalate with the request ID and the version ID. No fields were modified. |
DALP-0407 | Submit or request update | The state transition was validated, but the write that updates the version record returned no result. This can occur during a submit-for-review or a changes-requested step. | Escalate with the request ID and the version ID. The version stays in its current state until the write lands. |
Related
Identity recovery API
Preview, start, and monitor identity recovery workflows for users who lost access to a wallet.
KYC document upload and download API flow
Upload, confirm, list, download, and delete KYC documents through the DALP API, SDK, and CLI, with auth-gated download URLs that re-check access on every request.