Concepts
Rate limits
Each key has its own budget per minute. Over it, the API answers 429 and tells you how long to wait.
Limits per key
| Credential | Limit |
|---|---|
sf_test_ API key | 60 requests a minute |
sf_live_ API key | 300 requests a minute |
| MCP token | 60 tool calls a minute; a write tool counts as two |
Limits are per key, not per business: two integrations with their own keys don't eat each other's budget.
When you hit the limit
The API answers 429 with error.code rate_limited and a Retry-After header in seconds. Wait that long, then retry.
HTTP/1.1 429 Too Many Requests
Retry-After: 23
Content-Type: application/json
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded (300/min).", "request_id": "…" } }async function call(url, init = {}, tries = 5) {
for (let i = 0; i < tries; i++) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
const wait = Number(res.headers.get("Retry-After") ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
}
throw new Error("still rate limited");
}import time, requests
def call(method, url, tries=5, **kw):
for _ in range(tries):
res = requests.request(method, url, timeout=10, **kw)
if res.status_code != 429:
return res
time.sleep(int(res.headers.get("Retry-After", "1")))
raise RuntimeError("still rate limited")What counts
- A request counts once it passes the key, scope, outlet and plan checks, whatever it answers next (a
404or409included). - Requests refused before that (a bad key, a missing scope, a plan without the API, or a query or body that does not parse) do not count.
- The menu supports
ETag/If-None-Match. A304still counts as a request but carries no body, so polling it is cheap.
To stay well under the limit, page with limit=100 and use webhooks instead of polling for changes.
Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .