Errors and limits
Error format
An error has an HTTP status and this body:
{
"error": {
"code": "invalid_parameter",
"message": "status must be planned, spent or cancelled"
}
}
codeis stable: your code acts on it.messageis for people: show it or log it, but don't parse it. Its text may change.
Error codes
| Status | code | What happened | What to do |
|---|---|---|---|
| 400 | invalid_limit | limit is not an integer from 1 to 200. | Fix the request. |
| 400 | invalid_cursor | cursor is not a next_cursor from this API. | Send the next_cursor you got, as is. |
| 400 | invalid_parameter | A query parameter can't be read. The message names it and says what it takes. | Fix the request. |
| 401 | missing_key | No Authorization: Bearer gak_… header. | Send your key in this header. |
| 401 | invalid_key | The key is unknown, expired or revoked, or its user left the organization. | Create a new key. Retrying won't help. |
| 403 | personal_key_required | The route reads budgets or expenses, and the key doesn't act as a person. | Use a personal key. |
| 404 | not_found | The item doesn't exist, or isn't shared with you. The API doesn't say which. | Check the id, and that the budget is shared with you in the app. |
| 429 | rate_limited | Too many requests with this key. | Wait for the seconds in the Retry-After header, then retry. |
| 500 | internal_error | Something failed on our side. | Retry later, waiting longer each time. |
The key is checked before anything else: a request with a bad key and a bad parameter gets the 401.
A path that doesn't exist, such as /api/v1/invoices, gets a 404 with a plain-text body, not the JSON above.
Rate limit
Each key can make 120 requests per minute.
Over it, the API answers 429 rate_limited with a Retry-After header, in seconds. Wait that long, then retry. The
API sends no header with your remaining quota.
A broader limit per IP address also protects the service. A normal integration never reaches it. Its 429 also has
Retry-After, but its body is not the JSON above: rely on the status code.
To stay under the limit:
- Read lists with
limit=200, so you make fewer requests. - Filter on the server: ask
/expensesfor one budget or one period rather than reading everything. - Cache what changes rarely, such as members and vendors.
When to retry
| Status | Retry? |
|---|---|
| 429 | Yes, after Retry-After. |
| 500, 502, 503, 504 | Yes, a few times, waiting longer each time: 1 s, then 2 s, then 4 s. |
| Any other 4xx | No. Fix the request or the key first. |
Every route is a GET that changes nothing, so retrying is always safe.