Fibric. Docs fibric.io →
v0.9 · preview
Reference

Rate limits & quotas

The API enforces two distinct budgets. Rate limits protect the platform from bursty clients and reset every minute. Quotas are standing allowances tied to your plan, chiefly the monthly action allowance. Both surface the same way: response headers you can watch, and a 429 with Retry-After when a budget is exhausted.

i
Early access

During early access, limits are deliberately generous and are not enforced against normal integration work. Final limits for your workload are agreed during onboarding, and anything you negotiate there supersedes the defaults on this page. If a default below is tight for your use case, say so; raising it is configuration, not engineering.

Rate limit headers

Every response includes the state of the rate window the request was counted against:

HeaderMeaning
X-RateLimit-LimitThe size of the window for this route class, in requests per minute.
X-RateLimit-RemainingRequests left in the current window, after this one.
X-RateLimit-ResetUnix timestamp (seconds) at which the window resets.
Retry-AfterSent only with 429 and lock-related 409 responses: seconds to wait before retrying.
http · rate headers on a normal response
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 994
X-RateLimit-Reset: 1782172860

How limits are scoped

All rate limits and quotas apply per tenant, not per API key. Splitting traffic across keys does not raise a budget; the keys share the tenant's windows. This is deliberate: the limits protect the executor and the source systems behind your connectors, and those do not care how many keys the requests arrived on.

To see which bucket a request was counted against, read the headers on the response itself; the limit value identifies the class.

bash · inspect the budget without spending a write
curl -sI "https://api.fibric.io/v1/events?limit=1" \
  -H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9" | grep -i "x-ratelimit"
# X-RateLimit-Limit: 1000
# X-RateLimit-Remaining: 997
# X-RateLimit-Reset: 1782172860

Default limits by endpoint group

Limits apply per tenant, per route class, on a one-minute sliding window. Reads and writes are budgeted separately, so a listing loop can never starve your ingest and vice versa.

Endpoint groupReads / minWrites / minNotes
Events1,000600Ingest (POST /v1/events) has its own 600/min budget, separate from all other writes. Batch upstream if you sustain more.
Operators1,000120Create, update, pause, resume share the write budget.
Connectors1,000120POST /v1/connectors/:id/test counts as a write; it calls the source system.
Actions & plans1,000120Approve, veto, and undo share the write budget. Executed actions are metered by the action allowance, not the rate limit.
Receipts1,00060Writes here are export-job creation only, further capped by the concurrent export limit.

All read routes across the API share one additional global ceiling of 2,000 reads per minute per tenant, so five saturated groups cannot compound into an unbounded aggregate.

Burst behavior

Windows are enforced with a token bucket, not a hard per-second gate. Each bucket refills continuously at the per-minute rate and holds a burst reserve of 2× the per-minute limit. In practice:

Handling 429

A rate-limited response carries the standard error envelope with code rate_limited and a Retry-After header:

json · 429 rate_limited
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782172872

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Event ingest is limited to 600 requests per minute for this tenant.",
    "doc_url": "https://fibric.io/docs/limits#handling-429",
    "request_id": "req_c418e97d"
  }
}

Handling rules, in order of importance:

The official SDKs implement all four behaviors by default; hand-rolled clients should reproduce them. The reference loop, in shell form:

bash · retry loop honoring Retry-After with capped backoff
attempt=0
while true; do
  status=$(curl -s -o resp.json -w "%{http_code}" -D headers.txt \
    -X POST https://api.fibric.io/v1/events \
    -H "Authorization: Bearer $FIBRIC_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: magento:SO-10884:v7" \
    -d @envelope.json)
  [ "$status" != "429" ] && break
  wait=$(grep -i "^retry-after:" headers.txt | tr -dc "0-9")
  attempt=$((attempt + 1))
  backoff=$((wait * attempt > 60 ? 60 : wait * attempt))
  sleep $((backoff + RANDOM % 3))   # jitter
done

The same Idempotency-Key rides every attempt, so even a retry that races a proxy replay cannot ingest the event twice.

Action allowance quotas

Executed actions, side effects the executor actually applies with an ALLOW or approved ALERT disposition, are metered against your plan's monthly allowance. This is a quota, not a rate limit: it does not reset per minute and it is what your bill is computed from.

PlanIncluded actions / monthOverage
Early accessUncapped during the programFree during early access
Team, $240/moIncluded allowance per your plan$0.01 per additional action

What counts, and what does not:

Overage is on by default so operators never stall mid-incident; you can set a hard cap in the console instead, in which case actions beyond the cap return 429 with code quota_exceeded and the plans remain in proposed for approval after the cap is raised. Premium connectors (from $29/source/mo) and operator packs (from $49/operator/mo) are licensed separately and have no per-action component.

Tracking usage

The allowance is metered from the receipt ledger, which means you can reconcile it yourself: every billable action is a receipt with outcome applied, and nothing else bills. To count the current month's billable actions:

bash · count billable actions this month from receipts
curl -s "https://api.fibric.io/v1/receipts?outcome=applied&since=2026-07-01T00:00:00Z&limit=100" \
  -H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9" | jq '.data | length'

Walk next_cursor for the full count, or use a receipt export for a month-end statement. The number you compute this way is the number you are billed for; there is no separate metering system to trust.

Other quotas

QuotaDefaultWhen exceeded
Concurrent receipt export jobs2 per tenant429 quota_exceeded; wait for a running job to reach complete or failed.
Event payload size256 KB400 invalid_request; put large artifacts in your own store and reference them from the payload.
Active operators25 per tenant429 quota_exceeded on create or resume; raised on request during onboarding.
Installed connectors25 per tenant429 quota_exceeded on install; raised on request during onboarding.
API keys20 per tenant429 quota_exceeded on key creation; revoke unused keys or ask for more.

Upstream source-system limits

Fibric's limits are not the only limits in the loop. Every connector acts against a source system with rate limits of its own: a commerce API, a ticketing API, a building-management gateway. The executor paces outbound tool calls to stay inside each source system's published limits, and this pacing is separate from, and often tighter than, anything on this page.

The practical consequence: sizing your ingest rate against the tables above is necessary but not sufficient. If an operator can propose actions faster than the source system will accept them, the executor's single-flight queue absorbs the difference, and the receipt timestamps show the true applied rate.

How limits change

Limit changes follow the same discipline as the API surface. Raises take effect immediately and are not announced per tenant. Reductions to a default never apply retroactively to tenants already onboarded: your effective limits are the ones agreed at onboarding, and any reduction reaches you only with at least 30 days of notice and a migration path. The changelog records default changes; the headers on every response record your live values, which always win over this page.

Design for the quota, not around it

The cheapest request is the one you do not send. Forward raw source events and let operators reason, rather than pre-chunking one change into many envelopes; use since filters instead of full re-lists; and let idempotency keys, not client-side bookkeeping, dedupe your retries.