Vaze
API Reference

Files

File endpoints of the REST API

All endpoints below require the API-Key header unless noted otherwise.

File objects in responses have this shape:

{
  "id": "5f2d4a1e-...",
  "name": "photo.png",
  "key": "projects/demo/photo.png",
  "mimeType": "image/png",
  "size": 102400,
  "folderId": "9c8b7a6d-...",
  "visibility": "public",
  "url": "api/hosting/projects/demo/photo.png",
  "createdAt": "2026-07-15 12:00:00",
  "updatedAt": "2026-07-15 12:00:00"
}

key is the object's path relative to the storage root — it identifies the file and determines its public URL. url is relative to your instance's base URL; resolve it against that to get the public hosting link (the SDK does this for you).

visibility is public or private. url is always the canonical hosting URL regardless of visibility — for a private file it only works with a signed URL or valid credentials.

List files

GET /api/files

Returns every file on the instance. Supports the list query parameters.

curl "https://files.example.com/api/files?limit=20&orderBy=size&orderDirection=DESC" \
  -H "API-Key: your-api-key"
{ "data": { "files": [ ... ] }, "error": null }

Get a file

GET /api/files?id=<file-id>
GET /api/files?key=<object-key>

Fetch a single file by its id or by its key:

curl "https://files.example.com/api/files?key=projects/demo/photo.png" \
  -H "API-Key: your-api-key"
{ "data": { "file": { ... } }, "error": null }

Responds 404 when no file matches.

Search files by name

GET /api/files?name=<file-name>

Matches the exact file name across every folder — useful because the same name can now exist in more than one folder. Also supports the list query parameters. An empty result is a success with an empty files array.

Upload files

POST /api/files

Send a multipart/form-data body:

FieldTypeDescription
filesfileOne or more files to upload (repeat the field for multiple files)
folderstringOptional target folder path, e.g. projects/demo. Nested folders are created automatically. Defaults to the root folder.
visibilitystringOptional, public or private. Defaults to DEFAULT_FILE_VISIBILITY (public unless configured otherwise). Applies to every file in the request.
curl -X POST https://files.example.com/api/files \
  -H "API-Key: your-api-key" \
  -F "folder=projects/demo" \
  -F "files=@./photo.png" \
  -F "files=@./notes.txt"
{ "data": { "files": [ ... ] }, "error": null }

Files are stored under the name you upload them with, so you control the public URL. Names are unique per folder: the same name in two different folders is fine, but uploading over an existing name responds 409.

The request body is streamed straight to disk, so upload size is bounded by your volume rather than by memory. Uploads land in a staging area and are moved into place only once the whole request has been received — if anything fails partway through, nothing appears in the target folder.

StatusCause
409A file with that name already exists in the target folder
413A file exceeded MAX_UPLOAD_SIZE, or too many files in one request
507Not enough free space on the volume

Rename a file or change its visibility

PUT /api/files

JSON body — name, visibility, or both. At least one is required, or the request responds 400:

{ "id": "<file-id>", "name": "new-name.png", "visibility": "private" }

The name must be a single path segment — separators and .. respond 400. Renaming onto a name already used in the same folder responds 409. An unrecognised visibility responds 400.

Changing visibility alone never touches the bytes on disk. Renaming changes the key, which invalidates any signed URL previously minted for the old key.

Sign a URL

POST /api/files/sign

Mints a time-limited URL that reads a file without an API key — the way to hand a private object to a browser, an <img> tag, or a third party.

JSON body:

{ "key": "projects/demo/report.pdf", "expiresIn": 3600 }
FieldTypeDescription
idstringThe file id. Either this or key is required.
keystringThe object key. Either this or id is required.
expiresInnumberSeconds the link stays valid. Defaults to DEFAULT_PRESIGN_TTL_SECONDS, clamped to MAX_PRESIGN_TTL_SECONDS.
{
  "data": {
    "url": "api/hosting/projects/demo/report.pdf?exp=1786291200&sig=...",
    "expiresAt": "2026-08-09T12:00:00.000Z"
  },
  "error": null
}

Like url on a file object, the returned url is relative to your instance's base URL. Signing a public file is allowed; the link simply also works unsigned.

Signatures are stateless, so an individual link cannot be revoked. Rotating AUTH_SECRET invalidates every outstanding signed URL at once — that is the only way to withdraw them early.

Delete a file

DELETE /api/files

JSON body:

{ "id": "<file-id>" }

Removes the file from disk and the database. Responds 404 if no such file exists; a file already missing from disk still has its record cleaned up and reports success.

Delete several files

Send ids instead of id to delete a batch in one request:

{ "ids": ["<file-id>", "<file-id>"] }

Each id is handled on its own, so one bad id does not stop the rest. The response lists both outcomes:

{
  "data": {
    "deleted": ["<file-id>"],
    "failed": [{ "id": "<file-id>", "message": "File not found" }]
  },
  "error": null
}

Responds 404 only when none of the ids exist, and 400 for an empty list or more than MAX_DELETE_BATCH (default 500) ids.

Download a file

GET /api/files/download/<file-id>

Streams the raw file with Content-Disposition: attachment, so it works directly as a download link in authenticated contexts.

curl -OJ https://files.example.com/api/files/download/<file-id> \
  -H "API-Key: your-api-key"

Supports byte ranges and conditional requests, so an interrupted download can be resumed rather than restarted:

curl -C - -OJ https://files.example.com/api/files/download/<file-id> \
  -H "API-Key: your-api-key"

Serve a file

GET /api/hosting/<object-key>

Streams the file inline with the correct Content-Type, which makes it suitable for embedding in web pages:

<img src="https://files.example.com/api/hosting/projects/demo/photo.png" />

This is the URL carried in the url field of file objects and shown as the "hosting URL" in the dashboard.

Public files need no authentication. A private file is served only to a caller that presents one of:

  • a valid signature (exp + sig) from POST /api/files/sign,
  • an API-Key header,
  • a dashboard session cookie.

Anything else responds 404 — the same response as a key that does not exist, so the endpoint cannot be used to discover which private keys are real. Private responses also carry Cache-Control: private, no-store so a proxy or CDN in front of Vaze cannot retain them.

Add ?download=1 to serve with Content-Disposition: attachment instead of inline. It grants no extra access and is not part of the signature, so it can be appended to a signed URL.

Hosted responses are sent with Content-Security-Policy: sandbox and X-Content-Type-Options: nosniff. Assets render normally; uploaded HTML and SVG render without executing scripts, so hosted content can never act against your instance with your session.

Public hosting URLs are exactly that — anyone who knows a file's key can fetch it. Upload with visibility: private, or set DEFAULT_FILE_VISIBILITY=private on the instance, for anything you don't want reachable by URL.

Range requests and caching

Both /api/hosting/<key> and /api/files/download/<id> are range-aware and revalidatable. Every response carries Accept-Ranges: bytes.

Byte ranges

A Range header returns 206 Partial Content with a Content-Range. This is what lets a browser seek in an audio or video file — Safari in particular refuses to play a video at all from a server that can't serve ranges.

curl -r 0-1023 https://files.example.com/api/hosting/clips/demo.mp4
RequestResult
Range: bytes=0-499206, first 500 bytes
Range: bytes=500-206, byte 500 to the end
Range: bytes=-500206, final 500 bytes
Start past end of file416 with Content-Range: bytes */<size>

An end past the last byte is clamped rather than rejected. Multi-range requests (bytes=0-9,20-29) are answered with the complete file — a server may always decline to honour a range.

Conditional requests

Public responses carry an ETag and Last-Modified derived from the file's size and modification time. Send either back to skip the transfer:

curl -H 'If-None-Match: "a-6899f2c1"' \
  https://files.example.com/api/hosting/projects/demo/photo.png
# → 304 Not Modified, no body

If-None-Match takes precedence over If-Modified-Since when both are sent. If-Range is honoured with strong comparison: if the validator is stale, the whole file is returned rather than a range that would corrupt the cached copy.

Cache-Control on public responses is public, max-age=<HOSTING_CACHE_MAX_AGE>, must-revalidate, defaulting to max-age=0 — always revalidate, which is cheap because a match costs a bodiless 304. Raise HOSTING_CACHE_MAX_AGE when keys on your instance are effectively immutable.

Private files are sent Cache-Control: private, no-store and carry no validators — offering a revalidation path would contradict no-store. Byte ranges still work, so seeking in a private video behaves normally.

On this page