Skip to content

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.

LimitValueWindow
Requests per API key600 / minute60-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).

ActionEndpointCap
Create a share linkPOST /shares120 / minute
Start an upload (mint a presigned PUT)POST /uploads300 / minute
Start a URL import (server-side fetch)POST /uploads/import-url30 / minute

A request can be rejected by either the global cap or the relevant per-action cap — whichever you hit first. A 429 response is the same in both cases: an HTTP 429 with a Retry-After header.

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
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 a 429 from 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) with 402 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). A 429 is transient; a 402 is 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:

bash
# 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))
done

Python (fluidcloud)

The ApiError raised for a 429 carries the parsed Retry-After value:

python
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)

ts
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 429 means 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-After is 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/min is 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

FluidCloud API — part of the Fluidvip ecosystem.