Skip to content

Python SDK

The fluidcloud package is the official Python client for the FluidCloud Partner API. It wraps every endpoint your API key is authorized to call, hides the multi-step upload flow behind a single files.upload(...), and maps HTTP errors to typed exceptions.

This page is the method-by-method reference. For end-to-end task walkthroughs see Uploading, Raw links, Organizing files, and Quota & usage. For the underlying HTTP shapes, see the API reference.

Install

bash
pip install fluidcloud

The client requires Python 3.8+ and pulls in httpx as its only runtime dependency.

Construct a client

python
from fluidcloud import FluidCloud

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

Create your key in the FluidCloud dashboard at cloud.fluidvip.com under Settings → Developer → API keys (an active subscription is required). Keys cannot be created via the API. See Authentication for details.

Constructor parameters

ParameterTypeDefaultDescription
api_keystr— (required)Your partner key (fck_live_... or fck_test_...). Sent automatically as the X-API-Key header.
base_urlstrhttps://api-cloud.fluidvip.comAPI origin. The default is production; you should not need to change it.
timeoutfloat300.0Per-request timeout in seconds. Uploads can be slow, so the default is generous.
python
fc = FluidCloud(
    api_key="fck_live_...",
    base_url="https://api-cloud.fluidvip.com",
    timeout=300.0,
)

All requests target the versioned API under /api/v1, so the effective endpoint base is https://api-cloud.fluidvip.com/api/v1. You pass the bare origin; the client appends the /api/v1 prefix for you.

Lifecycle

The client opens a pooled HTTP connection. Close it when you are done, or use it as a context manager so it closes automatically:

python
with FluidCloud(api_key="fck_live_...") as fc:
    spaces = fc.spaces.list()
# connection closed here

# or, explicitly:
fc = FluidCloud(api_key="fck_live_...")
try:
    ...
finally:
    fc.close()

Resources

A constructed client exposes five resource namespaces. The methods you have access to are listed below; each shows its signature, the HTTP endpoint it calls, and what it returns. The return types are light dataclasses (see Result objects).

fc.spaces

A Space is the top-level container for your files and folders. Requires spaces:read / spaces:write.

MethodEndpointReturns
fc.spaces.list()GET /spaceslist[Space]
fc.spaces.create(name)POST /spacesSpace
python
spaces = fc.spaces.list()
brand = fc.spaces.create("Brand Assets")
print(brand.id)  # UUID string

See Spaces reference.

fc.folders

Folders organize files within a Space. delete is a soft-delete (the folder goes to trash); restore brings it back. Requires folders:read / folders:write.

MethodEndpointReturns
fc.folders.list(space_id, parent_id=None, *, trash=False)GET /folderslist[Folder]
fc.folders.create(name, space_id, parent_id=None)POST /foldersFolder
fc.folders.rename(folder_id, name)PATCH /folders/{folder_id}Folder
fc.folders.move(folder_id, parent_id)PATCH /folders/{folder_id}Folder
fc.folders.delete(folder_id)DELETE /folders/{folder_id}Folder
fc.folders.restore(folder_id)POST /folders/{folder_id}/restoreFolder
python
inbox = fc.folders.create("inbox", space_id=brand.id)

# List the Space root (default). Pass parent_id to list inside a folder,
# or trash=True to list soft-deleted folders.
top = fc.folders.list(brand.id)
children = fc.folders.list(brand.id, parent_id=inbox.id)
trashed = fc.folders.list(brand.id, trash=True)

fc.folders.rename(inbox.id, "incoming")
fc.folders.move(inbox.id, parent_id=None)   # None = move to Space root
fc.folders.delete(inbox.id)                 # soft-delete to trash
fc.folders.restore(inbox.id)

Note: move sends parent_id explicitly, so passing None means "move to the Space root" — it is not the same as omitting the argument.

See Folders reference and Organizing files.

fc.files

The core resource: upload, list, fetch, organize, soft-delete, and mint links. Requires files:read / files:write (and shares:write for public_url / signed_url, which mint share links). Every uploaded file is automatically virus-scanned; a file must reach scan_status == "clean" before it can be shared or served, otherwise link minting returns 409 (quarantine-until-clean).

MethodEndpoint(s)Returns
fc.files.upload(file, space_id, *, folder_id=None, name=None, content_type=None, public=False, client_key=None)POST /uploads/initiate → presigned PUTPOST /uploads/completeFile
fc.files.list(space_id, folder_id=None, *, trash=False)GET /fileslist[File]
fc.files.get(file_id)GET /files/{file_id}File
fc.files.resolve(client_key)GET /files/resolveFile
fc.files.rename(file_id, name)PATCH /files/{file_id}File
fc.files.move(file_id, folder_id)PATCH /files/{file_id}File
fc.files.delete(file_id)DELETE /files/{file_id}File
fc.files.restore(file_id)POST /files/{file_id}/restoreFile
fc.files.download_url(file_id)GET /files/{file_id}/downloadstr
fc.files.public_url(file_id)POST /sharesLink
fc.files.signed_url(file_id, *, expires_in_days=7, permission="view")POST /sharesLink

upload

upload accepts a filesystem path, raw bytes, or an open binary file object, and hides the entire presign → direct-PUT → complete flow (including multipart for large files). Your bytes go directly to storage via a presigned URL — the API never proxies them.

python
# From a path — name and content type are inferred from the filename.
asset = fc.files.upload("logo.png", space_id=brand.id)

# From bytes — supply a name so the type can be guessed.
asset = fc.files.upload(b"...", space_id=brand.id, name="report.pdf")

# Into a folder, with an explicit public hotlink minted on the result.
asset = fc.files.upload(
    "out.png",
    space_id=brand.id,
    folder_id=inbox.id,
    public=True,
)
print(asset.public_url)  # permanent hotlink on https://files.fluidadmin.com/s/<token>

Parameters:

  • file — a path (str), bytes/bytearray, or an open binary file object.
  • space_id — the Space to store the file in (required).
  • folder_id — optional folder within the Space; defaults to the Space root.
  • name — override the stored filename; defaults to the source filename (or upload.bin for raw bytes).
  • content_type — override the MIME type; defaults to a guess from the name.
  • public — when True, also mint a permanent public hotlink and set it on result.public_url.
  • client_key — your own logical key for this file (e.g. results/job-7/out.png). Store it instead of FluidCloud's id, then fetch the file later with files.resolve(client_key) — no key→id map needed on your side. See resolve below.

See Uploading and the Uploads reference for the raw three-step flow if you ever bypass the SDK.

resolve

Fetch a file by the client_key you supplied at upload time, scoped to your tenant. Raises NotFoundError if there is no live match.

python
fc.files.upload("out.png", space_id=brand.id, client_key="results/job-7/out.png")
# ...later, in a different process, with no stored FluidCloud id:
f = fc.files.resolve("results/job-7/out.png")
print(f.id, f.scan_status)

Organizing and lifecycle

python
files = fc.files.list(brand.id)                       # Space root
files = fc.files.list(brand.id, folder_id=inbox.id)   # inside a folder
trashed = fc.files.list(brand.id, trash=True)         # soft-deleted

f = fc.files.get(asset.id)
fc.files.rename(asset.id, "logo-final.png")
fc.files.move(asset.id, folder_id=None)               # None = Space root
fc.files.delete(asset.id)                             # soft-delete to trash
fc.files.restore(asset.id)

delete is a soft-delete and restore undoes it. See Deleting data for retention behavior.

There are three ways to get a URL for a file:

python
# 1. Owner download — a short-lived presigned GET, for fetching the bytes yourself.
url = fc.files.download_url(asset.id)            # -> str

# 2. Permanent public hotlink — never expires until revoked; good for <img>/embeds.
link = fc.files.public_url(asset.id)            # -> Link
print(link.url)                                 # https://files.fluidadmin.com/s/<token>

# 3. Expiring share link — 1..365 days.
link = fc.files.signed_url(asset.id, expires_in_days=7)   # -> Link
print(link.url, link.expires_at)

public_url and signed_url both create a share link (POST /shares) and require shares:write. The file must be scanned clean first or you get a 409 ConflictError. See Raw links and the Shares reference.

Two known limitations of serve-time behavior (carried from the API, not the SDK): a share-link password is accepted and stored but is not enforced when the file is served; and a revoked permanent link can still serve from edge cache until its max-age of 3600 seconds lapses. See Security.

fc.shares

List and revoke the share links you have minted. Requires shares:read / shares:write. The link token itself is never returned by list — only metadata.

MethodEndpointReturns
fc.shares.list(*, file_id=None, include_inactive=False)GET /shareslist[Share]
fc.shares.revoke(share_id)DELETE /shares/{share_id}None
python
shares = fc.shares.list()                          # active links across your files
for_file = fc.shares.list(file_id=asset.id)        # links for one file
everything = fc.shares.list(include_inactive=True)  # include revoked/expired

fc.shares.revoke(shares[0].id)

See Shares reference.

fc.quota

Read your current storage and share-link usage against your plan limits. Requires quota:read.

MethodEndpointReturns
fc.quota.usage()GET /quotaQuota
python
q = fc.quota.usage()
print(q.bytes_used, "/", q.bytes_limit)
print(q.links_used, "/", q.links_limit)

A limit of -1 means unlimited. See Quota & usage and the Quota reference.

Result objects

Methods return small dataclasses that mirror the API response shapes. UUIDs and timestamps are strings (ISO-8601 UTC). Each object ignores unknown fields, so a server that adds a field will not break an older SDK.

Space

FieldTypeNotes
idstrUUID
namestr
created_at, updated_atstr | NoneISO-8601 UTC

Folder

FieldTypeNotes
idstrUUID
namestr
space_id, parent_idstr | Noneparent_id is None at the Space root
deleted_atstr | Noneset when soft-deleted
created_at, updated_atstr | None

File

FieldTypeNotes
idstrUUID
original_namestrthe stored filename
space_id, folder_idstr | None
mimestr | NoneMIME type
sizeint | Nonebytes
sha256str | Nonecontent hash
scan_statusstr | Noneclean once virus-scanned and safe to share/serve
statusstr | Nonelifecycle status
client_keystr | Noneyour own logical key, if you set one on upload
deleted_atstr | Noneset when soft-deleted
created_at, updated_atstr | None
public_urlstr | Noneset only when you upload with public=True

The create response from public_url / signed_url.

FieldTypeNotes
urlstrthe raw file URL (https://files.fluidadmin.com/s/<token>)
idstrthe share id (pass to shares.revoke)
permissionstrdefaults to "view"
expires_atstr | NoneNone for a permanent public link; an ISO-8601 string for an expiring one

Share

A listed link from shares.list — the token itself is never included.

FieldTypeNotes
idstrshare id
file_idstr
permissionstr
expires_atstr | None
max_downloadsint | None
download_countint
revokedbool
has_passwordboola password may be set but is not enforced at serve time
created_at, updated_atstr | None

Quota

FieldTypeNotes
bytes_usedintstorage consumed
bytes_limitintstorage cap; -1 = unlimited
links_usedintactive share links
links_limitintshare-link cap; -1 = unlimited

Errors

The client raises a typed exception for every non-success status. Import them from the package:

python
from fluidcloud import (
    ApiError,           # base class for all of them
    AuthError,          # 401 — missing/invalid/revoked key
    QuotaExceededError, # 402 — over storage or share-link cap
    PermissionError,    # 403 — insufficient_scope or feature_not_in_plan
    NotFoundError,      # 404 — not found, or not yours (cross-tenant ids are 404)
    ConflictError,      # 409 — file not ready (not yet scanned clean)
)
ExceptionHTTP statusWhen
AuthError401Missing, invalid, or revoked key
QuotaExceededError402detail == "quota_exceeded" — over storage or share-link cap
PermissionError403The key lacks a required scope, or your subscription does not include API access
NotFoundError404The resource does not exist, or belongs to another tenant
ConflictError409The file is not yet scanned clean and cannot be shared/served
ApiErrorany other (400, 413, 422, 429, …)Catch-all base; also raised directly for the above codes

Every exception carries the HTTP status and the parsed detail from the response body. Handle the specific ones you care about and let ApiError cover the rest:

python
from fluidcloud import QuotaExceededError, ConflictError, ApiError

try:
    link = fc.files.public_url(asset.id)
except ConflictError:
    print("File still being scanned — retry shortly.")
except QuotaExceededError:
    print("Share-link cap reached — revoke an old link or upgrade.")
except ApiError as e:
    print("Unexpected API error:", e)

429 rate-limit responses surface as ApiError and include a Retry-After header on the underlying response; back off and retry. See Errors and Rate limits.

Full worked example

Create a Space, upload an asset, mint a permanent hotlink, check usage, and clean up.

python
from fluidcloud import FluidCloud, ConflictError, QuotaExceededError

with FluidCloud(api_key="fck_live_...") as fc:
    # 1. Create a Space and a folder.
    space = fc.spaces.create("Job Outputs")
    folder = fc.folders.create("job-7", space_id=space.id)

    # 2. Upload — tag it with our own logical key so we can find it later.
    asset = fc.files.upload(
        "out.png",
        space_id=space.id,
        folder_id=folder.id,
        client_key="results/job-7/out.png",
    )
    print("stored as", asset.id, "scan:", asset.scan_status)

    # 3. Mint a permanent public hotlink for embedding.
    try:
        link = fc.files.public_url(asset.id)
        print("hotlink:", link.url)   # https://files.fluidadmin.com/s/<token>
    except ConflictError:
        print("Not scanned clean yet — try again in a moment.")

    # 4. Later, in another process, fetch by client_key — no stored id needed.
    same = fc.files.resolve("results/job-7/out.png")
    assert same.id == asset.id

    # 5. Check usage.
    q = fc.quota.usage()
    print(f"storage {q.bytes_used}/{q.bytes_limit}, links {q.links_used}/{q.links_limit}")

    # 6. Clean up (soft-delete; restorable from trash).
    fc.files.delete(asset.id)

Generating a client from the OpenAPI spec (optional)

If you prefer to generate your own typed client instead of using this SDK, the API publishes an OpenAPI specification. Any standard OpenAPI code generator can produce a client from it; remember to send your key in the X-API-Key header. For most integrations the fluidcloud package is the simplest path.

See also

FluidCloud API — part of the Fluidvip ecosystem.