Large file uploads
Files over the 100 MB direct-upload cap go through the resumable upload
endpoint at /files/ — the same endpoint the app's uploader uses. It speaks
the open tus 1.0 protocol, so any tus client works, and an
interrupted transfer resumes where it left off instead of starting over. The
maximum file size is 2 GB.
Requires the upload scope. Send the Authorization: Bearer header on
every tus request. Note that one tus upload counts as two requests
against the rate limit (creation and
completion each verify the key).
The flow
- Create the upload:
POST /files/with the total size inUpload-Lengthand file metadata inUpload-Metadata. The response'sLocationheader is the upload URL. - Send bytes:
PATCHthe upload URL withContent-Type: application/offset+octet-streamand the currentUpload-Offset. - Resume after an interruption:
HEADthe upload URL to read the server'sUpload-Offset, then continue thePATCHfrom there.
The upload is admitted against your storage quota at creation (a full quota
fails the create with 507), and the file is committed to the library when
the last byte arrives.
Metadata
Upload-Metadata is a comma-separated list of key base64(value) pairs:
| Key | Required | Description |
|---|---|---|
name | yes | Filename, used for type detection and display. |
type | no | Mime type; inferred from name when omitted. |
batchId | yes | Any short id (up to 32 chars) grouping this upload — e.g. one id per import run. |
collectionId | no | Add the file to a collection you have access to. |
With curl
# 1. create — note the Location header in the response
curl -i -X POST https://your-host/files/ \
-H "Authorization: Bearer na_your_key" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Length: 734003200" \
-H "Upload-Metadata: name $(echo -n 'movie.mp4' | base64), type $(echo -n 'video/mp4' | base64), batchId $(echo -n 'import-2026-07' | base64)"
# 2. send the bytes to the returned Location
curl -X PATCH https://your-host/files/24e533e02ec3bc40c387f1a0e460e216 \
-H "Authorization: Bearer na_your_key" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Offset: 0" \
-H "Content-Type: application/offset+octet-stream" \
--data-binary @movie.mp4
With tus-js-client
import { createReadStream, statSync } from "node:fs"
import { Upload } from "tus-js-client"
const path = "movie.mp4"
const upload = new Upload(createReadStream(path), {
endpoint: "https://your-host/files/",
uploadSize: statSync(path).size,
chunkSize: 50 * 1024 * 1024,
metadata: {
name: "movie.mp4",
type: "video/mp4",
batchId: "import-2026-07",
},
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
onError: (error) => console.error("upload failed", error),
onSuccess: () => console.log("upload finished"),
})
upload.start()
Getting the file id
The tus protocol has no response body on completion, so the file's library id
is not returned by this flow. When your integration needs the id right
away, either upload via POST /api/v1/files
(returns the id immediately, files up to 100 MB) or find the file afterwards
with search — for example by filtering on the
upload's import_date and matching original_name in the results.