Organizing files: spaces, folders, trash
Every file in FluidCloud lives inside a space, and optionally inside a folder within that space. This guide shows how to structure your storage with the partner API: creating and listing spaces, building and reshaping the folder tree, browsing per space, and using the universal Trash to soft-delete and restore both files and folders.
All calls go to the production base URL https://api-cloud.fluidvip.com/api/v1 and authenticate with your API key (see authentication.md). The endpoints below require the spaces:*, folders:*, and files:* scopes that come with every partner key.
For the data model behind spaces, folders, and files, see concepts.md. For the full request/response shapes, see reference/spaces.md, reference/folders.md, and reference/files.md.
The shape of your storage
tenant (your account)
└── space "My files" (default) + any spaces you create
└── folder nestable tree (parent_id chains)
└── folder
└── file belongs to a space, optionally to a folder- A space is a top-level bucket. Your account always has at least one — the default "My files" space, provisioned automatically the first time you list spaces.
- A folder is a node in a tree inside one space. A folder can have a parent folder (
parent_id); a folder with no parent sits at the space root. - A file belongs to exactly one space and either to one folder or to the space root (
folder_idisnull). - Folders and files cannot cross spaces. A folder's parent and a file's target folder must be in the same space.
IDs are UUID strings; timestamps are ISO-8601 UTC. Anything addressed by an ID that does not belong to your account returns 404 (never 403), so cross-account IDs are indistinguishable from non-existent ones.
Spaces
List spaces
Listing spaces also provisions your default space on a brand-new account, so the response is never empty for an authenticated key. Spaces come back oldest-first, so "My files" leads.
curl https://api-cloud.fluidvip.com/api/v1/spaces \
-H "X-API-Key: $FLUIDCLOUD_API_KEY"from fluidcloud import FluidCloud
fc = FluidCloud(api_key="fck_live_...")
for space in fc.spaces.list():
print(space.id, space.name)import { FluidCloud } from "fluidcloud";
const fc = new FluidCloud({ apiKey: "fck_live_..." });
const spaces = await fc.spaces.list();
for (const space of spaces) {
console.log(space.id, space.name);
}Each space looks like:
{
"id": "0b1c…",
"tenant_id": "9f2a…",
"name": "My files",
"app": "fluidcloud",
"created_at": "2026-06-23T10:00:00Z",
"updated_at": "2026-06-23T10:00:00Z"
}Requires the spaces:read scope.
Create a space
Use a separate space to keep a workload's files apart from everything else — for example one space per environment, customer, or job batch. The name is a free-text label (1–200 characters); it is not a path component, so spaces and punctuation are fine.
curl -X POST https://api-cloud.fluidvip.com/api/v1/spaces \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Render outputs"}'space = fc.spaces.create(name="Render outputs")
print(space.id)const space = await fc.spaces.create({ name: "Render outputs" });
console.log(space.id);Returns 201 with the new space. Requires the spaces:write scope. An empty or over-200-character name returns 400.
The partner API can create and list spaces, but it does not rename or delete them. Manage those from the dashboard at cloud.fluidvip.com. To clear out a space's contents, trash its folders and files (below).
Folders
Create a folder
A folder belongs to a space_id. Pass parent_id to nest it; omit parent_id to put it at the space root. Folder names are path components — they are validated and may not contain a path separator or traversal sequence (an invalid name returns 422).
curl -X POST https://api-cloud.fluidvip.com/api/v1/folders \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "2026", "space_id": "0b1c…"}'year = fc.folders.create(name="2026", space_id=space.id)
june = fc.folders.create(name="june", space_id=space.id, parent_id=year.id)const year = await fc.folders.create({ name: "2026", spaceId: space.id });
const june = await fc.folders.create({
name: "june",
spaceId: space.id,
parentId: year.id,
});Returns 201. Requires folders:write. If parent_id points at a folder in a different space, you get 400.
List folders in a space
Browsing is per space. Pass space_id; pass parent_id to list the children of one folder, or omit it to list the space root. Results are sorted naturally (so 1, 2, …, 10, not 1, 10, 2).
# Root of the space
curl -G https://api-cloud.fluidvip.com/api/v1/folders \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
--data-urlencode "space_id=0b1c…"
# Children of a folder
curl -G https://api-cloud.fluidvip.com/api/v1/folders \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
--data-urlencode "space_id=0b1c…" \
--data-urlencode "parent_id=$YEAR_ID"roots = fc.folders.list(space_id=space.id) # space root
children = fc.folders.list(space_id=space.id, parent_id=year.id)const roots = await fc.folders.list({ spaceId: space.id });
const children = await fc.folders.list({ spaceId: space.id, parentId: year.id });Requires folders:read. When browsing (not Trash), space_id is required — omitting it returns 422.
Listing files within a folder uses the files endpoint with the same
space_id/folder_idscoping. See Uploading and reference/files.md.
Rename and move folders
PATCH /folders/{folder_id} both renames and moves. Send name to rename (re-validated as a path component), and/or send parent_id to move:
parent_idset to another folder → re-parent under it (must be in the same space, or400).parent_idset tonull→ move to the space root.
# Rename
curl -X PATCH https://api-cloud.fluidvip.com/api/v1/folders/$JUNE_ID \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "2026-06"}'
# Move to the space root
curl -X PATCH https://api-cloud.fluidvip.com/api/v1/folders/$JUNE_ID \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"parent_id": null}'fc.folders.update(june.id, name="2026-06") # rename
fc.folders.update(june.id, parent_id=None) # to space root
fc.folders.update(june.id, parent_id=other.id) # re-parentawait fc.folders.update(june.id, { name: "2026-06" }); // rename
await fc.folders.update(june.id, { parentId: null }); // to space root
await fc.folders.update(june.id, { parentId: other.id }); // re-parentRequires folders:write. A move that would put a folder inside its own descendant (or make it its own parent) returns 400.
Moving files between folders is a file operation, not a folder one:
PATCH /files/{file_id}with a newfolder_id(ornullfor the space root). The target folder must be in the same space as the file. See reference/files.md.
Folder stats
Get the recursive size and file count of a folder's whole subtree (descending into subfolders), excluding anything in Trash:
curl https://api-cloud.fluidvip.com/api/v1/folders/$YEAR_ID/stats \
-H "X-API-Key: $FLUIDCLOUD_API_KEY"stats = fc.folders.stats(year.id)
print(stats.file_count, stats.total_bytes)const stats = await fc.folders.stats(year.id);
console.log(stats.fileCount, stats.totalBytes);{ "folder_id": "…", "file_count": 128, "total_bytes": 5242880 }Requires folders:read. These counts reflect only live content — trashed subfolders and trashed files are not included.
Trash: soft-delete and restore
Deleting a file or folder via the API is a soft delete — it moves the item to Trash, where it stays fully restorable. The underlying bytes are not removed by a soft delete. Browsing (the listing calls above) hides trashed items; a separate Trash view lists them.
Soft-deleted bytes still count against your storage quota until they are permanently removed. To reclaim quota, empty the Trash — covered in Deleting data.
The universal Trash
Normal browsing is per space, but Trash is one tenant-wide view spanning every space. To list it, pass trash=true and omit space_id:
# Trashed folders (across all spaces)
curl -G https://api-cloud.fluidvip.com/api/v1/folders \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
--data-urlencode "trash=true"
# Trashed files (across all spaces)
curl -G https://api-cloud.fluidvip.com/api/v1/files \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
--data-urlencode "trash=true"trashed_folders = fc.folders.list(trash=True)
trashed_files = fc.files.list(trash=True)const trashedFolders = await fc.folders.list({ trash: true });
const trashedFiles = await fc.files.list({ trash: true });The Trash view lists only items you trashed directly. When you trash a folder, everything inside it is swept along but does not appear as separate Trash entries — those nested items restore (or purge) together with the folder they came in with. The parent_id / folder_id scope does not apply in Trash mode.
Trash a file, restore a file
# Trash
curl -X DELETE https://api-cloud.fluidvip.com/api/v1/files/$FILE_ID \
-H "X-API-Key: $FLUIDCLOUD_API_KEY"
# Restore
curl -X POST https://api-cloud.fluidvip.com/api/v1/files/$FILE_ID/restore \
-H "X-API-Key: $FLUIDCLOUD_API_KEY"fc.files.delete(file_id) # → Trash
fc.files.restore(file_id) # back from Trashawait fc.files.delete(fileId); // → Trash
await fc.files.restore(fileId); // back from TrashBoth return the file row (with deleted_at set on delete, cleared on restore). Requires files:write.
Trash a folder, restore a folder (recursive)
Trashing a folder is recursive: the folder, all of its subfolders, and every file inside it move to Trash together in one batch, so nothing is left hidden-but-stored.
# Trash the whole subtree
curl -X DELETE https://api-cloud.fluidvip.com/api/v1/folders/$FOLDER_ID \
-H "X-API-Key: $FLUIDCLOUD_API_KEY"
# Restore it
curl -X POST https://api-cloud.fluidvip.com/api/v1/folders/$FOLDER_ID/restore \
-H "X-API-Key: $FLUIDCLOUD_API_KEY"fc.folders.delete(folder_id) # recursive → Trash
fc.folders.restore(folder_id) # recursive ← Trashawait fc.folders.delete(folderId); // recursive → Trash
await fc.folders.restore(folderId); // recursive ← TrashRequires folders:write. Restore behaves as the exact inverse of the trash that swept the subtree in:
- It brings back only the items that were trashed in the same batch (matched by the shared delete timestamp). Anything you had separately trashed inside that folder earlier stays in Trash.
- If the folder's original parent is gone or still trashed, the restored folder is re-homed to the space root rather than left live-but-unreachable. Check the returned
parent_idto see where it landed.
Quota is held until permanent deletion
Soft-deleting (a file or a recursive folder trash) does not release storage quota — the bytes stay counted while the items sit in Trash, so a trash/restore round-trip never drifts your usage. Quota is reclaimed only when items are permanently deleted from Trash. See Quota and usage and Deleting data.
Errors you may hit here
| Status | Meaning in this context |
|---|---|
400 | Bad input — e.g. empty space name, or a parent_id / folder_id in a different space, or a move into a folder's own descendant. |
401 | Missing, invalid, or revoked API key. |
403 | insufficient_scope — your key lacks the scope the endpoint needs. |
404 | Space, folder, or file not found — including any ID that isn't yours (cross-account IDs return 404, never 403). |
422 | Validation — e.g. browsing folders/files without space_id, or a folder name containing a path separator. |
429 | Rate limited; honor the Retry-After header. |
See errors.md for the full error model and rate-limits.md for the per-key limits. The SDKs raise typed errors (PermissionError for 403, NotFoundError for 404, and so on).
Related
- Uploading files — get bytes into a space/folder.
- Raw links — share a file at
https://files.fluidadmin.com/s/<token>. - Quota and usage — read your storage and share-link limits.
- Deleting data — permanently empty the Trash and reclaim quota.
- Reference: spaces · folders · files