Developer documentation
Read caching
How repeat reads are served without spending Xero quota, what invalidates them, and how to opt out per request.
Why it exists
One connection serving many applications means they all spend the same organisation's Xero quota. Three apps polling the chart of accounts every minute cost three times what one does, for identical answers. Caching answers the repeats at the proxy.
It is off until an account turns it on, in the dashboard under Caching. Caching changes what a read returns, so it is never enabled on your behalf.
What is cached
| Request | Treatment |
|---|---|
| GET of reference data (accounts, tax rates, currencies, tracking categories, organisation, items, users, payroll settings) | Cached for the reference time limit, 1 hour by default |
| GET of anything else that returned 200 | Cached for the transactional time limit, 60 seconds by default |
| Attachments, file content, history endpoints | Never cached |
| Conditional requests (If-None-Match, If-Modified-Since) | Passed straight to Xero |
| Anything that is not a GET | Never cached, and clears its own family |
| Responses larger than the size limit | Streamed through and never stored |
The cache key covers the organisation, the path, the query (order-independent) and the Accept header, so a PDF and a JSON read of the same invoice are separate entries and two organisations can never share one.
Invalidation
A successful write clears everything cached for that organisation in the same API family, not just the resource you wrote. A payment changes an invoice and an invoice changes a contact's balance: chasing each of those with a narrower rule is how a cache ends up serving something quietly wrong.
Changes made in Xero directly, or by an application connected outside this platform, are invisible to the cache. Nothing here can see them, so the time limits are what bound how stale a read can be. Set them to match how fresh you actually need the data, and use 0 to turn off a class entirely.
Controlling it per request
| Header | Effect |
|---|---|
Cache-Control: no-cache | Ignore any stored copy, fetch fresh, and store the result |
Cache-Control: no-store | Neither read nor write the cache for this request |
Every response tells you what happened: X-XeroProxy-Cache is hit, miss, store or bypass, and a hit also carries Age and X-XeroProxy-Cache-Expires.
curl -sD - -o /dev/null \
-H "Authorization: Bearer $XERO_PROXY_KEY" \
https://xero-proxy.fullstackplus.com.au/api.xro/2.0/Accounts | grep -i x-xeroproxy-cache- A cached response carries no rate-limit headers: nothing was spent at Xero, so repeating the numbers from when it was stored would be a stale reading presented as a current one.
- Cached bodies are encrypted at rest with the same envelope scheme as connection tokens.
- Every gate still applies. A cached body is only ever returned to a caller that just passed the same credential, network, tenant and scope checks it would have needed to fetch it.