Skip to content

Rate limits

Fabric rate-limits requests with a token bucket, enforced on two levels at once so that minting extra keys cannot dodge the account-wide ceiling.

Scope Default Error code
Per API key 120 requests / minute rate_limited
Per account (all keys) 600 requests / minute tenant_rate_limited

Each bucket has a full-minute burst capacity and refills continuously (the per-minute limit divided across the minute). Both buckets are checked on every request; whichever is exhausted first returns the limit.

A sandbox workspace does not spend from a wallet — sandbox messages are free, and capacity is a daily allowance per channel instead.

Channel Default Unit Error code
SMS 100 / UTC day segment sandbox_daily_limit_exceeded
Email 200 / UTC day message sandbox_daily_limit_exceeded

SMS is counted per segment, not per message, so one 3-segment message draws 3 from the allowance. The allowance is per workspace and shared across every application and environment in it.

This arrives as a 429, but it is not a rate limit — no amount of backing off will clear it, because it refills at midnight UTC rather than continuously. The SDK knows the difference and does not retry this code, so it surfaces immediately instead of consuming your retry budget:

try {
await fabric.sms.send(params, { idempotencyKey });
} catch (err) {
if (err instanceof RateLimitError && err.code === "sandbox_daily_limit_exceeded") {
// Out of sandbox capacity until midnight UTC. Backing off will not help.
}
}

Going live replaces the allowance with your wallet balance — see Wallet and billing.

Over the limit, Fabric returns HTTP 429 with the error envelope:

{ "error": { "type": "rate_limit_error", "code": "rate_limited", "message": "…" } }

The SDK raises RateLimitError for a 429. It also retries a 429 automatically (up to maxRetries) with exponential backoff, honouring Retry-After when present.

import { RateLimitError } from "@fabric-messaging/sdk";
try {
await fabric.sms.send(params, { idempotencyKey });
} catch (err) {
if (err instanceof RateLimitError) {
// err.retryAfter may hold a seconds hint
}
}
  • Pace bulk work. Use batches (1–100 items in one request) instead of many single sends, and space batches out.
  • Back off with jitter. On a 429, wait and retry with randomised backoff — the SDK does this for eligible requests, but apply the same discipline in your own orchestration.
  • Attribute usage per service. Give each deployment its own key so one noisy service does not starve another — though remember the account-wide bucket still applies.