SettleMint
Compliance

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, or other.
  • 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, or image/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-0514 and 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:

TaskCLI command
Upload evidencekyc document-confirm-upload
List evidencekyc documents
Create download URLkyc document-download-url
Delete evidencekyc 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.

ErrorWhat DALP observedState change and caller response
DALP-0610fileData is not valid base64 document data.No document record is created. Encode the raw file bytes as base64 and retry.
DALP-0611fileSize does not match the decoded byte length.No document record is created. Send the raw file byte length, not the base64 length.
DALP-0612The 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-0394A delete targeted a document on a non-draft version.Nothing is removed. Create a new draft version to change the documents.
DALP-0393The 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.

ErrorOperationWhat DALP observedCaller response
DALP-0419List documentsThe 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-0393Download URL or downloadThe 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-0146Download URLThe 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-0609DownloadThe 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.

On this page