Rate limits
The FluidCloud API enforces per-key rate limits so a single integration cannot exhaust shared capacity. Limits are applied per API key. When you exceed a limit, the API returns 429 Too Many Requests with a Retry-After header telling you how many seconds to wait before retrying.
This page lists every limit that applies to a partner key and shows how to read Retry-After and back off correctly.
The limits
There are two kinds of limit, and they apply at the same time.
1. Global per-key request cap
Every request you make — across all endpoints — counts toward a single per-key budget.
| Limit | Value | Window |
|---|---|---|
| Requests per API key | 600 / minute | 60-second fixed window |
This bounds your overall call volume regardless of which endpoints you hit.
2. Per-action caps
A handful of cost-prone "start an operation" endpoints have their own, tighter cap. These are counted independently of the global cap and of each other, so hitting the share-create cap never affects your upload budget (or vice versa).
| Action | Endpoint | Cap |
|---|---|---|
| Create a share link | POST /shares | 120 / minute |
| Start an upload (mint a presigned PUT) | POST /uploads | 300 / minute |
| Start a URL import (server-side fetch) | POST /uploads/import-url | 30 / minute |
A request can be rejected by either the global cap or the relevant per-action cap — whichever you hit first. A
429response is the same in both cases: an HTTP429with aRetry-Afterheader.
All windows are fixed 60-second windows, so Retry-After never exceeds 60 and is the number of seconds until the current window rolls over.
What a 429 looks like
HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json
{
"detail": "Too many requests — please slow down and try again shortly."
}Retry-After— seconds to wait before the request will be accepted again. Always present on a429from a rate limit. Read this header; do not guess.detail— a human-readable string. The machine signal is the status code (429) plus the header.
See errors.md for the full error model and the complete list of status codes.
How the SDKs surface a 429
Both SDKs raise a typed ApiError for a 429 (it is not one of the specialized subtypes like QuotaExceededError). The retry delay from Retry-After is exposed on the error so you can back off without parsing headers yourself.
Don't confuse
429(rate limited — you're calling too fast, retry shortly) with402 quota_exceeded(you're over your storage or share-link plan limit — retrying won't help; see quota.md and guides/quota-and-usage.md). A429is transient; a402is not.
Backing off correctly
The right behavior is: on a 429, sleep for Retry-After seconds, then retry. Add a small jitter and cap the number of retries so a sustained limit doesn't loop forever. Because windows are fixed 60-second windows, a single wait of Retry-After is usually enough to clear.
curl
curl won't honor Retry-After for you, but you can read it and loop in a shell wrapper. A simpler approach for scripts is --retry with --retry-delay, though it ignores the header value:
# Honor Retry-After manually
attempt=0
until [ "$attempt" -ge 5 ]; do
resp=$(curl -s -o /tmp/body -w '%{http_code}' -D /tmp/headers \
-X POST https://api-cloud.fluidvip.com/api/v1/shares \
-H "X-API-Key: $FLUIDCLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"file_id":"3f0c…","kind":"permanent"}')
if [ "$resp" != "429" ]; then
cat /tmp/body
break
fi
wait=$(grep -i '^retry-after:' /tmp/headers | tr -d '\r' | awk '{print $2}')
echo "rate limited, sleeping ${wait:-5}s" >&2
sleep "${wait:-5}"
attempt=$((attempt + 1))
donePython (fluidcloud)
The ApiError raised for a 429 carries the parsed Retry-After value:
import time
from fluidcloud import FluidCloud
from fluidcloud.errors import ApiError
fc = FluidCloud(api_key="fck_live_…")
def with_backoff(call, *, max_attempts=5):
"""Run an API call, honoring Retry-After on 429."""
for attempt in range(max_attempts):
try:
return call()
except ApiError as exc:
if exc.status_code != 429 or attempt == max_attempts - 1:
raise
# retry_after is the Retry-After header in seconds; fall back to a small default
delay = getattr(exc, "retry_after", None) or 5
time.sleep(delay)
raise RuntimeError("exhausted retries")
share = with_backoff(
lambda: fc.shares.create(file_id="3f0c…", kind="permanent")
)
print(share.url)TypeScript (fluidcloud)
import { FluidCloud, ApiError } from "fluidcloud";
const fc = new FluidCloud({ apiKey: "fck_live_…" });
const sleep = (s: number) => new Promise((r) => setTimeout(r, s * 1000));
async function withBackoff<T>(call: () => Promise<T>, maxAttempts = 5): Promise<T> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await call();
} catch (err) {
if (!(err instanceof ApiError) || err.statusCode !== 429 || attempt === maxAttempts - 1) {
throw err;
}
// retryAfter is the Retry-After header in seconds
const delay = err.retryAfter ?? 5;
await sleep(delay);
}
}
throw new Error("exhausted retries");
}
const share = await withBackoff(() =>
fc.shares.create({ fileId: "3f0c…", kind: "permanent" }),
);
console.log(share.url);Staying under the limits
- Reuse results instead of re-fetching. If you stored a file id (or a
client_key), resolve it instead of re-listing a folder on every request. - Batch your own work, not the API. There is no bulk endpoint; pace your calls so a burst of uploads stays under 300 starts/minute and share creation stays under 120/minute.
- Mind URL imports especially. Server-side URL imports are capped at 30/minute because each one does a server-side fetch. If you have many URLs, enqueue them on your side and feed them in steadily. See guides/uploading.md.
- Always honor
Retry-After. A fixed wait of the header value clears the window. Retrying immediately just burns another rejected request. - Cap your retries. A persistent
429means your steady-state call rate is too high — slow the caller down rather than looping forever.
Notes and limitations
- Windows are fixed 60-second windows (not a rolling/sliding window). At a window boundary your full budget resets at once, so
Retry-Afteris at most 60 seconds. - Limits are tracked per API key. Issuing a separate key for a separate workload gives each its own independent budget. Keys are created in the FluidCloud dashboard under Settings → Developer → API keys, not via the API — see authentication.md.
- Rate limiting and plan quotas are different systems. Hitting
600/minis a speed limit (429, retry shortly); hitting your storage or share-link plan limit is a capacity limit (402 quota_exceeded). Read your current usage and limits from the quota endpoint — see reference/quota.md.
See also
- errors.md — full status-code and
detailreference - reference/shares.md — creating share links (120/min)
- reference/uploads.md — starting uploads and URL imports (300/min, 30/min)
- reference/quota.md — storage and share-link plan limits
- sdks/python.md · sdks/typescript.md — typed errors and SDK setup