Skip to content

Uploads API

Resumable uploads in chunks, for large files and several files behind one link. Every request sends a token in the Authorization: Bearer header; see Get started with the API.

POST /api/upload-session

Starts a resumable upload of one file, or of several files behind one link. The response’s artifact says how to send the bytes: when chunk_size is 0, send everything with Upload in one request; otherwise send total_chunks chunks with Upload a chunk, or straight to the bucket when direct_upload is true (Get upload URLs for chunks). Then Finish an upload creates the shared link.

To send several files behind one link, use session_type multi (or folder to keep folders): you write the files into one uncompressed ZIP archive, laid out as described under session_type, under the entry_name each file gets in the response, and upload that archive. A ZIP you already have can also be sent as a single upload.

When you act as a user with Dew-Act-As, the upload belongs to them: send the same header on every later call for this upload session.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.

Content type application/json.

Field Type Description
session_type (required) string: single, multi, folder single: one file, uploaded as it is. multi: several files behind one link. folder: like multi, keeping the folder structure and empty folders. For multi and folder you upload one ZIP archive that you write yourself. It must be exactly artifact.size_bytes long, which it is when you follow this layout: - Entries in order: the empty_dirs names first, as returned, then the files by entry_index, each named by its entry_name. - Every entry stored (no compression), with the UTF-8 name flag set and no extra fields except zip64 ones. No archive comment. - A file entry is a local header (30 bytes plus the name) with the data descriptor flag set and CRC and sizes zero, then the file’s bytes, then a data descriptor with its signature: 16 bytes, or 24 bytes when the file is 4 GiB - 1 bytes or larger. A folder entry is a local header only, with a name ending in /. - An entry uses zip64 when its size or the offset of its local header reaches 4 GiB - 1. Its central directory record then carries the 28-byte zip64 extra field with both sizes and the offset, and both 32-bit size fields are 0xFFFFFFFF. - Add the zip64 end of central directory record and locator when there are 65,535 entries or more, or the central directory’s size or offset reaches 4 GiB - 1. Finish an upload checks the archive’s central directory: exactly the files you listed (minus any you skip), stored, at their declared sizes.
files (required) array of UploadSessionFileInput The files to upload, in archive order. A single upload takes exactly one. A folder upload may send none when empty_dirs lists at least one folder. Requests are capped at 4 MiB; send more files with Add files.
link_type string: public, protected, private, onetime Who can open the shared link: public (anyone with the link), protected (password, SMS code or invited email addresses, set in protection), private (IP allowlist, set in protection) or onetime (one download). Default "public".
protection UploadLinkProtection
domain string The domain for the shared link. It must be active and one you may use. Defaults to the domain you call the API on.
folder_name string multi and folder: the archive’s name, without .zip, at most 1020 bytes. Defaults to archive- plus the date and time.
empty_dirs array of string folder: empty folders to keep, such as 2026/raw.

The upload session was created.

data is UploadSession.

{
  "success": true,
  "data": {
    "session_id": "Hn4Tz8Qa",
    "expires_at": "2026-10-05T09:15:00Z",
    "files": [
      {
        "session_file_id": "sf_Jx2kQ9vR4mT7wY1zA3bC5d",
        "filename": "brief.pdf",
        "entry_name": "brief.pdf",
        "entry_index": 0
      },
      {
        "session_file_id": "sf_Pq8nL3sV6hG0eK2rU9tW4x",
        "filename": "budget.xlsx",
        "entry_name": "budget.xlsx",
        "entry_index": 1
      }
    ],
    "artifact": {
      "file_id": "Hn4Tz8Qa",
      "size_bytes": 502881,
      "chunk_size": 0,
      "total_chunks": 0
    },
    "direct_upload": false,
    "upload_limit_bps": 0
  }
}
Status When Example message
400 The request is invalid: for example an unknown session_type, no files, a single upload with more than one file, an inactive domain, a blocked file type, an unsafe relative_path or invalid link settings. “The ‘.exe’ file type has been blocked by your administrator.”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, the domain is not one you may use (Not allowed to use this domain), or the user’s account is unlicensed (Unlicensed). “Not allowed to use this domain”
413 The upload is larger than the domain allows (Total size exceeds limit (N GB)), or larger than 5 TB on S3-compatible storage. “Total size exceeds limit (100 GB)”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.
503 The domain’s storage is unavailable. Contact your admin. “Storage provider for this domain is not available. Contact your administrator.”
507 The storage doesn’t have enough free space for this upload. “Not enough storage space for this upload”

GET /api/upload-session/{session_id}

Returns an upload session’s state and plan, for example to resume after a restart. The files are not listed again. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).

The upload session.

data is UploadSessionStatus.

{
  "success": true,
  "data": {
    "session_id": "Hn4Tz8Qa",
    "status": "active",
    "session_type": "single",
    "total_files": 1,
    "created_at": "2026-10-04T09:15:00Z",
    "expires_at": "2026-10-05T09:15:00Z",
    "artifact": {
      "file_id": "Hn4Tz8Qa",
      "size_bytes": 104857600,
      "chunk_size": 16777216,
      "total_chunks": 7
    },
    "artifact_status": "pending"
  }
}
Status When Example message
400 The request is invalid; message says what to change.
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.

DELETE /api/upload-session/{session_id}

Cancels an upload that hasn’t finished and deletes everything uploaded for it, including chunks sent straight to the bucket. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).

The upload was canceled.

data is Success.

{
  "success": true,
  "message": "Session canceled"
}
Status When Example message
400 The request is invalid; message says what to change.
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
409 The upload is being finished or has finished. Delete the file instead. “Session is no longer cancelable”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.

POST /api/upload-session/{session_id}/add-files

Adds more files to a multi or folder upload, for example when listing a large folder in batches. Only possible before any bytes of the archive are uploaded: write the archive from the plan this returns, which replaces the earlier one. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).

Content type application/json.

Field Type Description
files (required) array of UploadSessionFileInput The files to add. Requests are capped at 4 MiB.
{
  "files": [
    {
      "filename": "notes.txt",
      "relative_path": "docs/notes.txt",
      "size_bytes": 1024,
      "mime_type": "text/plain"
    }
  ]
}

The files were added.

data is UploadSessionFilesAdded.

{
  "success": true,
  "data": {
    "files": [
      {
        "session_file_id": "sf_Wm5cE1yH7kN3pB9sD2fJ6q",
        "filename": "notes.txt",
        "entry_name": "docs/notes.txt",
        "entry_index": 2
      }
    ],
    "artifact": {
      "file_id": "Hn4Tz8Qa",
      "size_bytes": 504079,
      "chunk_size": 0,
      "total_chunks": 0
    }
  }
}
Status When Example message
400 No files, the upload is single or no longer active, a blocked file type, or an unsafe relative_path. “Files can only be added to a multi-file or folder session”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
409 Bytes of the archive were already uploaded. “Files cannot be added after the upload has started”
410 The upload session expired. Start a new upload. “This upload has expired. Start it again.”
413 The upload is larger than the domain allows (Total size exceeds limit (N GB)), or larger than 5 TB on S3-compatible storage. “Total size exceeds limit (100 GB)”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.
507 The storage doesn’t have enough free space for this upload. “Not enough storage space for this upload”

PATCH /api/upload-session/{session_id}/files/{file_id}/chunk/{chunk_number}

Uploads one chunk of the planned bytes. Chunk n starts at byte (n - 1) × chunk_size and is chunk_size bytes long; the last chunk carries the rest. Chunks can be sent in parallel. Sending a chunk again replaces it, so retry any chunk that fails. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).
file_id (required) path string The upload’s artifact.file_id. It is always the same as session_id.
chunk_number (required) path integer The chunk’s number, from 1 to total_chunks. Minimum 1.
X-Chunk-Offset (required) header integer (int64) Where the chunk starts: (chunk_number - 1) × chunk_size. Minimum 0.
Content-Length (required) header integer (int64) The chunk’s exact length. Chunked transfer encoding is not accepted.

The chunk’s bytes.

Content type application/octet-stream.

The chunk was stored.

data fields:

Field Type Description
chunk_number integer
{
  "success": true,
  "data": {
    "chunk_number": 3
  }
}
Status When Example message
400 The chunk doesn’t match the plan (number, offset or length), X-Chunk-Offset is missing, the upload goes in one request, file_id is not the upload’s, or the upload is no longer active. “Chunk 3 must carry 16777216 bytes at offset 33554432”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
409 This chunk is still being uploaded by another request. Retry in a few seconds. “Chunk is already being uploaded”
410 The upload session expired. Start a new upload. “This upload has expired. Start it again.”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.

GET /api/upload-session/{session_id}/files/{file_id}/chunks

Returns which chunks the server already has, including chunks sent straight to the bucket. Use it to resume an interrupted upload: send only the missing chunks, then finish. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).
file_id (required) path string The upload’s artifact.file_id. It is always the same as session_id.

The chunks received so far.

data is UploadChunkProgress.

{
  "success": true,
  "data": {
    "session_file_id": "Hn4Tz8Qa",
    "completed_chunks": [
      1,
      2,
      3
    ],
    "uploaded_bytes": 50331648,
    "total_bytes": 104857600,
    "chunk_size": 16777216,
    "total_chunks": 7
  }
}
Status When Example message
400 file_id is not the upload’s, or the upload is no longer active. “Session is not active”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
410 The upload session expired. Start a new upload. “This upload has expired. Start it again.”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.

PUT /api/upload-session/{session_id}/files/{file_id}/fast

Uploads all the planned bytes in one request, for uploads whose plan has chunk_size 0. Send exactly artifact.size_bytes bytes: more is refused, and a different size makes finishing fail. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).
file_id (required) path string The upload’s artifact.file_id. It is always the same as session_id.

The file, or your ZIP archive for multi and folder uploads.

Content type application/octet-stream.

The bytes were stored.

data fields:

Field Type Description
uploaded_bytes integer (int64)
{
  "success": true,
  "data": {
    "uploaded_bytes": 502881
  }
}
Status When Example message
400 The upload must be sent in chunks, file_id is not the upload’s, or the upload is no longer active. “File must be uploaded in chunks”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
410 The upload session expired. Start a new upload. “This upload has expired. Start it again.”
413 More bytes were sent than planned. “File exceeds declared size”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.

POST /api/upload-session/{session_id}/files/{file_id}/presign

When the upload session has direct_upload: true, returns URLs to PUT chunks straight to the storage bucket, up to 500 per request. Each URL accepts exactly its chunk’s planned length and stops working after 6 hours; ask for URLs as you need them. Chunks sent this way and chunks sent with Upload a chunk can be mixed. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).
file_id (required) path string The upload’s artifact.file_id. It is always the same as session_id.

Content type application/json.

Field Type Description
part_numbers (required) array of integer Chunk numbers to get URLs for, from 1 to total_chunks. At most 500 items.
{
  "part_numbers": [
    1,
    2,
    3
  ]
}

The URLs, by chunk number.

data is UploadPartUrls.

{
  "success": true,
  "data": {
    "urls": {
      "1": "https://bucket.s3.example.com/Hn4Tz8Qa?partNumber=1&uploadId=VXBsb2FkSWQ&X-Amz-Signature=3f2a",
      "2": "https://bucket.s3.example.com/Hn4Tz8Qa?partNumber=2&uploadId=VXBsb2FkSWQ&X-Amz-Signature=9c1d",
      "3": "https://bucket.s3.example.com/Hn4Tz8Qa?partNumber=3&uploadId=VXBsb2FkSWQ&X-Amz-Signature=b7e4"
    }
  }
}
Status When Example message
400 Direct upload is not available for this domain, the upload is not sent in chunks, part_numbers is empty, has more than 500 numbers or a number out of range, file_id is not the upload’s, or the upload is no longer active. “Direct upload is not available for this domain”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
410 The upload session expired. Start a new upload. “This upload has expired. Start it again.”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.

POST /api/upload-session/{session_id}/finalize

Checks the uploaded bytes against the plan and creates the file and its shared link; for multi and folder uploads it also checks the archive’s file list. Email-protected links send their invitations now. Calling it again after a lost response returns the same links; a 409 Finalizing means it is still running, so ask again in a few seconds. If the upload session was created with Dew-Act-As, send the same header here.

  • Permission: Files: Upload and share (files:write)
  • Act for a user: accepts the Dew-Act-As header. Personal tokens work too.
Name In Type Description
session_id (required) path string The upload session’s ID (session_id from Start an upload).

Send {} when there is nothing to skip.

Content type application/json.

Field Type Description
skip_file_ids array of string multi and folder: the session_file_id of files you left out of the archive’s central directory because they couldn’t be read. Their planned bytes must still be in the archive (as zeros), so its size doesn’t change.
{}

The upload is finished.

data is UploadFinishResult.

{
  "success": true,
  "data": {
    "files": [],
    "zip_file": {
      "id": "Hn4Tz8Qa",
      "url": "https://files.example.com/p/Hn4Tz8Qa",
      "formatted_text": "https://files.example.com/p/Hn4Tz8Qa",
      "filename": "Project files.zip",
      "filesize": 502881,
      "expires_at": "2026-11-03T09:15:00Z"
    }
  }
}
Status When Example message
400 The body is not valid JSON, the upload is no longer active, or what was uploaded doesn’t match the plan: a different size, an archive whose files differ from the ones listed, or nothing left to deliver. In those last cases the uploaded bytes are deleted; start a new upload. “The uploaded archive does not match the files that were registered”
401 The token is missing, wrong, revoked or expired.
403 The token lacks the permission, or the upload session belongs to someone else. “Not your session”
404 No upload session with this ID. “Session not found”
409 Finalizing: an earlier call is still finishing this upload; ask again in a few seconds. Conflict: nothing was uploaded; start a new upload. “This upload is still being finished. Try again in a moment.”
410 The upload session expired. Start a new upload. “This upload has expired. Start it again.”
500 Something failed on the server. error is an error code and ref a reference to quote to your admin, who can find it in the system logs.