Rate Limits & Errors
Error response format
All API errors return a JSON body with an error field describing the problem:
{
"error": "name is required"
} For validation errors with multiple fields, the message may list all issues in a single string.
HTTP status codes
| Status | Meaning | Common causes |
|---|---|---|
200 OK | Request succeeded | GET requests |
201 Created | Resource created | Clean transaction or account insert |
202 Accepted | Saved, flagged for review | Transaction matched a soft duplicate — inserted but marked for review |
204 No Content | Deletion succeeded | DELETE /api/transactions/:id, DELETE /api/accounts/:id |
400 Bad Request | Invalid request | Missing or malformed body fields; PATCH with no editable fields, or account balance in the body (balances are app-side anchors, not API-editable) |
401 Unauthorized | Not authenticated | Missing or invalid API key |
403 Forbidden | Not authorized | Free-tier key (API requires Starter+), or the method/path is outside the API-key allowlist (PUT is never allowed — partial updates are PATCH) |
404 Not Found | Resource not found | Wrong ID, or resource belongs to another user |
409 Conflict | Duplicate or guarded mutation | Transaction matches an existing one on all fields — not inserted. Also returned by corrections (WEA-368): editing/deleting an initial_balance anchor or price_revaluation row, a pocket-allocated money edit/delete, an account type flip with ledger history, or deleting an account still referenced by transactions/pockets/rules/alerts. (POST /api/accounts has no duplicate detection: a retried account create returns 201 and makes a second account — keep returned ids.) |
422 Unprocessable Entity | Cloud sync required | The account has no per-user cloud database yet — the API reads/writes it |
429 Too Many Requests | Rate limit exceeded | See rate limits below |
500 Internal Server Error | Server error | Retry with backoff; if persistent, contact support |
Rate limits
Rate limits are applied per user: a monthly call quota and a token-bucket rate limit.
| Plan | Monthly API calls | Rate limit (requests/second) |
|---|---|---|
| Free | none (403) | — |
| Starter | 1,000 | 2 |
| Growth | 3,000 | 10 |
| Enterprise | 50,000 | 50 |
Exceeding the per-second bucket returns 429 Too Many Requests with a Retry-After header; exceeding the monthly quota returns 402 Payment Required { error, tier, limit, used }.
HTTP/1.1 429 Too Many Requests
Retry-After: 15
Content-Type: application/json
{
"error": "Rate limit exceeded. Retry after 15 seconds."
} Tip
Handling 401 errors
A 401 Unauthorized response means your key is missing or invalid. Check:
- The
Authorizationheader is present:Authorization: Bearer <your-api-key>. - The key has not been revoked (check Settings → API Keys).
- The key belongs to the correct user account.
Handling 500 errors
Transient 500 errors can occur during deploys or brief outages. Retry up to 3 times with exponential backoff (1s, 2s, 4s). If the error persists, the issue is likely in your request — check the request body and headers.
Pagination
Pagination is available on GET /api/transactions (limit up to 200, offset). Other list endpoints (e.g. GET /api/accounts) return all rows in one response.
CORS
The API allows cross-origin requests from trusted origins. For direct browser-to-API calls from your own domain, contact support to add your domain to the CORS allowlist. Server-side requests (from your backend) work without CORS configuration.
API versioning
The API is currently unversioned (all paths are /api/...). A versioned prefix (/api/v1/...) will be introduced with the stable release. Migration guides will be published when versioning is introduced.