Upload files directly to storage
This guide describes how to add and replace files in DAM using the direct upload flow, where your system uploads the file bytes straight to Azure Blob Storage instead of sending them through the DAM API.
Overview
Traditionally, files were created by sending the raw bytes to DAM in the request body (the import-from-byte-data endpoint). That approach forces every byte to travel through the DAM API, which does not scale well for large files or replace-heavy integrations.
With direct upload, DAM hands you a short-lived, writable URL pointing directly at Azure Blob Storage. Your system uploads the bytes to that URL, and DAM imports the file asynchronously once the upload completes. This removes the DAM API as a bottleneck and lets you upload large local files efficiently.
The flow has two phases:
- Request an upload URL from DAM. DAM reserves a file identifier and returns a writable URL.
- Upload the bytes directly to that URL.
Because the import happens asynchronously, you do not get the finished file in the response. Instead, you subscribe to the AfterFileCreated and AfterFileCreateFailed webhooks and correlate them with the fileId returned in phase 1 to learn when the file is ready or whether the import failed.
Note
The import-from-byte-data endpoint is deprecated. New integrations should use the direct upload flow described here. See Migrating from import-from-byte-data below.
Phase 1 — Request an upload URL
Call the upload-url endpoint to reserve a file and obtain a writable URL.
Request body:
| Field | Required | Description |
|---|---|---|
fileName |
Yes | The name the file will be created with. Up to 128 characters. |
parentFolderId |
Yes | The id of the folder the file will be placed in. |
description |
No | A description for the file. Up to 4000 characters. |
fileTypeId |
No | The file type to assign to the file. |
tryLoadExifData |
No | When true, EXIF metadata is read from the uploaded file and stored as attributes. When omitted, no EXIF metadata is read. |
reduceImageToRecommendedSize |
No | When true, an oversized transformable image is reduced to the recommended size after import. When omitted, the image is imported unchanged. |
Response:
| Field | Description |
|---|---|
uploadUrl |
A writable SAS URL pointing directly at Azure Blob Storage. Upload the file bytes here. |
validUntilUtc |
The UTC timestamp after which uploadUrl expires. Upload the bytes before this time. |
fileId |
The identifier the file will have once it is imported. Use it to correlate the webhook later. |
Warning
The uploadUrl is only valid until validUntilUtc. If you do not upload the bytes before it expires, the reservation is cleaned up automatically and you must request a new upload URL.
This endpoint requires the AddFile permission and validates the target folder and file type before returning a URL.
Phase 2 — Upload the bytes
Upload the file content to the returned uploadUrl with an HTTP PUT. Because this is a block blob SAS URL, set the x-ms-blob-type header to BlockBlob. See Azure's Put Blob reference for the request format.
A successful upload returns 201 Created from Azure Blob Storage. You do not call DAM again — DAM detects the completed upload and imports the file asynchronously.
If the SAS URL has expired, the upload is rejected by Azure Blob Storage. Request a new upload URL and try again.
Code examples
The examples below perform the complete add-file flow — requesting the upload URL and uploading the bytes — with the Bizzkit .NET SDK: first by calling the API directly alongside the Azure Storage SDK, and then with the Bizzkit.Sdk.Dam.Upload helper, which performs both phases in a single call.
Both examples use an authenticated DAM SDK client created with DamClientFactory:
Without the upload helper
Call the upload-url endpoint with the DAM SDK client, then upload the bytes to the returned URL with the Azure Storage Blob client library. It depends on the Bizzkit.Sdk.Dam and Azure.Storage.Blobs packages:
DAM imports the file asynchronously once the upload completes. Use upload.FileId to correlate the AfterFileCreated and AfterFileCreateFailed webhooks and learn when the file is ready or whether it failed.
With the upload helper
The Bizzkit.Sdk.Dam.Upload package provides an upload helper that performs both phases for you — it requests the upload URL and uploads the bytes in a single call:
Register the factory and the helper, then resolve IDamFileUploader:
Upload a file. The helper returns the pre-allocated FileId that the file will be created with:
The helper also accepts a source URL that Azure Storage copies from directly, without streaming the bytes through your process:
As with the direct API calls, DAM imports the file asynchronously. Use result.FileId to correlate the AfterFileCreated and AfterFileCreateFailed webhooks and learn when the file is ready or whether it failed.
Knowing when the file is ready
Because the import runs asynchronously, the file is not immediately available after the upload. To know when it is ready, subscribe to the AfterFileCreated webhook.
When DAM finishes importing the file, it sends an AfterFileCreated event whose payload contains the identifiers of the created files:
Match the fileId you received in phase 1 against FileIdsOfCreated to correlate the event with your upload.
If the import fails — for example, the uploaded blob cannot be promoted into a file — DAM instead sends an AfterFileCreateFailed event with the same fileId in its FileIdsOfFailed payload. Subscribe to it to handle the unhappy path.
For details on subscribing to webhooks and verifying their signatures, see the Events concept page.
Replacing a file
Replacing an existing file uses the same two-phase upload, but you start from the replace-upload-url endpoint, which lets you carry metadata over from the old file. See the DAM API reference for the complete replace-upload-url specification.
Request body:
| Field | Description |
|---|---|
targetFileId |
The identifier of the existing file to replace. |
newFileName |
The file name for the replacement file. |
copyAttributes |
Copy the attributes from the replaced file to the new file. |
copyTags |
Copy the tags from the replaced file to the new file. |
copyDescription |
Copy the description from the replaced file to the new file. |
tryLoadExifData |
Read EXIF metadata from the uploaded file, when available. |
reduceImageToRecommendedSize |
Reduce the image to the recommended size during import. |
The response has the same shape as the upload-url response (uploadUrl, validUntilUtc, fileId). Upload the bytes exactly as in phase 2, and rely on the webhook to learn when the replacement is complete. This endpoint requires the ReplaceFile permission.
Replacing with the upload helper
The same IDamFileUploader used for adding files also has a method to replace the existing files. ReplaceFileFromStreamAsync requests the replace-upload URL and uploads the replacement bytes in a single call:
As with adding a file, the helper also accepts a source URL that Azure Storage copies from directly, without streaming the bytes through your process:
Both methods return a DamFileReplaceResult carrying the information known at upload time — the TargetFileId being replaced, the NewFileName, and the FileId the replaced file will be created with once the upload is promoted. The replacement is imported asynchronously, so use result.FileId to correlate the AfterFileCreated and AfterFileCreateFailed webhooks and learn when the replacement is ready or whether it failed.
Migrating from import-from-byte-data
The import-from-byte-data endpoint is deprecated. It remains functional for backward compatibility, but new integrations should use the direct upload flow.
Previously, you sent the bytes in the request body:
Now, request an upload URL and upload the bytes directly to storage:
POST /api/{culture}/files/upload-url?api-version=26.0to reserve the file and get a writable URL.PUTthe bytes to the returneduploadUrl.- Wait for the
AfterFileCreatedwebhook, correlating byfileId.
This keeps the file bytes off the DAM API, scales better for large files, and uses the same readiness signal as the rest of the direct upload flow.
The full flow
The sequence below shows the complete add-file flow, from requesting an upload URL to receiving the readiness webhook.
sequenceDiagram
participant Client
participant DAM
participant Blob as Azure Blob Storage
Client->>DAM: POST /api/{culture}/files/upload-url
DAM-->>Client: uploadUrl, validUntilUtc, fileId
Client->>Blob: PUT bytes to uploadUrl
Blob-->>Client: 201 Created
Blob-->>DAM: Blob-created event
DAM->>DAM: Import file (async)
DAM-->>Client: AfterFileCreated webhook (FileIdsOfCreated)