Raw links & share links
A raw link is a public, cookieless URL that streams a file's bytes directly — no authentication, no cookies, no FluidCloud session. The bytes are served from a dedicated file domain, https://files.fluidadmin.com, and a link always looks like:
https://files.fluidadmin.com/s/<token>This is the right primitive whenever an external service needs to hand a plain URL to something that just fetches bytes: a chat bot, an <img> / <video> tag in a web page, a link unfurler, or a vision model that takes an image URL. The consumer does a normal GET and gets the file back — that's it.
Raw links are minted from files you've already uploaded. The file must have finished its automatic virus scan and be clean before you can share it (see Quarantine until clean below).
Required scopes: minting and revoking links needs
shares:write; listing needsshares:read. Both are included in a partner API key.
Two kinds of link
There are exactly two flavours, chosen by the expires_in_days field when you mint:
| Permanent public hotlink | Expiring signed link | |
|---|---|---|
expires_in_days | null | an integer 1–365 |
| Lifetime | Never expires (until you revoke it) | Expires after N days |
| Default disposition | Inline — serves as an <img>/<video> src | Download (attachment) unless permission: "view" |
| Edge cache | Yes — Cache-Control: public, max-age=3600 | Not edge-cached |
| Counts against your share-link cap | No (metered as storage, not as a link) | Yes (one slot per link) |
| Best for | Stable public assets, embeds, CDN-style hotlinks | Time-boxed access, expiring download URLs |
Pick the permanent hotlink when you want a durable URL to drop into an <img> tag or pass to a vision model — it's inline, cached, and doesn't burn a slot from your share-link allowance. Pick an expiring signed link when access should lapse on its own. Your share-link cap and current usage are visible via the quota endpoint — see Quota & usage (a limit of -1 means unlimited).
The permission field controls how the browser handles the response when a human opens the link directly:
"view"— served inline (opens in the browser)."download"— served as an attachment (forces a save), using the file's original filename.
A permanent link defaults to inline; an expiring link defaults to download. Setting permission explicitly overrides that default either way.
Mint a link
POST /api/v1/shares
Request body:
| Field | Type | Notes |
|---|---|---|
file_id | UUID string | The file to share. Must be one of your files and scanned clean. |
expires_in_days | integer 1–365, or null | null = permanent public hotlink. Default 7. |
permission | "view" or "download" | Inline vs attachment. Default "view". |
max_downloads | integer ≥ 1, optional | Best-effort serve cap (metered after the fact). |
The response carries the link URL once — the token in the path is never stored in plaintext on the server and is not retrievable later, so capture it now.
{
"url": "https://files.fluidadmin.com/s/eyJrIjoi...Q.xQ2f...",
"id": "7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e",
"expires_at": "2026-06-30T12:00:00Z",
"permission": "view"
}For a permanent link, expires_at is null.
Permanent public hotlink
curl
curl -X POST https://api-cloud.fluidvip.com/api/v1/shares \
-H "X-API-Key: fck_live_..." \
-H "Content-Type: application/json" \
-d '{
"file_id": "7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e",
"expires_in_days": null
}'Python (pip install fluidcloud)
from fluidcloud import FluidCloud
fc = FluidCloud(api_key="fck_live_...")
link = fc.files.public_url("7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e")
print(link.url) # https://files.fluidadmin.com/s/... (permanent, inline, cached)public_url(...) is sugar for POST /shares with expires_in_days set to null. If you upload with public=True, the same hotlink is minted for you and surfaced as result.public_url — see Uploading files.
TypeScript (npm install fluidcloud)
import { FluidCloud } from "fluidcloud";
const fc = new FluidCloud({ apiKey: "fck_live_..." });
const link = await fc.files.publicUrl("7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e");
console.log(link.url);Expiring signed link
curl
curl -X POST https://api-cloud.fluidvip.com/api/v1/shares \
-H "X-API-Key: fck_live_..." \
-H "Content-Type: application/json" \
-d '{
"file_id": "7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e",
"expires_in_days": 7,
"permission": "download"
}'Python
link = fc.files.signed_url(
"7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e",
expires_in_days=7,
permission="view",
)
print(link.url)TypeScript
const link = await fc.files.signedUrl("7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e", {
expiresInDays: 7,
permission: "view",
});
console.log(link.url);The API never proxies your bytes through the share path: the link points straight at storage on the file domain. Minting a link only creates the signed URL — it does not move data.
Consume the URL
A consumer fetches a raw link with a plain GET and no credentials. The response carries the correct Content-Type, an ETag, and Content-Disposition (inline or attachment per the link's permission). CORS is open for cross-origin reads (Access-Control-Allow-Origin: *, GET/HEAD only), so it works as an embed on any page.
# Full file (200)
curl -L https://files.fluidadmin.com/s/<token> -o out.bin<!-- Permanent public hotlink used directly as an image source -->
<img src="https://files.fluidadmin.com/s/<token>" alt="asset" />Range requests
The file domain supports HTTP Range, so media players and any consumer that seeks get partial content. Send a Range header and you get a 206 Partial Content with Content-Range; a plain GET (no Range) always returns a clean 200 for the whole object.
# First 1024 bytes -> 206 Partial Content
curl -H "Range: bytes=0-1023" https://files.fluidadmin.com/s/<token> -iA range that happens to cover the whole object is treated as a normal 200, so plain hotlinks and unfurlers never see a surprising 206. Conditional requests (If-None-Match / If-Modified-Since) are honoured and return 304 Not Modified when the object is unchanged.
What the consumer can see
200— full object.206— partial content (a realRangewas requested).304— not modified (conditional request).401— the token is invalid (tampered or wrong signature).403— the link has been revoked.410— an expiring link is past its expiry.404— the object is gone.
These are the edge statuses returned by the file domain to the consumer. They are distinct from the API error model you get back from api-cloud.fluidvip.com when minting/listing/revoking.
List your links
GET /api/v1/shares returns your active links (not revoked, not expired). Pass file_id to filter to one file, and include_inactive=true to also see revoked/expired links.
Each entry includes the share id, the file_id, permission, expires_at (null for permanent), max_downloads, download_count, revoked, has_password, and timestamps. The token is not returned — it only ever appears in the create response.
curl
curl "https://api-cloud.fluidvip.com/api/v1/shares?file_id=7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e" \
-H "X-API-Key: fck_live_..."Python
for s in fc.shares.list(file_id="7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e"):
print(s.id, s.permission, s.expires_at, s.revoked)TypeScript
const shares = await fc.shares.list({
fileId: "7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e",
});
for (const s of shares) console.log(s.id, s.permission, s.expiresAt, s.revoked);Revoke a link
DELETE /api/v1/shares/{share_id} revokes a link. It's idempotent (revoking twice is fine and returns 204), and revoking an expiring link frees the slot back to your share-link cap. Revoking a link you don't own returns 404.
curl
curl -X DELETE https://api-cloud.fluidvip.com/api/v1/shares/7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e \
-H "X-API-Key: fck_live_..."Python
fc.shares.revoke("7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e")TypeScript
await fc.shares.revoke("7c1f2a90-3b4e-4f8a-9c2d-1e0a5b6c7d8e");Quarantine until clean
Every upload is automatically virus-scanned. A file can only be shared or served once it has been scanned clean (scan_status == "clean"). If you try to mint a link for a file that hasn't cleared scanning yet, you get:
409 Conflict
{ "detail": "File is not shareable yet (scan_status=...); must be 'clean'." }This is the quarantine-until-clean rule. If you just uploaded and immediately tried to share, wait briefly and retry, or check the file's scan_status first. In the SDKs this surfaces as a ConflictError.
Known limitations — read before relying on these
Two behaviours do not work the way the surface area might suggest. Do not depend on them for access control:
- Password protection is not enforced. The create body accepts a
passwordand the field is stored, but the file domain serves on a valid token alone and does not prompt for or check a password at serve time. Treat any link you mint as effectively public to whoever holds the URL. Do not use the password field as a security control. - Revocation of a permanent link can lag the edge cache. Permanent public hotlinks are cached at the edge with
max-age=3600(one hour). After you revoke a permanent link, a cached copy can keep being served until that cache entry lapses — up to 3600 seconds. Expiring links are not edge-cached, so their revocation takes effect immediately. If you need a hard, instant cutoff, prefer an expiring link (or delete the underlying file — see Deleting data).
Reference
Full field-by-field schemas for the share endpoints live in reference/shares.md. Related guides: Uploading files, Quota & usage, Organizing files, and the API error model and rate limits (minting links is capped at 120/min per key).