Overview
A backend integration reads and writes DALP files through domain routes that accept an API key: the asset document routes under /, and the KYC document upload and download routes. The /dalp/* routes serve public branding assets to anyone, and /dalp/private/* serves stored private files to a platform administrator's signed-in Console session only and refuses an API key.
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.
Tenancy in object storage
Object storage is one bucket per deployment. Writes made through an organisation's storage scope carry the key prefix org_{organizationId}/, and an organisation's public branding assets sit under it too: a logo is stored at org_ and served from /. KYC documents, stored under kyc/{participantId}/..., and the platform-reserved platform/... namespace sit outside that prefix. For a file outside the prefix, the route that serves it decides who may read it.
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, after an optional org_{organizationId}/ organisation prefix: 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/org_<organizationId>/logos/light/<id>-icon.svg"DALP sets Cache- on a public asset response: a one-year cache lifetime that tells every intermediary the bytes at this URL never change. Cache it on your side too. The cost is that a file stored under a public key by mistake stays in caches for a year, because the header tells them not to re-check it. A response also carries ETag and Last-Modified when the stored object has them.
Private files
GET /dalp/ and HEAD /dalp/ serve a stored private file to a platform administrator only. The object key is everything after /dalp/private/, and it needs at least two segments, such as kyc/{participantId}/.... A signed-in caller without the platform admin role receives 403 Forbidden for every key, whatever resource type or id the key names. Every other caller reads a file through the route of the resource it belongs to:
admin is a single role on the caller's session, not a per-organisation membership: a user is admin or member platform-wide, separate from whatever role they hold inside any one organisation. A platform administrator reads every valid private key, whatever its resource type and whatever id it names. Grant the admin role deliberately, because besides the administrative controls it gives its holder read access to every stored KYC document and every organisation's private files on the deployment.
A private file request does not accept an API key. A platform administrator authenticates with a signed-in Console session (cookie) instead. A backend service has no session cookie to send, so it reads each file through the route of its resource.
curl "https://your-instance.example.com/dalp/private/kyc/user_123/passport.pdf" \
-H "Cookie: <your session cookie>"This request returns the stored object only when the session belongs to a platform administrator. Any other signed-in caller receives 403 Forbidden, whether or not an object exists at that key, and reads the document through the KYC document download route.
DALP sets Cache-, plus Pragma: no- and Expires: 0, on every private file response. Do not cache a private file in a shared cache or CDN.
Read a KYC document through its domain route
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": "2027-01-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.
downloadUrl is always a relative path starting /api/v2/. The path stays gated by a session or API key after expiresAt, so send your credential each time you fetch it. expiresAt is always one hour after the call that issued the link, and it bounds the link only, not your credential. No KYC document is served through a link that anyone holding it can use.
A document that the v1 KYC upload stored before 3.2 as unencrypted is encrypted in place by a background job on the runner, which also deletes the unencrypted object. Until the job reaches it, the same /api/v2/ path serves that document straight from storage, still behind your credential.
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.
API keys are refused on private file routes
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 its response is the same for “your key is invalid” and “you sent no key”. If your integration authenticates every other call with an API key, expect this one route to need a different credential path.
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-. Other providers need none. Request a fresh upload URL if expiresAt passes or storage rejects the headers you sent.
A KYC document upload does not use a presigned URL. Send it to POST /api/ with documentType, fileName, fileSize, mimeType and the file as base64 in fileData, so the platform can validate and encrypt the document before object storage receives it.
The v1 KYC API still offers a presigned upload. Its link points at an object key that ends in /upload under the version, and the client sends the file there unencrypted. The confirm call must name that upload key, and any other key is refused. The platform requires a draft version, reads the uploaded object and refuses it when it is over 25 MiB, when its size differs from the declared size, or when its bytes are not the declared type. It then encrypts the file and stores the encrypted copy the same way as a v2 upload, so the document downloads through a relative /api/v2/ path. The platform deletes the uploaded object at the end of that confirm, whether the confirm stored the document, refused it or failed. After a refused or failed confirm, request a new upload link and send the file again. If you never confirm, a job on the runner deletes the uploaded object 30 minutes after the link was issued, which is the 15 minutes the link lasts plus 15 minutes of margin. The file sits unencrypted in object storage until the confirm or that deletion, so upload KYC documents through the v2 route.
A v2 KYC upload repeated with the same Idempotency-Key header replays the first one. The platform derives the document's id from the organisation, the version, the calling user and the key. A retry with the same key, from the same user, on the same version answers with the document the first request stored and stores nothing again, including when two attempts arrive at the same time. A retry with the same key and a different file, whether its bytes, document type, filename, size or media type differ, answers 400, and data.errors names Idempotency-Key. Send a new key for each new file. Without the header, each request stores a new document.
This replay differs from the transaction queue's rules in Idempotency and async settlement. It has no 24-hour window: it holds as long as the document exists. After the document is deleted, the same key stores the file again. The replay answers with the stored document's details, not a stored copy of the first response.
Asset documents
An asset's documents, such as its prospectus, term sheet and holder notices, use the presigned flow. The file goes between the client and object storage, and the Platform API hands out the links and records the document.
The upload link response sets expiresAt 15 minutes out, and the download link response one hour out. A file is at most 52,428,800 bytes, which is 50 MiB, and is a PDF, JPEG, PNG, WebP, DOCX, XLSX or CSV file. A brand_logo document is a PNG, JPEG or WebP image of at most 5,242,880 bytes, which is 5 MiB. An upload link request for any other file type or size is refused with 400 REQUEST_INVALID (DALP-14261), and data.errors names the rule. The platform stores a brand logo as public whatever visibility the request names. The confirm call also refuses and deletes a brand_logo whose stored size differs from the declared size, whose stored bytes are not the declared image type, or that is over 2048 pixels on either side. The document type must fit the asset type.
The confirm call names the objectKey, the filename, size and type, the document type, the visibility, which is public, holders or restricted, and an optional title and description. To publish a new version of a document, send the document's groupId as replaceGroupId and the same documentType. A confirm that names another documentType answers 404 DALP-0322, the same answer as an unknown group. The platform reads the stored file back, refuses it when its size differs from the size the request declared, computes the SHA-256 fingerprint from the stored bytes and records the document. A confirmed holders document emits the token.document.published webhook event.
Only callers in the issuing organisation, or in the organisation whose own system registers the asset, reach its documents. Within that scope, visibility decides who reads a document. Every caller reads public documents. A caller whose wallet holds the asset, has units allocated or money contributed in one of its primary offerings that were not withdrawn or refunded, or holds any role on the asset also reads holders documents. A caller with the governance or admin role on the asset also reads restricted documents. A download request beyond the caller's level answers 404 with DALP-9486, the same answer as a missing document, so the response does not reveal that the document exists. A superseded or deleted version answers the same way.
Asset documents and holder notices describes the same flow in the Console.
Status codes
These routes answer an error with a short plain-text body, such as Forbidden, and no DALP error envelope or DALP- id, so branch on the HTTP status.
/dalp/* and /dalp/private/* have no route-specific rate limit. The per-key limit on Authentication and API keys does not apply, because these routes reject API keys or never require one.
Tell a session problem from a scope problem
A 401 and a 403 have different causes. 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 who you are, but your user does not hold the platform admin role, or the key has a . or .. segment or fewer than two segments. The platform checks this before it reads storage, so a refused key answers 403 whether or not an object exists there.
A caller without the platform admin role, an organisation owner included, receives 403 on every private key, including a key that names their own user or organisation. Switching the active organisation or signing in again does not change the answer. Read the file through the route of its resource instead: the KYC document download route, the statement download route or the asset document routes.
Next steps
- Errors covers the error envelope of the Platform API routes, which the file routes do not use.
- Authentication and API keys covers API-key authentication and its scope, which private file routes do not accept.