Deleting data
FluidCloud gives a partner API key two distinct ways to delete a file, and they are not the same thing:
- Soft delete (trash + restore) — reversible. The file moves to Trash, but its bytes stay in storage and keep counting against your storage quota until you purge them. You can restore it.
- Permanent delete (purge) — irreversible. The bytes are removed from object storage, the database row is deleted, share links are revoked, and the freed bytes are returned to your quota. There is no undelete.
Both flows are available to a partner key with the files:write scope. Listing/reading the trash needs files:read.
Irreversibility. Permanent deletes (
/gdpr/files/{id}and/gdpr/trash) destroy data with no recovery path at the application layer. There is no "undelete" endpoint. Treat these calls as final. If you might need the file again, use soft delete instead.
See the deletion endpoint reference for the exact request/response shapes.
Choosing a level
| You want to… | Use | Reversible? | Frees quota? |
|---|---|---|---|
| Remove a file from view but keep it recoverable | DELETE /files/{id} (soft delete) | Yes — POST /files/{id}/restore | No (bytes still counted) |
| Get back a soft-deleted file | POST /files/{id}/restore | — | — |
| Permanently destroy one file and reclaim its bytes | DELETE /gdpr/files/{id} | No | Yes |
| Permanently empty the whole Trash (or one space's) | DELETE /gdpr/trash | No | Yes |
A common, safe pattern is soft delete → verify → purge later: soft-delete in your hot path, then run a background sweep that calls DELETE /gdpr/trash to reclaim storage on a schedule.
1. Soft delete (trash)
DELETE /files/{id} marks the file as deleted and moves it to Trash. The underlying stored object is not removed, so the file is fully restorable and its bytes still count toward your storage quota. The call returns the updated file record (note deleted_at is now set).
Required scope: files:write.
curl
curl -X DELETE \
https://api-cloud.fluidvip.com/api/v1/files/3f2504e0-4f89-41d3-9a0c-0305e82c3301 \
-H "X-API-Key: fck_live_..."Python (fluidcloud)
from fluidcloud import FluidCloud
fc = FluidCloud(api_key="fck_live_...")
file = fc.files.delete("3f2504e0-4f89-41d3-9a0c-0305e82c3301")
print(file.deleted_at) # now set -> the file is in TrashTypeScript (fluidcloud)
import { FluidCloud } from "fluidcloud";
const fc = new FluidCloud({ apiKey: "fck_live_..." });
const file = await fc.files.delete("3f2504e0-4f89-41d3-9a0c-0305e82c3301");
console.log(file.deletedAt); // now set -> the file is in TrashRestore from trash
POST /files/{id}/restore clears deleted_at and returns the file to its folder.
curl -X POST \
https://api-cloud.fluidvip.com/api/v1/files/3f2504e0-4f89-41d3-9a0c-0305e82c3301/restore \
-H "X-API-Key: fck_live_..."file = fc.files.restore("3f2504e0-4f89-41d3-9a0c-0305e82c3301")
print(file.deleted_at) # None -> restoredconst file = await fc.files.restore("3f2504e0-4f89-41d3-9a0c-0305e82c3301");
console.log(file.deletedAt); // null -> restoredListing the trash
To find soft-deleted files before purging, list with trash=true. The Trash spans every space in your tenant, so you can omit space_id:
curl "https://api-cloud.fluidvip.com/api/v1/files?trash=true" \
-H "X-API-Key: fck_live_..."trashed = fc.files.list(trash=True)
for f in trashed:
print(f.id, f.original_name, f.size)const trashed = await fc.files.list({ trash: true });
trashed.forEach((f) => console.log(f.id, f.originalName, f.size));Listing requires files:read. See Organizing files for the full listing parameters.
2. Permanent delete (purge)
Permanent deletes live under the /gdpr prefix. They remove the stored object and the database row, revoke and delete every share link for the file, and return the freed bytes to your storage quota. There is no recovery.
Both purge operations require the files:write scope.
This cannot be undone. Once a purge call returns success, the bytes are gone from storage and the row is gone from the database. There is no undelete endpoint and no support recovery path. Operational backups age out under a short retention policy, so no copy survives long-term either. Confirm you have the right id before calling.
Purge one file — DELETE /gdpr/files/{id}
Permanently destroys a single file you own and frees its bytes. This works whether or not the file is currently in Trash — you do not have to soft-delete first. It returns 204 No Content on success.
The SDKs do not ship a dedicated helper for this destructive endpoint, so call it via the SDK's low-level request method (which still applies your API key, base URL, and typed error mapping).
curl
curl -X DELETE \
https://api-cloud.fluidvip.com/api/v1/gdpr/files/3f2504e0-4f89-41d3-9a0c-0305e82c3301 \
-H "X-API-Key: fck_live_..."
# -> HTTP/1.1 204 No ContentPython (fluidcloud, low-level request)
from fluidcloud import FluidCloud
fc = FluidCloud(api_key="fck_live_...")
# No high-level helper for a permanent purge — call the raw endpoint.
fc.request("DELETE", "/gdpr/files/3f2504e0-4f89-41d3-9a0c-0305e82c3301")
# 204 No Content -> the file's bytes are permanently gone and quota is freed.TypeScript (fluidcloud, low-level request)
import { FluidCloud } from "fluidcloud";
const fc = new FluidCloud({ apiKey: "fck_live_..." });
// No high-level helper for a permanent purge — call the raw endpoint.
await fc.request("DELETE", "/gdpr/files/3f2504e0-4f89-41d3-9a0c-0305e82c3301");
// 204 No Content -> the file's bytes are permanently gone and quota is freed.A purge of a file id that is not yours (or does not exist) returns 404 — cross-tenant ids never leak existence. See Errors.
Empty the trash — DELETE /gdpr/trash
Permanently purges everything currently in Trash: every soft-deleted file (objects + rows, with their share links revoked) and the now-empty trashed folders. This is how you reclaim the storage that soft-deleted files were still occupying.
- Omit
space_idto empty the universal Trash — every space in your tenant. - Pass
space_idto empty just that one space's Trash.
The work runs in the background: the call returns immediately with the count and byte total being purged, and reports {"status": "emptying", ...}. Re-requesting the same scope while a sweep is already running is a no-op. Because the sweep is asynchronous, a successful response means the purge was accepted, not that every byte is already gone — poll the trash listing if you need to confirm completion.
curl
# Empty the universal Trash (every space)
curl -X DELETE \
https://api-cloud.fluidvip.com/api/v1/gdpr/trash \
-H "X-API-Key: fck_live_..."
# Empty just one space's Trash
curl -X DELETE \
"https://api-cloud.fluidvip.com/api/v1/gdpr/trash?space_id=8c1f0b2a-6d4e-4a11-9b3c-0a1b2c3d4e5f" \
-H "X-API-Key: fck_live_..."
# -> {"status":"emptying","files":42,"bytes":10485760}Python (fluidcloud, low-level request)
from fluidcloud import FluidCloud
fc = FluidCloud(api_key="fck_live_...")
# Whole-tenant Trash
result = fc.request("DELETE", "/gdpr/trash")
print(result["files"], result["bytes"]) # being purged
# One space's Trash
fc.request(
"DELETE",
"/gdpr/trash",
params={"space_id": "8c1f0b2a-6d4e-4a11-9b3c-0a1b2c3d4e5f"},
)TypeScript (fluidcloud, low-level request)
import { FluidCloud } from "fluidcloud";
const fc = new FluidCloud({ apiKey: "fck_live_..." });
// Whole-tenant Trash
const result = await fc.request("DELETE", "/gdpr/trash");
console.log(result.files, result.bytes); // being purged
// One space's Trash
await fc.request("DELETE", "/gdpr/trash", {
params: { space_id: "8c1f0b2a-6d4e-4a11-9b3c-0a1b2c3d4e5f" },
});What gets cleaned up on a purge
When you permanently purge a file (either endpoint), FluidCloud, in one atomic operation:
- Revokes and deletes every share link for the file. Each link is added to the edge denylist before its row is removed.
- Deletes the stored object, removing the actual bytes.
- Returns the freed bytes to your storage quota — your usage drops by the file's size (read it via the quota endpoint; see Quota and usage).
- Deletes the file record. It will no longer appear in any listing or resolve by
client_key.
Edge-cache note on revoked links. A purge revokes the file's share links, but a previously issued permanent raw link can still serve from edge cache until its max-age of 3600 seconds (one hour) lapses. If you need a link to stop serving immediately, do not rely on the cache window — and never treat a purge as an instant content takedown. See Raw links for link behavior.
Errors you may see
All errors are an HTTP status with a JSON body keyed on detail. The most relevant cases for deletion:
| Status | Meaning |
|---|---|
| 401 | Missing, invalid, or revoked API key. |
| 403 | insufficient_scope — your key lacks files:write (or files:read for the trash listing). |
| 404 | File not found or not yours. Cross-tenant ids always return 404, never 403, so existence never leaks. |
| 429 | Rate limited. Honor the Retry-After header. |
The SDKs map these to typed errors — AuthError (401), PermissionError (403), NotFoundError (404), and ApiError for the rest. Full table in Errors and limits in Rate limits.
from fluidcloud import FluidCloud, NotFoundError, PermissionError
fc = FluidCloud(api_key="fck_live_...")
try:
fc.request("DELETE", "/gdpr/files/3f2504e0-4f89-41d3-9a0c-0305e82c3301")
except NotFoundError:
print("Already gone, or not in your tenant.")
except PermissionError:
print("This key can't purge — it lacks files:write.")Scope and what's not available
A partner API key can soft-delete, restore, and permanently purge its own tenant's files and trash, as documented above. Tenant-wide account erasure and per-application erasure are not available to API keys — those are operator-only surfaces and are out of scope for partner integrations.
Related pages
- Deletion endpoint reference — exact parameters and responses.
- Files reference — soft delete, restore, and listing.
- Quota and usage — confirm reclaimed storage.
- Raw links — share-link and edge-cache behavior.
- Organizing files — listing and trash filters.
- Errors · Rate limits