KYC document uploads
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.
Attach KYC documents to a draft KYC version when an investor or operator needs to provide identity, address, or other review evidence. The Platform API accepts base64-encoded file bytes, validates the metadata and content, encrypts the file, stores an encrypted envelope, and then creates the document record.
The document record belongs to a KYC version, not directly to the user profile. Submit the version only after you have filled in the required profile fields and attached the supporting files.
Prerequisites
Before uploading, create or select a draft KYC version for the investor. A submitted or under-review version cannot receive new files. If the latest version is no longer draft, create a new draft and upload your replacement files there.
The caller must also be allowed to manage documents for that KYC version. The document owner can request a download URL for their own file. A non-owner needs a KYC document read role, such as identity manager or claim issuer, to request one. How the returned URL is then secured depends on the document; see Download a document.
Supported document inputs
A document upload through user.kyc.documents.confirmUpload requires these
fields:
versionId: draft KYC version ID.documentType:passport,drivers_license,national_id,proof_of_address, orother.fileName: original file name, up to 255 characters.fileSize: raw byte size, greater than zero and up to 25 MiB.mimeType:application/pdf,image/jpeg,image/png, orimage/webp.fileData: base64-encoded raw file bytes.
DALP checks the decoded byte length against fileSize. It also checks that the
file signature matches the declared MIME type. Send the raw byte length before
base64 encoding, not the length of the encoded string.
Quickstart
Call user.kyc.documents.confirmUpload with the KYC version ID and the
base64-encoded file bytes. DALP creates the document record only after the
payload is validated, encrypted, and stored.
The SDK method names mirror the KYC document operations: confirm upload, list,
get download URL, and delete. Direct HTTP integrations use these routes:
- Upload evidence:
POST /api/v2/kyc-profile-versions/{versionId}/documents. - List evidence:
GET /api/v2/kyc-profile-versions/{versionId}/documents. - Create a download URL:
POST /api/v2/kyc-profile-versions/{versionId}/documents/{documentId}/downloads. - Delete evidence:
DELETE /api/v2/kyc-profile-versions/{versionId}/documents/{documentId}.
import { readFile } from "node:fs/promises";
const passportBytes = await readFile("./northwind-passport.pdf");
const document = await client.user.kyc.documents.confirmUpload({
params: { versionId: "kycv_01hzt7n4passportdraft" },
body: {
documentType: "passport",
fileData: Buffer.from(passportBytes).toString("base64"),
fileName: "northwind-passport.pdf",
fileSize: passportBytes.byteLength,
mimeType: "application/pdf",
},
});The response contains the document record you can show in a review screen:
{
"data": {
"id": "kycdoc_01hzt7n4passport001",
"versionId": "kycv_01hzt7n4passportdraft",
"documentType": "passport",
"fileName": "northwind-passport.pdf",
"fileSize": 204800,
"mimeType": "application/pdf",
"uploadedAt": "2026-05-24T10:23:17.628Z",
"uploadedBy": "usr_01hzt7n4reviewer001"
}
}Do not use the legacy presigned upload flow for new KYC integrations. The current KYC document path sends file bytes through the Platform API so DALP can validate and encrypt the document before object storage receives it.
Retry an upload safely
A document upload carries the full file bytes, so a timeout or a dropped
connection can leave you unsure whether the document was stored. Send an
Idempotency-Key header on the upload to make a retry safe: a retry with the
same key and the same payload returns the result of the first upload instead of
storing the file twice. Use one key for one upload, store it alongside your own
upload job, and reuse it only when you retry that exact upload.
The SDK sends the key as a request header from the client configuration. Set
the client's idempotencyKey option for a one-shot client that performs a
single upload, or add a headers callback that returns a fresh
Idempotency-Key per upload when one client runs many uploads. See
Use idempotency safely
for the multi-upload pattern.
import { readFile } from "node:fs/promises";
import { createDalpClient, createDalpContextHeaders } from "@settlemint/dalp-sdk";
import { v2UserKycDocumentsConfirmUpload } from "@settlemint/dalp-sdk/operations";
const passportBytes = await readFile("./northwind-passport.pdf");
const client = createDalpClient({
baseUrl: "https://your-platform.example.com",
apiKey: "YOUR_DALP_API_KEY",
});
const uploaded = await v2UserKycDocumentsConfirmUpload({
client,
path: { versionId: "kycv_01hzt7n4passportdraft" },
body: {
documentType: "passport",
fileData: Buffer.from(passportBytes).toString("base64"),
fileName: "northwind-passport.pdf",
fileSize: passportBytes.byteLength,
mimeType: "application/pdf",
},
headers: createDalpContextHeaders({
idempotencyKey: "kyc-doc-upload-2026-05-24-001",
}),
throwOnError: true,
});Direct HTTP integrations send the header on the upload request:
curl -X POST "https://your-platform.example.com/api/v2/kyc-profile-versions/kycv_01hzt7n4passportdraft/documents" \
-H "X-Api-Key: YOUR_DALP_API_KEY" \
-H "Idempotency-Key: kyc-doc-upload-2026-05-24-001" \
-H "Content-Type: application/json" \
-d '{ "documentType": "passport", "fileName": "northwind-passport.pdf", "fileSize": 204800, "mimeType": "application/pdf", "fileData": "..." }'Retry behavior follows the platform's standard idempotency rules:
- A retry with the same key and the same payload returns the cached response from the first upload. DALP stores one document record, not two.
- A retry while the first upload is still running returns
DALP-0515. Wait, then retry with the same key. - A request that reuses the key with a different payload returns
DALP-0514and is rejected, so a key stays bound to the upload it first ran.
The upload is the KYC document step where an idempotency key matters, because it
is the call that stores file bytes. A retry of a list, download, or delete is
already safe to repeat on its own. For the full retry contract and the 24-hour
response window, see
Idempotency-Key.
List documents
Use user.kyc.documents.list to read documents attached to a KYC version.
Filters can narrow the result set, for example to one document type.
const documents = await client.user.kyc.documents.list({
params: { versionId: "kycv_01hzt7n4passportdraft" },
query: {
filters: [{ id: "documentType", operator: "eq", value: "passport" }],
},
});The response includes paginated document records and metadata, so integrations
can build review screens without fetching every document at once. A list against
a version that does not exist returns DALP-0419; see
Read documents for the read-operation errors.
Download a document
Downloading a document is a two-step flow. First call
user.kyc.documents.getDownloadUrl with the KYC version and document ID. DALP
returns a downloadUrl plus document metadata such as the file name, MIME type,
and an expiresAt timestamp.
const download = await client.user.kyc.documents.getDownloadUrl({
params: {
versionId: "kycv_01hzt7n4passportdraft",
documentId: document.data.id,
},
});Then fetch the returned downloadUrl with an authenticated request. For
documents uploaded through the current encrypted flow, the URL points at
GET /api/v2/kyc-profile-versions/{versionId}/documents/{documentId}/download,
and DALP authenticates each request and checks document ownership or the KYC
document read permission before streaming the decrypted bytes.
The URL is not bound to the session that requested it, so a backend can mint the
URL and a separate authorized caller can retrieve it. The response uses no-store
cache headers.
For these encrypted documents the downloadUrl is not a signed or shareable
link. A copied URL is unusable without an authenticated caller that owns the
document or holds the read permission, so access stays enforced even if the URL
leaks. Treat expiresAt as a hint for when to request a fresh URL, not as a
hard signature expiry. Documents created through the older plaintext upload flow
instead return a storage presigned URL that does expire at expiresAt. In both
cases, request a fresh URL when an operator needs to view the document. Do not
persist the URL as a permanent file reference.
Delete a document
Use user.kyc.documents.delete when an uploaded document was attached to the
wrong version, has the wrong type, or needs to be replaced before submission.
await client.user.kyc.documents.delete({
params: {
versionId: "kycv_01hzt7n4passportdraft",
documentId: document.data.id,
},
});You can delete a document only while its KYC version is still a draft. Once the
version is submitted, under review, approved, or rejected, its documents are
locked. A delete against a non-draft version returns DALP-0394 and changes
nothing. To swap a file after submission, create a new draft version and upload
the replacement there.
Deletion removes the document record from that KYC version in a single
transaction, so the document stops appearing in user.kyc.documents.list right
away. The encrypted file is then deleted from storage as a best-effort cleanup.
If the storage deletion fails, the record is still gone and the API returns
success, but the encrypted object may remain in storage. Deleting a document
does not approve, reject, or submit the KYC version.
For a draft version, if the versionId and documentId do not point at the
same existing document, the request returns DALP-0393 and nothing is removed.
List the documents on the version first when you are unsure which records exist.
CLI equivalents
The DALP CLI exposes the same KYC document flow for operator scripts. Use these commands when a back-office job is easier to run outside the SDK:
| Task | CLI command |
|---|---|
| Upload evidence | kyc document-confirm-upload |
| List evidence | kyc documents |
| Create download URL | kyc document-download-url |
| Delete evidence | kyc document-delete |
For uploads, pass a local filePath, versionId, documentType, fileName,
and mimeType. The CLI reads the local file, checks the 25 MiB limit before the
request, base64-encodes the file bytes, and sends them to the same Platform API endpoint
used by the SDK. If you pass fileSize, it must still match the decoded file
bytes.
Use CLI commands for back-office scripts and SDK calls for application integrations. Both paths follow the same model: send the file bytes through the Platform API, let DALP validate and encrypt the document, then manage the document record on the KYC version.
Validation and error handling
KYC document errors split into two classes, and your retry logic should treat them differently. A terminal error means the request, the document, or the KYC version state is wrong, so a blind retry fails the same way: fix the input first. A retryable error means a backend dependency was briefly unavailable, so the same call can succeed on a later attempt. Store and share the request ID from the API response or HTTP headers when you escalate a repeated platform error.
Upload and manage documents
Treat these as terminal request errors. Fix the document payload or the KYC version state before you retry.
| Error | What DALP observed | State change and caller response |
|---|---|---|
DALP-0610 | fileData is not valid base64 document data. | No document record is created. Encode the raw file bytes as base64 and retry. |
DALP-0611 | fileSize does not match the decoded byte length. | No document record is created. Send the raw file byte length, not the base64 length. |
DALP-0612 | The declared MIME type does not match the file signature. | No document record is created. Upload a PDF, JPEG, PNG, or WebP file with matching bytes. |
DALP-0394 | A delete targeted a document on a non-draft version. | Nothing is removed. Create a new draft version to change the documents. |
DALP-0393 | The versionId and documentId do not match one document. | Nothing is removed. List the documents on the version and retry with a matching pair. |
Read documents
Listing documents, requesting a download URL, and downloading the file have their own failures. Two are terminal, so confirm the version or document ID before retrying. Two are retryable: the platform marks them as a temporary dependency outage, so repeat the same call after a short delay.
| Error | Operation | What DALP observed | Caller response |
|---|---|---|---|
DALP-0419 | List documents | The versionId does not match a KYC version. | Terminal. Confirm the version ID. A version that was never created or has been deleted returns this error, not an empty list. |
DALP-0393 | Download URL or download | The versionId and documentId do not match one stored document. | Terminal. The document was never stored or has been deleted, including after a URL was issued. List the documents on the version and retry with a matching pair. |
DALP-0146 | Download URL | The platform could not reach object storage to mint the URL. | Retryable. Retry after a short delay, then escalate with the request ID if the outage persists. |
DALP-0609 | Download | The stored document envelope could not be read, verified, or decrypted. | Retryable. The record stays private. Storage read failures during the download surface here, not as a separate storage code. Request the document again and escalate if the failure repeats. |
Security and storage model
KYC document bytes are private compliance evidence. DALP validates and encrypts the file before object storage receives it, then stores an encrypted envelope rather than the plaintext file. The public document record stores metadata such as document type, file name, file size, MIME type, upload time, and uploader.
KYC documents and review evidence remain part of the verifier or operator workflow. The on-chain identity and claims model records verification results and references, not the uploaded document bytes.
Related
KYC version submission API
Create, edit, submit, and read back a versioned KYC profile, including the review-outcome trail, through the DALP Platform API, SDK, and CLI.
KYC reviewer version action API
Approve, reject, or request changes on a KYC profile version under review through the DALP Platform API, SDK, and CLI.