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.
The limits
Section titled “The limits”| 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.
Sandbox allowances
Section titled “Sandbox allowances”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 |
| 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.
The 429 response
Section titled “The 429 response”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 }}Stay under the limit
Section titled “Stay under the limit”- 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.