Skip to content

API Reference — Files

Operations for working with file metadata after a file has been uploaded: list, look up (by id or by your own client_key), rename, move, get a short-lived download URL, get an inline preview URL, soft-delete, and restore.

All routes are under the production base URL:

https://api-cloud.fluidvip.com/api/v1

These endpoints manage file records and access to their bytes. They do not move bytes through FluidCloud — a download or preview returns a short-lived presigned URL that your client fetches directly from storage. To create new files, see Uploads. To make a file publicly shareable at a stable raw link, see Shares.

Scopes. Read operations require files:read; mutating operations (rename, move, delete, restore) require files:write. Your partner API key carries both. See Authentication for how the key is sent.


The FileOut object

Every endpoint on this page (except download and preview, which return URL objects) returns one or more FileOut objects.

FieldTypeDescription
idstring (UUID)The FluidCloud file id. Stable for the life of the file.
tenant_idstring (UUID)The tenant (workspace account) the file belongs to.
space_idstring (UUID)The space the file lives in. See Spaces.
folder_idstring (UUID) | nullThe containing folder, or null for the space root. See Folders.
source_appstring | nullThe product namespace the file was created under.
client_keystring | nullYour own logical key supplied at upload time, or null if you didn't set one. See GET /files/resolve.
original_namestringThe user-facing filename.
namestringAlias of original_name (convenience for UIs).
mimestring | nullDetected MIME type, e.g. image/png.
sizeinteger | nullSize in bytes.
sha256string | nullSHA-256 of the bytes (hex).
scan_statusstringVirus-scan state. A file must be clean before it can be downloaded, previewed, or shared (otherwise 409). See quarantine-until-clean.
statusstringLifecycle status of the file record.
created_bystring | nullIdentity that created the file.
deleted_atstring (ISO-8601 UTC) | nullWhen the file was soft-deleted, or null if live.
created_atstring (ISO-8601 UTC)When the file record was created.
updated_atstring (ISO-8601 UTC)When the file record was last modified.

Example:

json
{
  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
  "tenant_id": "0b9a8c7d-6e5f-4a3b-2c1d-0e9f8a7b6c5d",
  "space_id": "11111111-2222-3333-4444-555555555555",
  "folder_id": null,
  "source_app": "acme",
  "client_key": "results/job-7/out.png",
  "original_name": "out.png",
  "name": "out.png",
  "mime": "image/png",
  "size": 482113,
  "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "scan_status": "clean",
  "status": "ready",
  "created_by": "fck_live_...",
  "deleted_at": null,
  "created_at": "2026-06-23T10:04:11Z",
  "updated_at": "2026-06-23T10:04:18Z"
}

quarantine-until-clean

Every uploaded file is automatically scanned. A file can only be downloaded, previewed, or shared once scan_status is clean. Call those endpoints on a file that is still being scanned (or that failed the scan) and you get 409 Conflict with detail describing that the file isn't ready. Poll GET /files/{file_id} and check scan_status before requesting a download or creating a share. See Concepts for the full lifecycle.


GET /files

List files in a space scope (a folder, or the space root).

Scope: files:read

Query parameters

NameTypeRequiredDescription
space_idstring (UUID)Yes (for normal browsing)The space to list in. Omit only when trash=true.
folder_idstring (UUID)NoFolder to list. Omit to list the space root (files directly in the space, not in any folder).
statusstringNoFilter to one lifecycle status. Invalid values return 400.
appstringNoFilter to one product namespace. Omit to list every namespace in the space.
trashbooleanNoWhen true, list soft-deleted files across the whole tenant instead of live files. With trash=true, omit space_id and folder_id.

Results are ordered by original_name.

Trash listing. With trash=true the listing spans every space in the tenant (one universal trash) and ignores space_id/folder_id. It returns files that were trashed on their own — files swept up when their parent folder was trashed are restored/purged with that folder and are not listed here.

Examples

bash
curl "https://api-cloud.fluidvip.com/api/v1/files?space_id=11111111-2222-3333-4444-555555555555" \
  -H "X-API-Key: fck_live_..."
python
from fluidcloud import FluidCloud

fc = FluidCloud(api_key="fck_live_...")

# Files in the space root
files = fc.files.list(space_id="11111111-2222-3333-4444-555555555555")

# Files in a folder
files = fc.files.list(
    space_id="11111111-2222-3333-4444-555555555555",
    folder_id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
)

# Tenant-wide trash
trashed = fc.files.list(trash=True)
typescript
import { FluidCloud } from "fluidcloud";

const fc = new FluidCloud({ apiKey: "fck_live_..." });

// Files in the space root
const files = await fc.files.list({
  space_id: "11111111-2222-3333-4444-555555555555",
});

// Tenant-wide trash
const trashed = await fc.files.list({ trash: true });

GET /files/resolve

Look up a file by your own logical key (client_key) instead of by FluidCloud's file id.

When you upload (see Uploads) you may attach your own client_key (for example results/job-7/out.png). Later you can resolve that same key back to the file — so a stateless client never has to persist FluidCloud's file id. The most-recent live (non-deleted) match wins; a re-upload under the same key supersedes the old one.

Scope: files:read

Query parameters

NameTypeRequiredDescription
client_keystringYesThe logical key you supplied at upload time.
appstringNoThe product namespace the key lives in.

Returns a single FileOut. Returns 404 if no live file matches the key in your tenant — it never reveals another tenant's keys.

Examples

bash
curl "https://api-cloud.fluidvip.com/api/v1/files/resolve?client_key=results/job-7/out.png" \
  -H "X-API-Key: fck_live_..."
python
file = fc.files.resolve(client_key="results/job-7/out.png")
print(file.id, file.scan_status)
typescript
const file = await fc.files.resolve({ client_key: "results/job-7/out.png" });
console.log(file.id, file.scan_status);

GET /files/{file_id}

Fetch a single file's metadata.

Scope: files:read

Path paramTypeDescription
file_idstring (UUID)The file id.

Returns one FileOut. A file id that isn't in your tenant returns 404 (cross-tenant ids are never distinguishable from "not found"). This is the endpoint to poll on scan_status after an upload.

Examples

bash
curl "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
  -H "X-API-Key: fck_live_..."
python
file = fc.files.get("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
typescript
const file = await fc.files.get("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");

GET /files/{file_id}/download

Get a short-lived presigned GET URL to fetch the file's bytes. Your client downloads directly from storage at the returned URL — FluidCloud never proxies the bytes.

Scope: files:read

Path paramTypeDescription
file_idstring (UUID)The file id.

Response

json
{ "url": "https://<presigned-storage-url>..." }

The URL is time-limited; request a fresh one each time you need to download. The object key is read from the file record on the server — you never supply it.

409 if not clean. If scan_status is not yet clean, this returns 409 Conflict instead of a URL (quarantine-until-clean).

Examples

bash
curl "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/download" \
  -H "X-API-Key: fck_live_..."
python
dl = fc.files.download("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
print(dl.url)  # GET this URL directly to retrieve the bytes
typescript
const dl = await fc.files.download("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
console.log(dl.url); // GET this URL directly to retrieve the bytes

A download URL is a private, expiring link for your own backend. To expose a file at a stable, public raw link (https://files.fluidadmin.com/s/<token>), create a share instead — see Shares and the Raw links guide.


GET /files/{file_id}/preview

Get an inline-renderable URL suitable for an in-app viewer (image, video, audio, PDF, or plain text). For image types a browser can't decode directly (HEIC/HEIF/TIFF/BMP), FluidCloud converts to a JPEG once, caches it, and points the URL at the converted copy — that cached preview does not count against your storage quota.

Scope: files:read

Path paramTypeDescription
file_idstring (UUID)The file id.

Response (PreviewOut)

FieldTypeDescription
urlstringShort-lived inline URL to render.
kindstringOne of image, video, audio, pdf, text.
mimestring | nullMIME type of the served preview (e.g. image/jpeg when converted).
convertedbooleantrue if a JPEG was generated from a non-web-native image; false if the original is served inline.
json
{ "url": "https://<presigned-inline-url>...", "kind": "image", "mime": "image/jpeg", "converted": true }

Statuses

  • 409 — file isn't clean yet (quarantine-until-clean).
  • 415 — the file type has no in-app preview (e.g. an archive or an arbitrary binary).
  • 422 — a preview couldn't be built for an otherwise-previewable image.

Examples

bash
curl "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/preview" \
  -H "X-API-Key: fck_live_..."
python
pv = fc.files.preview("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
print(pv.kind, pv.converted, pv.url)
typescript
const pv = await fc.files.preview("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
console.log(pv.kind, pv.converted, pv.url);

PATCH /files/{file_id}

Rename and/or move a file.

Scope: files:write

Path paramTypeDescription
file_idstring (UUID)The file id.

Request body

FieldTypeDescription
original_namestringNew filename. Re-validated; an invalid name returns 400/422.
folder_idstring (UUID) | nullMove the file. Set to a folder id to move it there, or null to move it to the space root. Omit the field entirely to leave the location unchanged.

A destination folder must exist and be in the same space as the file (otherwise 400); a missing destination folder returns 404. Returns the updated FileOut.

Examples

bash
# Rename
curl -X PATCH "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
  -H "X-API-Key: fck_live_..." \
  -H "Content-Type: application/json" \
  -d '{"original_name": "final-render.png"}'

# Move into a folder
curl -X PATCH "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
  -H "X-API-Key: fck_live_..." \
  -H "Content-Type: application/json" \
  -d '{"folder_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}'
python
# Rename
fc.files.update("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", original_name="final-render.png")

# Move into a folder
fc.files.update(
    "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
    folder_id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
)

# Move to the space root
fc.files.update("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", folder_id=None)
typescript
// Rename
await fc.files.update("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", {
  original_name: "final-render.png",
});

// Move into a folder
await fc.files.update("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", {
  folder_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
});

// Move to the space root
await fc.files.update("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", { folder_id: null });

See Organizing files for spaces and folders.


DELETE /files/{file_id}

Soft-delete a file. This sets deleted_at and moves the file to trash; it is restorable (restore). It does not erase the stored bytes.

Scope: files:write

Path paramTypeDescription
file_idstring (UUID)The file id.

Returns the soft-deleted FileOut (with deleted_at set).

bash
curl -X DELETE "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
  -H "X-API-Key: fck_live_..."
python
fc.files.delete("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
typescript
await fc.files.delete("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");

Permanent deletion is separate. This endpoint only soft-deletes. To permanently purge bytes from storage, see Deletion and the Deleting data guide.


POST /files/{file_id}/restore

Restore a soft-deleted file from trash (clears deleted_at).

Scope: files:write

Path paramTypeDescription
file_idstring (UUID)The file id (must currently be soft-deleted).

Returns the restored FileOut. Returns 404 if the file no longer exists (e.g. it was already permanently purged).

bash
curl -X POST "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/restore" \
  -H "X-API-Key: fck_live_..."
python
fc.files.restore("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
typescript
await fc.files.restore("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");

Errors

StatusWhen
400Bad input (e.g. invalid status filter, or a move target folder in a different space).
401Missing, invalid, or revoked API key.
403insufficient_scope (key lacks files:read/files:write); or the destination folder of a move is outside your access.
404File not found — including ids that belong to another tenant (cross-tenant ids return 404, never 403).
409File is not yet cleandownload, preview, and sharing are blocked until the scan clears.
415preview only — no in-app preview exists for this file type.
422Validation error (e.g. an invalid filename), or preview couldn't build an image preview.
429Rate limited; retry after the Retry-After header.

See Errors for the full error model and Rate limits for limit details. The SDKs raise typed errors (AuthError, QuotaExceededError, PermissionError, NotFoundError, ConflictError, ApiError) — see Python and TypeScript.


FluidCloud API — part of the Fluidvip ecosystem.