Rate limiting

Wingspan V3 rate limits, the four read and write buckets, the RateLimit headers on each response, and how to back off after a 429.

This page explains how Wingspan V3 limits request rates, how to read your remaining capacity from response headers, and what to do when you hit a limit.

The four buckets

Each request uses up points from buckets that refill over a sliding one-second window:

BucketDefault limit
account-read500 points per second
account-write250 points per second
organization-read2,000 points per second
organization-write1,000 points per second

Every request is charged to the Account it acts on. When an Organization limit applies, the request is also charged to the Organization's bucket. Both must have room, so the tighter of the two applies.

These are defaults. If your integration needs more, contact your Wingspan account team. The RateLimit-Policy header always shows the limits in force for you.

Reads and writes

The HTTP method decides the bucket:

MethodBucket
GET, HEADRead
POST, PATCH, PUT, DELETEWrite
OPTIONSNot counted

Action endpoints such as POST /v3/payments/payables/{payableId}/open are writes, even when they only change a status.

Which Account is charged

The bucket belongs to the Account the request acts on. When you send X-Wingspan-Account, the target Account is charged (and its Organization, when an Organization limit applies), and your own Account isn't. See Acting on behalf of Accounts. Requests to Person-only endpoints, such as your own MFA factors, are counted separately for the Person. The headers still name these buckets account-read and account-write. Endpoints that work for either an Account or a Person, called by a Person without X-Wingspan-Account, are charged to that Person's Principal Account.

What a request costs

Most operations cost 1 point. Some cost more because they do more work, such as creating a session, submitting a tax form, or bulk-dismissing notifications (up to 10 points). The cost is fixed per operation and never depends on the request body, so you can plan around it. When a request is rejected, extensions.costPoints in the 429 response tells you what that operation costs.

A request that fails validation still uses its points. A request rejected with 429 doesn't.

Response headers

Responses carry two headers that describe your limits. Requests rejected before they're counted, for example with 401, 403, 404, or a validation error, may not include them.

RateLimit-Policy lists the buckets that apply to you, with their quota (q) and window in seconds (w):

RateLimit-Policy: "account-read";q=500;w=1, "account-write";q=250;w=1

The Organization buckets (organization-read, organization-write) appear only when an Organization limit applies to the request.

RateLimit shows the points remaining (r) and the seconds until the window resets (t) for the buckets this request used. A POST shows the write buckets:

RateLimit: "account-write";r=248;t=1

To pace yourself, watch the smaller r value and slow down as it approaches zero.

If a response includes Wingspan-RateLimit-Mode: degraded, Wingspan couldn't count the request and served it anyway. It won't include a RateLimit header. Keep your usual pacing.

When you hit the limit

A rejected request returns 429 with a Retry-After header in seconds (always at least 1):

HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit: "account-write";r=0;t=1
Content-Type: application/problem+json
{
  "type": "https://api.wingspan.app/errors/rate-limit-exceeded",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "Account write rate limit exceeded.",
  "code": "RateLimitExceeded",
  "requestId": "3f2c9a7e1b4d4e0f9a8c6b5d2e1f0a93",
  "extensions": {
    "bucket": "account-write",
    "limit": 250,
    "windowSeconds": 1,
    "costPoints": 1
  }
}

extensions.bucket tells you which bucket ran out. It's usually one of the four buckets above, but it can name other buckets, so handle values you don't recognize the same way. Nothing happened on Wingspan's side: no record was created or changed, and your Idempotency-Key wasn't used, so you can retry with the same key.

To recover:

  1. Wait for the number of seconds in Retry-After.
  2. Retry the request with the same Idempotency-Key.
  3. If you keep getting 429, reduce your concurrency, and add random jitter to your retry delays so parallel workers don't retry in lockstep.

A 429 always means your request rate is too high. When Wingspan itself has a problem, you'll get 503 instead. See Errors.

Stay under the limit

  • Queue your work. Put outgoing API calls on a queue and process it at a controlled rate, rather than sending everything at once.
  • Limit concurrency. Cap the number of requests your workers have in flight for each Account and for your Organization.
  • Use bulk endpoints. Import many payees or payables through batches instead of thousands of individual calls.
  • Use webhooks instead of polling. Subscribe to events rather than polling lists on a timer. Webhook deliveries to you don't count against your limits. See Webhooks and events.
  • Queue inbound webhooks too. If a burst of events triggers API calls from your side, process them from a queue so the burst doesn't turn into a burst of requests.

From V1

V1 allowed 200 reads and 100 writes per second per account. V3 limits are higher and are split into Account and Organization buckets, and responses tell you where you stand.

Related pages


Did this page help you?