Welcome to the Lunch Money v2 API. This page covers the core concepts you need to work with the API effectively — authentication, response shapes, naming conventions, error handling, and more. If you're migrating from v1, see the Migration Guide.
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer YOUR_ACCESS_TOKEN
Get your access token from the Lunch Money developers page. See the Getting Started guide for a step-by-step walkthrough including how to create a test budget.
https://api.lunchmoney.dev/v2
The v2 API uses consistent HTTP status codes and response bodies across all endpoints.
200 OK. Response body contains either a single object or an array property named after the endpoint (e.g., { "transactions": [...] }).201 Created. Response body contains the complete created object including system-defined properties like id and created_at.200 OK. Response body contains the complete updated object.204 No Content. No response body.All errors return an appropriate 4XX status code — the v2 API never returns an error inside a 200 OK response.
| Code | Meaning |
|---|---|
400 |
Bad Request — invalid parameters or request body |
401 |
Unauthorized — missing or invalid Bearer token |
404 |
Not Found — the requested object does not exist |
422 |
Unprocessable Content — request is valid but cannot be fulfilled (e.g., deleting a category with dependents) |
429 |
Too Many Requests — rate limit exceeded; response includes a Retry-After header. See the Rate Limiting guide. |
All error responses share a consistent body format:
{
"message": "Overall error description",
"errors": [
{ "errMsg": "Specific error about a parameter or field" }
]
}
If a request has multiple validation errors, the response will attempt to list as many as possible, but it is not guaranteed to catch all errors in a single response.
The v2 API validates all requests strictly. A 400 is returned if the request includes unexpected parameters, missing required fields, fields with invalid values, or unexpected properties in the request body. This makes it easier to catch mistakes during development.
Object properties follow consistent rules across the entire API:
id, name, statuscategory_id, tag_ids, manual_account_id_at: created_at, updated_at, archived_attag_ids (not tags)children propertyid fields are integer type (not number)The v2 API uses a consistent set of properties — amount, balance, currency, and to_base — across all objects that deal with money. See the Amounts & Balances guide for a full explanation of how these properties work, including the sign convention and multi-currency support.
In v1, some endpoints returned "hydrated" objects — related object details were embedded directly in the response alongside their IDs. For example, a transaction included category_name, asset_name, and an array of full tag objects.
In v2, responses are non-hydrated: related objects are returned as IDs only. To get the full details, call the appropriate endpoint with the returned ID.
// v1 — category details embedded in transaction
{ "category_id": 456, "category_name": "Groceries", "is_income": false }
// v2 — ID only; fetch separately when needed
{ "category_id": 456 }
// GET /v2/categories/456 → { "id": 456, "name": "Groceries", ... }
This improves response times and keeps the API consistent. For apps that need related object details frequently, maintaining a local cache of categories, accounts, and tags is the recommended pattern.
Most objects in the v2 API are updatable via PUT /endpoint/{id}. The rules are consistent:
id or created_at (accepted but ignored).400.tag_ids array, then PUT the full updated array.The v2 API uses a modified SEMVER scheme:
2) — a new major version would only be released for breaking changes/me, /categories, and /transactions has minor version 3)The Version History tracks changes release by release.