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/v1These 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) requirefiles: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.
| Field | Type | Description |
|---|---|---|
id | string (UUID) | The FluidCloud file id. Stable for the life of the file. |
tenant_id | string (UUID) | The tenant (workspace account) the file belongs to. |
space_id | string (UUID) | The space the file lives in. See Spaces. |
folder_id | string (UUID) | null | The containing folder, or null for the space root. See Folders. |
source_app | string | null | The product namespace the file was created under. |
client_key | string | null | Your own logical key supplied at upload time, or null if you didn't set one. See GET /files/resolve. |
original_name | string | The user-facing filename. |
name | string | Alias of original_name (convenience for UIs). |
mime | string | null | Detected MIME type, e.g. image/png. |
size | integer | null | Size in bytes. |
sha256 | string | null | SHA-256 of the bytes (hex). |
scan_status | string | Virus-scan state. A file must be clean before it can be downloaded, previewed, or shared (otherwise 409). See quarantine-until-clean. |
status | string | Lifecycle status of the file record. |
created_by | string | null | Identity that created the file. |
deleted_at | string (ISO-8601 UTC) | null | When the file was soft-deleted, or null if live. |
created_at | string (ISO-8601 UTC) | When the file record was created. |
updated_at | string (ISO-8601 UTC) | When the file record was last modified. |
Example:
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
space_id | string (UUID) | Yes (for normal browsing) | The space to list in. Omit only when trash=true. |
folder_id | string (UUID) | No | Folder to list. Omit to list the space root (files directly in the space, not in any folder). |
status | string | No | Filter to one lifecycle status. Invalid values return 400. |
app | string | No | Filter to one product namespace. Omit to list every namespace in the space. |
trash | boolean | No | When 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=truethe listing spans every space in the tenant (one universal trash) and ignoresspace_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
curl "https://api-cloud.fluidvip.com/api/v1/files?space_id=11111111-2222-3333-4444-555555555555" \
-H "X-API-Key: fck_live_..."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)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
| Name | Type | Required | Description |
|---|---|---|---|
client_key | string | Yes | The logical key you supplied at upload time. |
app | string | No | The 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
curl "https://api-cloud.fluidvip.com/api/v1/files/resolve?client_key=results/job-7/out.png" \
-H "X-API-Key: fck_live_..."file = fc.files.resolve(client_key="results/job-7/out.png")
print(file.id, file.scan_status)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 param | Type | Description |
|---|---|---|
file_id | string (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
curl "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
-H "X-API-Key: fck_live_..."file = fc.files.get("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")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 param | Type | Description |
|---|---|---|
file_id | string (UUID) | The file id. |
Response
{ "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.
409if not clean. Ifscan_statusis not yetclean, this returns409 Conflictinstead of a URL (quarantine-until-clean).
Examples
curl "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/download" \
-H "X-API-Key: fck_live_..."dl = fc.files.download("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
print(dl.url) # GET this URL directly to retrieve the bytesconst dl = await fc.files.download("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
console.log(dl.url); // GET this URL directly to retrieve the bytesA 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 param | Type | Description |
|---|---|---|
file_id | string (UUID) | The file id. |
Response (PreviewOut)
| Field | Type | Description |
|---|---|---|
url | string | Short-lived inline URL to render. |
kind | string | One of image, video, audio, pdf, text. |
mime | string | null | MIME type of the served preview (e.g. image/jpeg when converted). |
converted | boolean | true if a JPEG was generated from a non-web-native image; false if the original is served inline. |
{ "url": "https://<presigned-inline-url>...", "kind": "image", "mime": "image/jpeg", "converted": true }Statuses
409— file isn'tcleanyet (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
curl "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/preview" \
-H "X-API-Key: fck_live_..."pv = fc.files.preview("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")
print(pv.kind, pv.converted, pv.url)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 param | Type | Description |
|---|---|---|
file_id | string (UUID) | The file id. |
Request body
| Field | Type | Description |
|---|---|---|
original_name | string | New filename. Re-validated; an invalid name returns 400/422. |
folder_id | string (UUID) | null | Move 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
# 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"}'# 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)// 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 param | Type | Description |
|---|---|---|
file_id | string (UUID) | The file id. |
Returns the soft-deleted FileOut (with deleted_at set).
curl -X DELETE "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
-H "X-API-Key: fck_live_..."fc.files.delete("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")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 param | Type | Description |
|---|---|---|
file_id | string (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).
curl -X POST "https://api-cloud.fluidvip.com/api/v1/files/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/restore" \
-H "X-API-Key: fck_live_..."fc.files.restore("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f")await fc.files.restore("9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f");Errors
| Status | When |
|---|---|
400 | Bad input (e.g. invalid status filter, or a move target folder in a different space). |
401 | Missing, invalid, or revoked API key. |
403 | insufficient_scope (key lacks files:read/files:write); or the destination folder of a move is outside your access. |
404 | File not found — including ids that belong to another tenant (cross-tenant ids return 404, never 403). |
409 | File is not yet clean — download, preview, and sharing are blocked until the scan clears. |
415 | preview only — no in-app preview exists for this file type. |
422 | Validation error (e.g. an invalid filename), or preview couldn't build an image preview. |
429 | Rate 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.