Rate limits and plan limits

Request rate limits and their headers, retrying 429 and 503, plan limits and modules, daily sends, and reading usage.

Every tenant runs under a plan that sets its request rate, its count limits and the modules it includes. This page covers what each limit counts, what answers when you reach it, and how to read where you stand with GET /v1/usage.

Request rate

Requests count against two fixed one-minute windows from the plan:

  • one for the whole tenant (apiCallsPerMinute), every credential together;
  • one per credential (apiCallsPerMinutePerKey): each API key, account token, OAuth grant or console user on its own, so one busy key cannot starve the others.

A request counts on both windows or on neither. Over either, it is refused with 429 rate_limited and a Retry-After header in seconds.

A counted request answers the state of whichever window has fewer requests left:

HeaderMeaning
RateLimit-LimitRequests the window allows.
RateLimit-RemainingRequests the window still admits.
RateLimit-ResetSeconds until the window ends.
Retry-AfterOn a 429 or 503: seconds to wait before trying again.

The RateLimit-* headers are absent:

  • on plans without request limits;
  • on the 429 refusing a key whose own window is already spent before the request is read (it carries only Retry-After);
  • while the rate limiter's store is unreachable, when requests are let through uncounted.

Retry 429 and 503

AnswerCauseRetry
429 rate_limitedThe tenant's or the credential's window is spent.After Retry-After, when the window ends.
429 too_many_requestsThe credential has too many requests in flight at once.After Retry-After.
429 provider_throttledThe mail or calendar provider asked Workwae to slow down.After Retry-After when the provider named a wait.
503 overloadedThe API is shedding load.After Retry-After.

rate_limited, too_many_requests and overloaded are answered before the request does anything, so retrying is safe.

The SDK retries for you

The TypeScript SDK retries 429 and 503 answers twice by default. It waits for Retry-After, or backs off exponentially with jitter when there is none, and throws instead when Retry-After exceeds maxDelayMs (60 seconds). Tune it with createClient({ retry: { maxRetries, maxDelayMs } }).

Concurrency and abuse limits

Beyond the plan, each API process limits how much of it one caller takes:

  • Past 16 requests in flight for one credential, a request answers 429 too_many_requests. A download counts until its body is sent.
  • When the process is overloaded, a request answers 503 overloaded.
  • A client address that fails authentication too often in a minute has its bearer tokens refused with rate_limited until the minute ends, except API keys that authenticated recently.
  • POST /v1/accounts/{accountId}/sync allows 30 requests per account per minute; past it, 429 rate_limited.

Downloads are sent chunked, without Content-Length. Sizes are in the message's size, each attachment's size and an export's bytes.

Plan limits

Count limits are in plan.limits; null means unlimited. They are abuse ceilings: usage between what the base fee includes (plan.limits.included) and the limit is billed as overage.

LimitCountsPast it
maxAccountsAccounts not deleted, whatever their status.Connecting another account answers 403 plan_limit_accounts. Connected accounts keep syncing.
sendsPerDaySends queued per UTC day.403 plan_limit_sends. See Daily sends.
webhookEndpointsWebhook endpoints.Adding another answers 403 plan_limit_webhooks. Existing endpoints keep delivering.
storageBytesStored message content, in bytes.Storing more answers 403 plan_limit_storage.
bodyStorageAllowedWhether body storage is available at all.Setting body storage to cache or full answers 403 plan_limit_storage.
apiCallsPerMinute, apiCallsPerMinutePerKeyRequests per minute.429 rate_limited. See Request rate.

A hosted or headless connect flow stopped by the account limit returns to your redirectUri with status=error&error=plan_limit_accounts.

A tenant without an assigned plan runs under the platform defaults (plan.id is null). A canceled subscription falls back to them too.

Daily sends

sendsPerDay counts messages as they are queued, per UTC day:

  • Past it, a send answers 403 plan_limit_sends until 00:00 UTC.
  • In a mail merge, the entries beyond what the day has left fail with plan_limit_sends; the others are queued.
  • current.sendsToday in usage is the count the limit checks.

The MCP send policy can set its own dailySendLimit per credential, up to 10,000, on top of the plan's.

Past-due payment

When a tenant's payment is past due beyond the grace period, new sends and new accounts answer 402 billing_past_due until an invoice is paid. Sends already queued or scheduled still go out, and sync and reads continue.

Modules

A plan names its modules in plan.limits.modules: calendar, contacts, tracking, ai, meetings and sharedMailboxes. Mail is always included.

Without a module:

  • Its routes answer 403 plan_module_unavailable: Calendar, Contacts, meeting apps and connections, shared mailboxes, AI drafts, and tracking asked for on a send.
  • Its settings have no effect: no calendar or contacts sync, no calendar or contacts scopes on connect, no tracking defaults on sends, no reply classification.
  • Shared-mailbox accounts added before the plan lost sharedMailboxes keep syncing and sending like any other account. Only adding new ones, and asking for the shared mailbox scope on connect, is refused.

Read usage

GET /v1/usage (read scope) returns the effective plan and the tenant's usage against it.

curl "https://api.workwae.com/v1/usage?from=2026-10-01&to=2026-10-18" \
  -H "Authorization: Bearer $WORKWAE_API_KEY"

from and to are UTC days, both included:

  • Leave both out for the last 30 days, ending today. from alone runs to today; to alone covers the 30 days ending on it.
  • The range spans at most 366 days.
  • A range that starts after it ends, or after today when to is omitted, answers 400 invalid_range.
FieldMeaning
planThe effective plan: id (null for the platform defaults), name and limits. limits.included is what the base fee covers; limits.pricing is null when the plan is not billed.
currentWhere the tenant stands now: accounts (not deleted, what maxAccounts counts), webhookEndpoints, sendsToday (queued today, what sendsPerDay counts) and storedBytes (the last storage listing plus content stored since).
daysOne entry per UTC day that had usage, oldest first. Days without usage are absent. Today's counters are live.

Each day counts:

FieldCounts
liveAccountsHighest number of active accounts sampled that day.
apiCallsCalls per scope (read, write, send, admin).
sendsMessages delivered. sendsPerDay counts messages as they are queued, which can be earlier.
webhookDeliveriesSuccessful webhook deliveries.
aiCallsAnswers from the AI gateway: reply classifications and drafts.
storedBytesObject storage from that day's listing; null until the listing has run.

On this page