Xero Proxy

Developer documentation

Rate limits and quota

What Xero limits, what we forward untouched, and how an account divides one shared budget between several applications.

View as markdown

Passthrough behaviour

Xero's own 429s pass straight through with Retry-After intact. The proxy does not queue, retry or absorb them, so your existing backoff keeps working exactly as it did against Xero directly.

This applies to Xero's refusals. A refusal *this platform* generated under a quota policy is a different thing, because the budget still exists and is merely spoken for: those can be queued and carried out when it frees up, if you ask. See [deferred execution](/developers/deferred).

  • X-DayLimit-Remaining, X-MinLimit-Remaining and X-AppMinLimit-Remaining are forwarded verbatim.
  • Every proxied request records those values, so the account can see how close the organisation is running to its ceiling.
  • A response served from cache carries none of them. Nothing was spent, and repeating the numbers from whenever it was stored would be a stale reading presented as a current one.

The budget is shared

Xero counts its limits against the organisation, roughly 60 calls a minute and 5,000 a day, and from Xero's side every application behind this platform is one app on one connection. That is the point of the platform and it is also its one hazard: without a policy the budget goes to whoever asks fastest, so a nightly export can leave an invoicing integration with nothing.

An account can divide that budget deliberately instead. Governance is off until the account turns it on, so unless yours has, nothing below applies to you and the proxy behaves exactly as this page described before.

The three controls

ControlApplies toWhat it does
Rate limitOne keyA token bucket, refilling continuously. Stops one consumer monopolising the per-minute ceiling. Refusals return rate_limited.
Daily quotaOne keyA count against the UTC day, the same day boundary Xero's own limit uses. Refusals return quota_exhausted.
ReserveThe organisationKeys stop at a floor of the organisation's remaining Xero budget, by priority: bulk first, then standard, never critical. Refusals return budget_reserved.

Priority is set per key by the account: critical for the work that must not stop, standard by default, bulk for backfills and exports. It only matters as the day's budget runs down, which is exactly when it matters.

What spends budget

One rule: a request that reaches Xero spends budget, and a request that does not, does not. A read served from the cache spends no rate token, no daily quota and no reserve, and a request refused by any gate (a bad credential, a missing scope, an unmapped route) never counted in the first place.

That makes caching the cheapest way to stay inside a quota: the same reads still work and simply stop costing anything. Cache-Control: no-cache opts a single request out of the cache, and back into spending.

Knowing where you stand

When a limit applies to your key, every proxied response carries your current position. These are ours and are calculated per request, unlike the X-...Limit-Remaining set above, which is Xero's and is forwarded.

HeaderMeaning
X-XeroProxy-RateLimit-LimitRequests a minute this key is allowed.
X-XeroProxy-RateLimit-RemainingWhole requests available right now. The allowance refills continuously rather than resetting on a boundary.
X-XeroProxy-RateLimit-ResetSeconds until the allowance is back to full.
X-XeroProxy-Quota-LimitRequests this key may make in a UTC day.
X-XeroProxy-Quota-RemainingHow many of them are left.
X-XeroProxy-Quota-ResetSeconds until midnight UTC.
X-XeroProxy-Org-Day-RemainingWhat Xero last reported the organisation has left today. Present when a reserve applies to your key.
X-XeroProxy-Would-ThrottleOnly in observe mode: this request went through, and names the limit that would have refused it.

Handling a throttle

All three refusals are 429 with Retry-After, so a client that already backs off on Xero's 429s needs no changes. If you want to tell them apart, X-Proxy-Error: true marks ours and the code says which limit objected:

{
  "error": {
    "code": "budget_reserved",
    "message": "This organisation has 380 Xero calls left today and bulk keys stop below 1500, so the remainder stays available for higher-priority work. The budget resets at midnight UTC.",
    "retryAfterSeconds": 3600,
    "limit": "org-reserve",
    "documentation": "/developers/errors"
  }
}
  • rate_limited clears by itself within seconds. Retry-After is exact.
  • quota_exhausted clears at midnight UTC and not before. Retrying sooner wastes both our time and yours.
  • budget_reserved is the account's own policy, not Xero's. Retry-After is capped at an hour so that raising a key's priority takes effect promptly, but the budget itself only returns at midnight UTC.

A 429 without X-Proxy-Error came from Xero and means something different: the organisation's budget is genuinely gone, for everyone. There is a built-in alert rule for sustained Xero 429s, and another for an organisation running low before it gets there.

Trying a policy before it bites

An account can run the policy in observe mode. Every request is evaluated and the decision recorded, and nothing is refused. Callers see X-XeroProxy-Would-Throttle on requests an enforcing policy would have stopped, and the account sees the counts per key. It is the honest way to size limits: refusing a request Xero would have accepted changes the behaviour of software that is already shipped, and that is not a change to make on a guess.