SettleMint
API referenceGuides

Read files and object storage

Read public branding assets from /dalp/* with no authentication, and private KYC, organization, and platform files from /dalp/private/* with a session.

Overview

DALP serves public branding assets without authentication and restricts private files to authorized sessions. Backend integrations should use a domain download route, such as the KYC document endpoint, when one exists. This page covers the URL, access, and caching rules for each file type.

Choose public or private

Public assets need no credential. DALP allows public responses to be cached for one year. Private responses require a session and use headers that forbid caching. Keep KYC evidence and other sensitive documents private.

Deleting or moving a public asset cannot recall copies that clients already downloaded. If a sensitive file was exposed, treat those copies as outside the platform's control.

One bucket, a key prefix instead of separate buckets

Object storage is single-bucket per deployment. Tenancy is a key prefix, org_{organizationId}/, applied automatically to every read and write an organization's storage operations make; it is not a bucket per organization. The only unprefixed namespaces are the two with no tenant to prefix for: public assets, and the platform-reserved platform/... namespace.

One interface sits in front of the provider implementations a deployment can select: S3, GCP Cloud Storage, Azure, any S3-compatible backend, and a filesystem provider for a self-hosted or air-gapped deployment. A caller sees the same upload-and-download URL contract on all of them, so an integration written against one deployment's storage backend does not need to change for another's.

Public assets

GET /dalp/* and HEAD /dalp/* serve public assets with no authentication. The object key is everything after /dalp/, and DALP only serves keys under a small set of prefixes: logos/, auth/, backgrounds/, favicons/, icons/. The API cannot add a new public prefix. A key outside the allowed prefixes, or one containing a . or .. path segment, returns 404 Not Found instead of a listing or an error that reveals why the request failed.

curl "https://your-instance.example.com/dalp/logos/org_.../icon.svg"

DALP sets Cache-Control: public, max-age=31536000, immutable on a public asset response: a one-year cache lifetime that tells every intermediary the bytes at this URL never change. Cache it aggressively on your side too. The cost of that promise is the one described above: if a key is ever misclassified as public, the immutable header actively works against you, telling caches to hold onto the exposed copy for a year rather than re-check it. A response also carries ETag and Last-Modified when the stored object has them.

Private files

GET /dalp/private/* and HEAD /dalp/private/* serve private files. The object key is everything after /dalp/private/, and its first segment names a resource type that decides who can read it:

Resource typeOwnerWho can read it
kyc/{userId}/...The user named by {userId}That user, or any user with the admin role.
org/{orgId}/...The organization named by {orgId}A member of that organization, or an admin.
reporting/{orgId}/...The organization named by {orgId}A member of that organization, or an admin. Kept separate from org so retention and legal-hold sweeps can target the regulatory namespace alone.
platform/...The platform itself, no organizationAdmin only. Never served through an organization-scoped route, because nothing under it belongs to a tenant.
statements/{orgId}/...One investor's walletAdmin only on this route. An investor downloads their own statement through GET /api/v2/reporting/statements/{statementId}/download, which checks the wallet.

admin is a single role on the caller's session, not a per-organization membership: a user is admin, owner, or member platform-wide, separate from whatever role they hold inside any one organization. An admin user reads any kyc, org, or reporting key regardless of whose id it names; the role check for those three resource types is skipped entirely once the caller is an admin. Treat that as a scope decision to make deliberately: granting the admin role hands its holder read access to every KYC document and every organization's private files on the deployment, not just administrative controls. platform/... alone requires admin without a bypass to explain, since it is the one resource type no other role can read at all.

A private file request does not accept an API key. Authenticate with a signed-in Console session (cookie) instead. This route is the underlying object-storage surface the Console itself calls to render a file inline in a browser tab. Treat it as browser-context infrastructure, not as an integration point for a backend service, which has no session cookie to send.

curl "https://your-instance.example.com/dalp/private/kyc/user_123/passport.pdf" \
  -H "Cookie: <your session cookie>"

DALP sets Cache-Control: no-cache, no-store, must-revalidate, plus Pragma: no-cache and Expires: 0, on every private file response. Do not cache a private file in a shared cache or CDN.

Reading a KYC document programmatically: use the domain route, not this one

Do not build a /dalp/private/kyc/... URL by hand from a backend integration. KYC documents have their own route that accepts an API key: the KYC document download endpoint. It is a POST because each call issues a new time-limited link rather than reading a fixed one. Call it with X-Api-Key, and it returns a scoped link instead of the file itself:

{
  "data": {
    "downloadUrl": "/api/v2/kyc-profile-versions/.../documents/.../download",
    "fileName": "proof-of-address.pdf",
    "mimeType": "application/pdf",
    "expiresAt": "2026-06-05T15:05:00.000Z"
  },
  "links": { "self": "/api/v2/kyc-profile-versions/.../documents/.../downloads" }
}

The fields you want are under data, as on every other v2 single-resource response.

expiresAt is always one hour out from the call that issued it. What it means depends on the shape of downloadUrl itself, so check that before deciding whether to resend your credential. A relative path starting /api/v2/... means the document is stored under the platform's current encryption. That path stays session-or-API-key-gated even after expiresAt passes, so send the same credential again when you fetch it; here, expiresAt bounds a storage-level link, not your credential's validity. An absolute URL against your object-storage host means an older, pre-encryption document: that link works on its own with no further DALP credential, and it stops working at expiresAt, not before. Request a fresh link if one stops working rather than assuming it is permanently dead.

For other files, use the download or export operation documented for the owning resource. Do not infer a supported integration route from an object-key prefix alone.

Sending an API key here does not do what it does elsewhere

If you send only an X-Api-Key header directly on /dalp/private/*, with no session cookie, you get 401 Unauthorized, the same response as sending no credential at all. The route does not evaluate API keys, so it never distinguishes "your key is invalid" from "you sent no key"; both look identical from the response. If your integration authenticates every other call with an API key, expect this one route to need a different credential path. Do not spend time debugging the key itself when this route is the one failing; check first whether a domain-specific route like the KYC one above already accepts your key.

Upload with a presigned URL

An asset or token document upload is a two-step, presigned flow. Request an upload URL first; the response carries a PUT method, an objectKey, headers to send with the upload, and an expiresAt fifteen minutes out. Send the file bytes directly to that URL, then confirm with the returned objectKey so DALP records the file against its owning record. An Azure-backed deployment needs one provider-specific header on the direct upload, x-ms-blob-type: BlockBlob; other providers need none. Request a fresh upload URL if expiresAt passes or storage rejects the headers you sent.

A KYC document upload deliberately does not follow this presigned pattern at all. The Platform API accepts base64-encoded file bytes directly, so DALP can validate and encrypt the document before object storage ever receives it; a presigned upload would let unvalidated, unencrypted bytes reach storage first. The legacy presigned upload flow still exists but is not for new KYC integrations.

A KYC upload accepts an Idempotency-Key header that labels one attempt of the request, and the platform generates a value automatically when the header is absent. Sending the same key again does not replay the first result: the write runs again, storing the file a second time. A dropped connection during a KYC upload therefore leaves the outcome unclear, and resending the request does not resolve that on its own.

Status codes

StatusMeaning
200The object exists and the caller can read it.
400The path could not be parsed into an object key.
401No signed-in session on the request.
403The session is valid, but its user or organization does not own this object key.
404No object exists at that key. Also returned for a public request whose key is outside the allowed prefixes.
405A method other than GET or HEAD.
503DALP could not reach object storage. Retry once the service recovers.
500An unclassified storage read failure: everything that is not a missing object, a storage-unavailable error, or an auth or scope failure falls here. DALP gives no retry guarantee for it, unlike the explicit 503 case; a 500 that recurs on the same key is a signal to investigate rather than a request to keep retrying blindly.

Neither route has a documented route-specific rate limit; the per-key limit on Authentication and API keys does not apply here either, since these routes reject API keys or never require one. Do not assume a cap that is not documented.

Tell a session problem from a scope problem

401 and 403 mean different things here, and treating them the same wastes a support cycle. 401 means DALP found no valid session at all: no cookie, an expired one, or a request sent from a context that never signed in. 403 means the session is real and DALP knows exactly who you are, but the object key's owner does not match that identity.

The most common cause of an unexpected 403 is an organization mismatch, not a permission gap. A user signed into organization A requesting an org/{orgB-id}/... key gets 403 even though they are fully authenticated: the active organization in their session is A, not B. Switch the session's active organization before retrying, rather than re-authenticating, when this is the actual cause.

Do not construct private object keys by hand

Build a private file URL from the reference your integration already has, such as a document id returned by the workflow that created it, rather than guessing the key. kyc/{userId}/... and org/{orgId}/... only work when {userId} or {orgId} matches the caller's own session; guessing another user's or organization's id returns 403 Forbidden, not the file, and does not reveal whether the object even exists.

Next steps

  • Errors covers the 401 and 403 responses this route returns in the standard error envelope.
  • Authentication and API keys covers API-key authentication and its scope, which private file routes do not accept.

On this page