@lunch-money/developer-docs 2.11.1-preview.8 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/STYLE_GUIDE.md +84 -0
- package/docs/amounts-and-balances.md +5 -5
- package/docs/branding-your-app.md +2 -2
- package/docs/currencies.md +8 -9
- package/docs/getting-started.md +11 -11
- package/docs/introduction.md +5 -5
- package/docs/locales.md +4 -4
- package/docs/pagination.md +43 -44
- package/docs/rate-limiting.md +45 -45
- package/docs/using-with-ai.md +2 -2
- package/package.json +1 -1
- package/v2/docs/intro-to-v2.md +12 -12
- package/v2/docs/migration-guide.md +2 -2
- package/v2/docs/version-history.md +1 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Documentation style guide
|
|
2
|
+
|
|
3
|
+
Use this guide for hand-written Markdown in `docs/` and `v2/docs/`. Preserve
|
|
4
|
+
intentional conventions in generated pages, the v1 Slate reference, and version
|
|
5
|
+
history entries.
|
|
6
|
+
|
|
7
|
+
## Voice and structure
|
|
8
|
+
|
|
9
|
+
- Address the reader as “you” and use direct, practical language.
|
|
10
|
+
- Start with the reader's goal before introducing protocol or API details.
|
|
11
|
+
- Use one H1 per page and keep heading levels in logical order.
|
|
12
|
+
- Use sentence case for prose headings. Preserve official names, initialisms,
|
|
13
|
+
endpoint paths, property names, and exact UI labels.
|
|
14
|
+
- Add a next step when a guide belongs to a longer reader journey. Use
|
|
15
|
+
`## Need help?` only when the page provides support options.
|
|
16
|
+
|
|
17
|
+
## Terminology
|
|
18
|
+
|
|
19
|
+
- Use “Lunch Money” for the product and “Lunchbag Labs, Inc.” only when the
|
|
20
|
+
legal company name is required.
|
|
21
|
+
- Use “Developer Portal,” “Developers page,” and “API reference” consistently.
|
|
22
|
+
Preserve different capitalization only for an exact UI label.
|
|
23
|
+
- Write “v1” and “v2” in prose. Use “V2 API” only where it is an established
|
|
24
|
+
proper label, such as the rendered reference title.
|
|
25
|
+
- Distinguish authentication from authorization, personal access tokens from
|
|
26
|
+
OAuth access tokens, and applications from their registered OAuth clients.
|
|
27
|
+
|
|
28
|
+
## Code and placeholders
|
|
29
|
+
|
|
30
|
+
- Format endpoints, properties, headers, status codes, commands, filenames,
|
|
31
|
+
scope names, and literal values as code.
|
|
32
|
+
- Use `YOUR_ACCESS_TOKEN` as the generic access-token placeholder. Environment
|
|
33
|
+
variable examples may use `LUNCH_MONEY_ACCESS_TOKEN`.
|
|
34
|
+
- For equivalent multi-language examples, order tabs as JavaScript/Node.js,
|
|
35
|
+
Python, then cURL. Comparison tabs may follow the order that best teaches the
|
|
36
|
+
difference.
|
|
37
|
+
- Keep examples fictional and never include or request real credentials.
|
|
38
|
+
|
|
39
|
+
## Callouts
|
|
40
|
+
|
|
41
|
+
Use the supported blockquote form:
|
|
42
|
+
|
|
43
|
+
```markdown
|
|
44
|
+
> [!NOTE] Optional title
|
|
45
|
+
> Supporting content.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- Use `NOTE` for context that affects understanding.
|
|
49
|
+
- Use `TIP` for optional advice or a more effective workflow.
|
|
50
|
+
- Use `WARNING` for credible security, privacy, data-loss, breaking-change, or
|
|
51
|
+
operational risk.
|
|
52
|
+
- Use sentence case for optional callout titles. Do not use HTML line breaks to
|
|
53
|
+
separate callout paragraphs.
|
|
54
|
+
|
|
55
|
+
## Tabs
|
|
56
|
+
|
|
57
|
+
Use tabs when equivalent examples in multiple languages or a focused comparison
|
|
58
|
+
are easier to understand together:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
:::tabs
|
|
62
|
+
@tab JavaScript/Node.js
|
|
63
|
+
JavaScript example
|
|
64
|
+
|
|
65
|
+
@tab Python
|
|
66
|
+
Python example
|
|
67
|
+
:::
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use an `@tab` label for each panel and close the container with `:::`. For
|
|
71
|
+
equivalent language examples, order tabs as JavaScript/Node.js, Python, then
|
|
72
|
+
cURL. Comparison tabs may use the order that best teaches the difference.
|
|
73
|
+
|
|
74
|
+
## Links and presentation
|
|
75
|
+
|
|
76
|
+
- Use canonical documentation paths from `manifest.json`. Keep aliases only for
|
|
77
|
+
compatibility; do not introduce new links to aliases.
|
|
78
|
+
- Use normal Markdown links for internal navigation. Keep HTML only when it adds
|
|
79
|
+
behavior Markdown cannot express, such as opening an external link in a new
|
|
80
|
+
tab.
|
|
81
|
+
- Avoid deeply nested headings, very wide tables, and long unbroken text that is
|
|
82
|
+
difficult to use in the constrained content column.
|
|
83
|
+
- Remove trailing whitespace and use blank lines, not `<br>`, for Markdown
|
|
84
|
+
structure unless a verified rendering requirement needs HTML.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Amounts &
|
|
1
|
+
# Amounts & balances
|
|
2
2
|
|
|
3
3
|
The Lunch Money API uses four properties — `amount`, `balance`, `currency`, and `to_base` — consistently across all objects that deal with money. Understanding how they work will help you build accurate, multi-currency-aware integrations.
|
|
4
4
|
|
|
@@ -24,7 +24,7 @@ Like `amount`, `balance` is always in the `currency` of the same object and foll
|
|
|
24
24
|
|
|
25
25
|
## `currency`
|
|
26
26
|
|
|
27
|
-
`currency` is an ISO 4217 currency code (e.g., `"usd"`, `"eur"`, `"gbp"`). It is present on every object that carries an `amount` or `balance` and tells you what currency those values are in. See the [Supported
|
|
27
|
+
`currency` is an ISO 4217 currency code (e.g., `"usd"`, `"eur"`, `"gbp"`). It is present on every object that carries an `amount` or `balance` and tells you what currency those values are in. See the [Supported currencies guide](/currencies) for the full list.
|
|
28
28
|
|
|
29
29
|
## `to_base`
|
|
30
30
|
|
|
@@ -42,7 +42,7 @@ Like `amount`, `balance` is always in the `currency` of the same object and foll
|
|
|
42
42
|
- Display amounts and balances in a single consistent currency across a multi-currency account
|
|
43
43
|
- Perform arithmetic across transactions or accounts in different currencies
|
|
44
44
|
|
|
45
|
-
## Sign
|
|
45
|
+
## Sign convention
|
|
46
46
|
|
|
47
47
|
### Transaction amounts
|
|
48
48
|
|
|
@@ -70,10 +70,10 @@ A negative balance indicates the reversed state for that account type:
|
|
|
70
70
|
- A savings account in overdraft has a negative `balance`
|
|
71
71
|
- A credit card where the issuer owes you money (e.g., after a refund or overpayment) has a negative `balance`
|
|
72
72
|
|
|
73
|
-
> [!NOTE] App
|
|
73
|
+
> [!NOTE] App display setting
|
|
74
74
|
> The Lunch Money app has a setting — **"Show Debits as Negative, Credits as Positive"** — that can flip how amounts are displayed in the UI. This setting is exposed in the API as `debits_as_negative` on the User object. **It has no effect on the API.** The API always returns positive values for debits and negative values for credits regardless of this setting. Apps may read `debits_as_negative` to match the user's preferred display format when showing amounts in their own UI.
|
|
75
75
|
|
|
76
|
-
## Exception:
|
|
76
|
+
## Exception: budget and summary amounts
|
|
77
77
|
|
|
78
78
|
Budget objects and summary response objects don't use the transaction sign convention. Direction is conveyed by the **structure of the response** rather than the sign of the value — amounts are always positive magnitudes, and the object type or category flag tells you whether that activity is income or expense.
|
|
79
79
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Branding your
|
|
1
|
+
# Branding your app
|
|
2
2
|
|
|
3
3
|
Building a tool, plugin, or integration on top of Lunch Money? Use the official **Powered by Lunch Money** badge to credit the connection clearly — without implying that your app *is* Lunch Money.
|
|
4
4
|
|
|
5
5
|
<a href="https://lunchmoney.app/assets/images/media-kit/powered-by-lunch-money-badge.png" download="powered-by-lunch-money-badge.png" target="_blank" rel="noopener noreferrer" title="Download Powered by Lunch Money badge">
|
|
6
|
-
<img src="/v2/images/powered-by-lunch-money-badge.png" alt="Powered by Lunch Money badge" />
|
|
6
|
+
<img src="/v2/images/powered-by-lunch-money-badge.png" alt="Powered by Lunch Money badge" width="320" />
|
|
7
7
|
</a>
|
|
8
8
|
|
|
9
9
|
> [!TIP]
|
package/docs/currencies.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# Supported
|
|
1
|
+
# Supported currencies
|
|
2
2
|
|
|
3
3
|
Lunch Money supports a wide range of currencies for transactions and accounts. All currency values in the API use three-letter lowercase currency codes in ISO 4217 format.
|
|
4
4
|
|
|
5
|
-
## Currency
|
|
5
|
+
## Currency code format
|
|
6
6
|
|
|
7
7
|
Currency codes must be:
|
|
8
8
|
- Three letters
|
|
9
9
|
- Lowercase
|
|
10
10
|
- Valid ISO 4217 currency codes
|
|
11
11
|
|
|
12
|
-
## Supported
|
|
12
|
+
## Supported currencies
|
|
13
13
|
|
|
14
14
|
The following currencies are currently supported in Lunch Money:
|
|
15
15
|
|
|
@@ -183,7 +183,7 @@ The following currencies are currently supported in Lunch Money:
|
|
|
183
183
|
| `zmw` | Zambian Kwacha |
|
|
184
184
|
| `zwl` | Zimbabwean Dollar |
|
|
185
185
|
|
|
186
|
-
## Using
|
|
186
|
+
## Using currencies in the API
|
|
187
187
|
|
|
188
188
|
When creating or updating transactions, accounts, or other resources that include currency information, use the three-letter lowercase currency code:
|
|
189
189
|
|
|
@@ -247,11 +247,11 @@ curl -X POST 'https://api.lunchmoney.dev/v2/transactions' \
|
|
|
247
247
|
```
|
|
248
248
|
:::
|
|
249
249
|
|
|
250
|
-
## Default
|
|
250
|
+
## Default currency
|
|
251
251
|
|
|
252
252
|
If you don't specify a currency when creating a transaction or account, Lunch Money will default to your account's primary currency. You can check your primary currency by calling the `GET /v2/me` endpoint.
|
|
253
253
|
|
|
254
|
-
## Currency
|
|
254
|
+
## Currency validation
|
|
255
255
|
|
|
256
256
|
The API validates that currency codes match the supported list. If you provide an unsupported currency code, you'll receive a validation error:
|
|
257
257
|
|
|
@@ -268,10 +268,9 @@ The API validates that currency codes match the supported list. If you provide a
|
|
|
268
268
|
}
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
-
## Requesting
|
|
271
|
+
## Requesting new currencies
|
|
272
272
|
|
|
273
273
|
If your currency is missing from the supported list, please let us know via email at [support@lunchmoney.app](mailto:support@lunchmoney.app) and we'll work on getting it added.
|
|
274
274
|
|
|
275
|
-
> [!NOTE] Currency
|
|
275
|
+
> [!NOTE] Currency support
|
|
276
276
|
> Currency support is continuously expanding. If you need a currency that's not currently listed, reach out to our support team and we'll prioritize adding it.
|
|
277
|
-
|
package/docs/getting-started.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# Getting
|
|
1
|
+
# Getting started with the API
|
|
2
2
|
|
|
3
3
|
Welcome to the Lunch Money developer API! This API has enabled the Lunch Money user community to build a broad set of tools and plug-ins to complement their Lunch Money experience and help other users. This guide will help you get started using the Lunch Money API.
|
|
4
4
|
|
|
5
5
|
Most users interact with their Lunch Money data using the official web or mobile app. Making API calls is another way to interact with your Lunch Money data. You can make API calls that **get** your data for you, allowing you to do other things with it, like generating your own charts or simply archiving your data. You can also make API calls that **change** or **delete** your data. Any changes you make are permanent, just like they would be if you made changes using the web or mobile app.
|
|
6
6
|
|
|
7
7
|
> [!WARNING]
|
|
8
|
-
> Changes made via the API are permanent and cannot be undone
|
|
8
|
+
> Changes made via the API are permanent and cannot be undone. Always use a test budget when getting started.
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
## Create a
|
|
11
|
+
## Create a test budgeting account
|
|
12
12
|
|
|
13
13
|
Since changes to your data made by the API are permanent, the best way to begin interacting with the API is to <a href="https://support.lunchmoney.app/miscellaneous/unlimited-budgeting-accounts" target="_blank" rel="noopener noreferrer">create a new test budgeting account</a> in your existing Lunch Money account. As you start using the API, you can interact with this test account and ensure that your real data is not modified.
|
|
14
14
|
|
|
@@ -49,7 +49,7 @@ After you do this, the web page will show you a long alphanumeric string. This i
|
|
|
49
49
|
Congratulations! You've got an access token and are ready to make an API request! Later, when you are more comfortable with the API, you can repeat this process to create a new access token associated with your real budget.
|
|
50
50
|
|
|
51
51
|
> [!WARNING]
|
|
52
|
-
> Treat your access tokens like passwords
|
|
52
|
+
> Treat your access tokens like passwords. Don't share them with anyone you don't trust.
|
|
53
53
|
|
|
54
54
|
If you ever worry that your access token has been compromised, you can always come back to the Developers page and hit the Revoke button.
|
|
55
55
|
|
|
@@ -63,21 +63,21 @@ When you start to write code that makes API calls, you will need to include this
|
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
curl --location 'https://api.lunchmoney.dev/v2/me' \
|
|
66
|
-
--header 'Authorization: Bearer
|
|
66
|
+
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
If this doesn't make sense yet, that's OK!
|
|
69
|
+
If this doesn't make sense yet, that's OK!
|
|
70
70
|
|
|
71
|
-
There is an easier way to use your access token to make requests. Head over to the [API Reference Documentation](/v2/docs).
|
|
71
|
+
There is an easier way to use your access token to make requests. Head over to the [API Reference Documentation](/v2/docs).
|
|
72
72
|
|
|
73
73
|
:::tabs
|
|
74
74
|
|
|
75
75
|
@tab 1. Supply Your Token
|
|
76
|
-
Paste your new token into the box that says "Bearer Token" in the server configuration in the upper right hand corner.
|
|
76
|
+
Paste your new token into the box that says "Bearer Token" in the server configuration in the upper right hand corner.
|
|
77
77
|

|
|
78
78
|
|
|
79
79
|
@tab 2. Make a Test Request
|
|
80
|
-
Scroll down to the section called "Get Current User" and hit the "Test Request" button.
|
|
80
|
+
Scroll down to the section called "Get Current User" and hit the "Test Request" button.
|
|
81
81
|

|
|
82
82
|
:::
|
|
83
83
|
|
|
@@ -85,14 +85,14 @@ This will pop up a dialog box that you can use to send an API request. This is a
|
|
|
85
85
|
|
|
86
86
|
From here, you can explore the rest of the Lunch Money API and start thinking about what you might do with it.
|
|
87
87
|
|
|
88
|
-
## What
|
|
88
|
+
## What can you build?
|
|
89
89
|
|
|
90
90
|
The Lunch Money community has already built a wide variety of tools — automations, importers, dashboards, and more. Check out the <a href="https://lunchmoney.app/developers#community-projects" target="_blank" rel="noopener noreferrer">Developer Tools & Community Projects</a> list for inspiration, or drop by the <a href="https://discord.com/channels/842337014556262411/1134597088504729780" target="_blank" rel="noopener noreferrer">Show and Tell channel</a> on Discord to see what others are sharing.
|
|
91
91
|
|
|
92
92
|
> [!NOTE]
|
|
93
93
|
> Built something with the API? Let us know and we'll add it to the list!
|
|
94
94
|
|
|
95
|
-
##
|
|
95
|
+
## Need help?
|
|
96
96
|
|
|
97
97
|
If you still aren't sure how to get started, we have other resources to help.
|
|
98
98
|
|
package/docs/introduction.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Welcome to the Lunch Money developer documentation. This site covers the **v2 API** — the current generation of the Lunch Money API — along with guides, a reference for the legacy v1 API, and tools for exploring the API without touching real data.
|
|
4
4
|
|
|
5
|
-
## Getting
|
|
5
|
+
## Getting started
|
|
6
6
|
|
|
7
7
|
The v2 API is available at:
|
|
8
8
|
|
|
@@ -16,9 +16,9 @@ Get your access token from the <a href="https://my.lunchmoney.app/developers" ta
|
|
|
16
16
|
Authorization: Bearer YOUR_ACCESS_TOKEN
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
See the [Getting
|
|
19
|
+
See the [Getting started guide](/getting-started) for a full walkthrough, including how to create a test budget and make your first request.
|
|
20
20
|
|
|
21
|
-
## Trying the API
|
|
21
|
+
## Trying the API without real data
|
|
22
22
|
|
|
23
23
|
The <a href="/v2/docs">interactive API reference</a> lets you make live API requests directly from the documentation — including a **static mock server** you can use to explore the API without an access token or any risk to real data.
|
|
24
24
|
|
|
@@ -34,7 +34,7 @@ Set your Bearer Token to any string of **11 or more characters** — no real tok
|
|
|
34
34
|
|
|
35
35
|
:::
|
|
36
36
|
|
|
37
|
-
## Client
|
|
37
|
+
## Client libraries & SDKs
|
|
38
38
|
|
|
39
39
|
An official **TypeScript SDK** is available:
|
|
40
40
|
- NPM: <a href="https://www.npmjs.com/package/@lunch-money/v2-api-spec" target="_blank" rel="noopener noreferrer">`@lunch-money/v2-api-spec`</a>
|
|
@@ -58,6 +58,6 @@ Join the conversation in the <a href="https://lunchmoney.app/discord" target="_b
|
|
|
58
58
|
|
|
59
59
|
**Useful links:**
|
|
60
60
|
- <a href="/v2/docs">Interactive API Reference</a>
|
|
61
|
-
- [Getting
|
|
61
|
+
- [Getting started guide](/getting-started)
|
|
62
62
|
- <a href="https://github.com/lunch-money/awesome-lunchmoney" target="_blank" rel="noopener noreferrer">Awesome Lunch Money Projects</a>
|
|
63
63
|
- [Contact: devsupport@lunchmoney.app](mailto:devsupport@lunchmoney.app)
|
package/docs/locales.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Supported
|
|
1
|
+
# Supported locales
|
|
2
2
|
|
|
3
3
|
Lunch Money uses locale tags to format numbers and currency amounts in the web
|
|
4
4
|
and mobile apps. Date presentation is controlled separately by the user-level
|
|
@@ -10,14 +10,14 @@ currency symbol or code placement and rendering. It does not change the
|
|
|
10
10
|
account's selected currency, numeric values, or the representation of dates,
|
|
11
11
|
numbers, and amounts in API responses.
|
|
12
12
|
|
|
13
|
-
## Locale
|
|
13
|
+
## Locale tag format
|
|
14
14
|
|
|
15
15
|
Each supported value combines a language code and a region code, separated by a
|
|
16
16
|
hyphen. For example, `en-US` selects English formatting conventions for the
|
|
17
17
|
United States, while `en-GB` selects English formatting conventions for the
|
|
18
18
|
United Kingdom.
|
|
19
19
|
|
|
20
|
-
## Supported
|
|
20
|
+
## Supported locales
|
|
21
21
|
|
|
22
22
|
The examples format `123456789` as USD. An account's primary currency determines
|
|
23
23
|
the currency symbol and number of fraction digits. Exact spacing and symbol
|
|
@@ -111,7 +111,7 @@ implementation.
|
|
|
111
111
|
| `uk-UA` | Ukrainian | Ukraine | `123 456 789,00 USD` |
|
|
112
112
|
| `vi-VN` | Vietnamese | Vietnam | `123.456.789,00 US$` |
|
|
113
113
|
|
|
114
|
-
## Legacy
|
|
114
|
+
## Legacy locale tags
|
|
115
115
|
|
|
116
116
|
The supported list retains two legacy language codes for compatibility:
|
|
117
117
|
|
package/docs/pagination.md
CHANGED
|
@@ -6,10 +6,10 @@ The Lunch Money API uses offset-based pagination for the `GET /transactions` end
|
|
|
6
6
|
|
|
7
7
|
When retrieving transactions using `GET /transactions`, the API supports pagination through the `limit` and `offset` query parameters. By default, the endpoint returns up to 1000 transactions per request. If more transactions match your filter criteria, the response includes a `has_more` property set to `true`, indicating that additional pages are available.
|
|
8
8
|
|
|
9
|
-
> [!NOTE] Pagination
|
|
9
|
+
> [!NOTE] Pagination scope
|
|
10
10
|
> Pagination works with all filter parameters including date ranges, account IDs, categories, tags, and status filters. The pagination applies to the filtered result set.
|
|
11
11
|
|
|
12
|
-
## How
|
|
12
|
+
## How pagination works
|
|
13
13
|
|
|
14
14
|
### Parameters
|
|
15
15
|
|
|
@@ -24,13 +24,13 @@ When retrieving transactions using `GET /transactions`, the API supports paginat
|
|
|
24
24
|
- Minimum: `0`
|
|
25
25
|
- Use this to fetch subsequent pages of results
|
|
26
26
|
|
|
27
|
-
### Response
|
|
27
|
+
### Response property
|
|
28
28
|
|
|
29
29
|
- **`has_more`**: A boolean property in the response indicating whether more transactions are available
|
|
30
30
|
- `true`: More transactions match your filter criteria and are available on subsequent pages
|
|
31
31
|
- `false`: All matching transactions have been returned
|
|
32
32
|
|
|
33
|
-
### Basic
|
|
33
|
+
### Basic pagination flow
|
|
34
34
|
|
|
35
35
|
1. Make your first request (optionally with `limit` specified, defaults to 1000)
|
|
36
36
|
2. Check the `has_more` property in the response
|
|
@@ -39,7 +39,7 @@ When retrieving transactions using `GET /transactions`, the API supports paginat
|
|
|
39
39
|
|
|
40
40
|
## Examples
|
|
41
41
|
|
|
42
|
-
### Basic
|
|
42
|
+
### Basic pagination
|
|
43
43
|
|
|
44
44
|
Here's how to fetch transactions page by page:
|
|
45
45
|
|
|
@@ -61,10 +61,10 @@ async function getAllTransactions(accessToken, limit = 1000) {
|
|
|
61
61
|
|
|
62
62
|
const data = await response.json();
|
|
63
63
|
allTransactions.push(...data.transactions);
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
hasMore = data.has_more;
|
|
66
66
|
offset += limit;
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
console.log(`Fetched ${data.transactions.length} transactions (total: ${allTransactions.length})`);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -84,22 +84,22 @@ def get_all_transactions(access_token, limit=1000):
|
|
|
84
84
|
all_transactions = []
|
|
85
85
|
offset = 0
|
|
86
86
|
has_more = True
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
headers = {
|
|
89
89
|
'Authorization': f'Bearer {access_token}'
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
while has_more:
|
|
93
93
|
url = f'https://api.lunchmoney.dev/v2/transactions?limit={limit}&offset={offset}'
|
|
94
94
|
response = requests.get(url, headers=headers)
|
|
95
95
|
data = response.json()
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
all_transactions.extend(data['transactions'])
|
|
98
98
|
has_more = data['has_more']
|
|
99
99
|
offset += limit
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
print(f"Fetched {len(data['transactions'])} transactions (total: {len(all_transactions)})")
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
return all_transactions
|
|
104
104
|
|
|
105
105
|
# Usage
|
|
@@ -119,18 +119,18 @@ ALL_TRANSACTIONS="[]"
|
|
|
119
119
|
|
|
120
120
|
while [ "$HAS_MORE" = "true" ]; do
|
|
121
121
|
echo "Fetching transactions with offset=$OFFSET, limit=$LIMIT..."
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
RESPONSE=$(curl -s -X GET \
|
|
124
124
|
"https://api.lunchmoney.dev/v2/transactions?limit=$LIMIT&offset=$OFFSET" \
|
|
125
125
|
-H "Authorization: Bearer $ACCESS_TOKEN")
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
TRANSACTIONS=$(echo $RESPONSE | jq -r '.transactions')
|
|
128
128
|
HAS_MORE=$(echo $RESPONSE | jq -r '.has_more')
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
# Merge transactions (simplified - in practice you'd want proper JSON merging)
|
|
131
131
|
COUNT=$(echo $TRANSACTIONS | jq 'length')
|
|
132
132
|
echo "Fetched $COUNT transactions"
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
OFFSET=$((OFFSET + LIMIT))
|
|
135
135
|
done
|
|
136
136
|
|
|
@@ -138,7 +138,7 @@ echo "All transactions fetched"
|
|
|
138
138
|
```
|
|
139
139
|
:::
|
|
140
140
|
|
|
141
|
-
### Pagination with
|
|
141
|
+
### Pagination with date filters
|
|
142
142
|
|
|
143
143
|
Pagination works seamlessly with date range filters:
|
|
144
144
|
|
|
@@ -166,7 +166,7 @@ async function getTransactionsByDateRange(accessToken, startDate, endDate) {
|
|
|
166
166
|
|
|
167
167
|
const data = await response.json();
|
|
168
168
|
allTransactions.push(...data.transactions);
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
hasMore = data.has_more;
|
|
171
171
|
offset += limit;
|
|
172
172
|
}
|
|
@@ -192,9 +192,9 @@ def get_transactions_by_date_range(access_token, start_date, end_date):
|
|
|
192
192
|
offset = 0
|
|
193
193
|
has_more = True
|
|
194
194
|
limit = 1000
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
headers = {'Authorization': f'Bearer {access_token}'}
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
while has_more:
|
|
199
199
|
params = {
|
|
200
200
|
'start_date': start_date,
|
|
@@ -202,15 +202,15 @@ def get_transactions_by_date_range(access_token, start_date, end_date):
|
|
|
202
202
|
'limit': limit,
|
|
203
203
|
'offset': offset
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
url = f"https://api.lunchmoney.dev/v2/transactions?{urlencode(params)}"
|
|
207
207
|
response = requests.get(url, headers=headers)
|
|
208
208
|
data = response.json()
|
|
209
|
-
|
|
209
|
+
|
|
210
210
|
all_transactions.extend(data['transactions'])
|
|
211
211
|
has_more = data['has_more']
|
|
212
212
|
offset += limit
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
return all_transactions
|
|
215
215
|
|
|
216
216
|
# Usage: Get all transactions for January 2024
|
|
@@ -236,17 +236,17 @@ while [ "$HAS_MORE" = "true" ]; do
|
|
|
236
236
|
RESPONSE=$(curl -s -X GET \
|
|
237
237
|
"https://api.lunchmoney.dev/v2/transactions?start_date=$START_DATE&end_date=$END_DATE&limit=$LIMIT&offset=$OFFSET" \
|
|
238
238
|
-H "Authorization: Bearer $ACCESS_TOKEN")
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
HAS_MORE=$(echo $RESPONSE | jq -r '.has_more')
|
|
241
241
|
COUNT=$(echo $RESPONSE | jq -r '.transactions | length')
|
|
242
242
|
echo "Fetched $COUNT transactions (offset=$OFFSET)"
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
OFFSET=$((OFFSET + LIMIT))
|
|
245
245
|
done
|
|
246
246
|
```
|
|
247
247
|
:::
|
|
248
248
|
|
|
249
|
-
### Handling the `has_more`
|
|
249
|
+
### Handling the `has_more` flag
|
|
250
250
|
|
|
251
251
|
Always check the `has_more` property to determine if you need to fetch more pages:
|
|
252
252
|
|
|
@@ -262,7 +262,7 @@ async function fetchTransactionsPage(accessToken, limit = 1000, offset = 0) {
|
|
|
262
262
|
});
|
|
263
263
|
|
|
264
264
|
const data = await response.json();
|
|
265
|
-
|
|
265
|
+
|
|
266
266
|
return {
|
|
267
267
|
transactions: data.transactions,
|
|
268
268
|
hasMore: data.has_more,
|
|
@@ -288,10 +288,10 @@ import requests
|
|
|
288
288
|
def fetch_transactions_page(access_token, limit=1000, offset=0):
|
|
289
289
|
url = f'https://api.lunchmoney.dev/v2/transactions?limit={limit}&offset={offset}'
|
|
290
290
|
headers = {'Authorization': f'Bearer {access_token}'}
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
response = requests.get(url, headers=headers)
|
|
293
293
|
data = response.json()
|
|
294
|
-
|
|
294
|
+
|
|
295
295
|
return {
|
|
296
296
|
'transactions': data['transactions'],
|
|
297
297
|
'has_more': data['has_more'],
|
|
@@ -335,9 +335,9 @@ fi
|
|
|
335
335
|
```
|
|
336
336
|
:::
|
|
337
337
|
|
|
338
|
-
## Best
|
|
338
|
+
## Best practices
|
|
339
339
|
|
|
340
|
-
### 1. Use
|
|
340
|
+
### 1. Use appropriate limit values
|
|
341
341
|
|
|
342
342
|
Choose a `limit` value that balances performance and number of requests:
|
|
343
343
|
|
|
@@ -353,7 +353,7 @@ const response = await fetch('/v2/transactions?limit=1000');
|
|
|
353
353
|
const response = await fetch('/v2/transactions?limit=500');
|
|
354
354
|
```
|
|
355
355
|
|
|
356
|
-
### 2. Always
|
|
356
|
+
### 2. Always check `has_more`
|
|
357
357
|
|
|
358
358
|
Don't assume you've received all results. Always check the `has_more` property:
|
|
359
359
|
|
|
@@ -375,7 +375,7 @@ while (hasMore) {
|
|
|
375
375
|
}
|
|
376
376
|
```
|
|
377
377
|
|
|
378
|
-
### 3. Combine with
|
|
378
|
+
### 3. Combine with date ranges when possible
|
|
379
379
|
|
|
380
380
|
If you know the date range you need, use `start_date` and `end_date` parameters to reduce the result set size:
|
|
381
381
|
|
|
@@ -387,12 +387,12 @@ const response = await fetch(
|
|
|
387
387
|
|
|
388
388
|
// ❌ Less efficient: Fetches all transactions then filters client-side
|
|
389
389
|
const allTransactions = await fetchAllTransactions();
|
|
390
|
-
const janTransactions = allTransactions.filter(t =>
|
|
390
|
+
const janTransactions = allTransactions.filter(t =>
|
|
391
391
|
t.date >= '2024-01-01' && t.date <= '2024-01-31'
|
|
392
392
|
);
|
|
393
393
|
```
|
|
394
394
|
|
|
395
|
-
### 4. Handle
|
|
395
|
+
### 4. Handle edge cases
|
|
396
396
|
|
|
397
397
|
- **Empty results**: The `transactions` array will be empty, and `has_more` will be `false`
|
|
398
398
|
- **Exact page boundary**: If the number of results is exactly divisible by your limit, `has_more` will be `false` on the last page
|
|
@@ -406,24 +406,24 @@ async function safeFetchTransactions(accessToken, limit = 1000, offset = 0) {
|
|
|
406
406
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
|
407
407
|
}
|
|
408
408
|
);
|
|
409
|
-
|
|
409
|
+
|
|
410
410
|
if (!response.ok) {
|
|
411
411
|
throw new Error(`API error: ${response.status}`);
|
|
412
412
|
}
|
|
413
|
-
|
|
413
|
+
|
|
414
414
|
const data = await response.json();
|
|
415
|
-
|
|
415
|
+
|
|
416
416
|
// Handle empty results
|
|
417
417
|
if (data.transactions.length === 0 && !data.has_more) {
|
|
418
418
|
console.log('No transactions found');
|
|
419
419
|
return [];
|
|
420
420
|
}
|
|
421
|
-
|
|
421
|
+
|
|
422
422
|
return data;
|
|
423
423
|
}
|
|
424
424
|
```
|
|
425
425
|
|
|
426
|
-
### 5. Implement
|
|
426
|
+
### 5. Implement rate limit awareness
|
|
427
427
|
|
|
428
428
|
When paginating through many pages, be mindful of rate limits. Consider adding delays between requests:
|
|
429
429
|
|
|
@@ -439,7 +439,7 @@ async function getAllTransactionsWithRateLimit(accessToken) {
|
|
|
439
439
|
allTransactions.push(...data.transactions);
|
|
440
440
|
hasMore = data.has_more;
|
|
441
441
|
offset += limit;
|
|
442
|
-
|
|
442
|
+
|
|
443
443
|
// Small delay to avoid hitting rate limits
|
|
444
444
|
if (hasMore) {
|
|
445
445
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -452,7 +452,7 @@ async function getAllTransactionsWithRateLimit(accessToken) {
|
|
|
452
452
|
|
|
453
453
|
## Troubleshooting
|
|
454
454
|
|
|
455
|
-
### Common
|
|
455
|
+
### Common issues
|
|
456
456
|
|
|
457
457
|
**Issue**: Getting duplicate transactions across pages
|
|
458
458
|
|
|
@@ -506,7 +506,7 @@ const data = await fetch('/v2/transactions?start_date=2024-01-01&end_date=2024-0
|
|
|
506
506
|
// data.transactions.length might be 200, and has_more will be false
|
|
507
507
|
```
|
|
508
508
|
|
|
509
|
-
## Need
|
|
509
|
+
## Need help?
|
|
510
510
|
|
|
511
511
|
If you're experiencing pagination issues that can't be resolved through the techniques described above:
|
|
512
512
|
|
|
@@ -514,4 +514,3 @@ If you're experiencing pagination issues that can't be resolved through the tech
|
|
|
514
514
|
- Verify that you're checking the `has_more` property on each response
|
|
515
515
|
- Consider using date range filters to reduce the result set size
|
|
516
516
|
- [Email our developer advocate](mailto:jp@lunchmoney.app) if you need assistance with pagination implementation
|
|
517
|
-
|
package/docs/rate-limiting.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Rate
|
|
1
|
+
# Rate limiting
|
|
2
2
|
|
|
3
3
|
The Lunch Money API implements rate limiting to ensure fair usage and prevent abuse of API resources. This guide explains how rate limiting works and how to monitor your rate limit status using response headers.
|
|
4
4
|
|
|
@@ -9,20 +9,20 @@ Rate limiting is applied to all requests to the `/v1/` and `/v2/` endpoints and
|
|
|
9
9
|
|
|
10
10
|
If you exceed this limit, you will receive a `429 Too Many Requests` response.
|
|
11
11
|
|
|
12
|
-
> [!NOTE] Rate
|
|
12
|
+
> [!NOTE] Rate limit scope
|
|
13
13
|
> Rate limiting is applied per IP address. Requests from the same IP address share the same rate limit quotas.
|
|
14
14
|
|
|
15
|
-
## Rate
|
|
15
|
+
## Rate limit response
|
|
16
16
|
|
|
17
17
|
When you exceed a rate limit, the API returns a `429 Too Many Requests` response:
|
|
18
18
|
|
|
19
|
-
### HTTP
|
|
19
|
+
### HTTP status code
|
|
20
20
|
|
|
21
21
|
```
|
|
22
22
|
429 Too Many Requests
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
### Response
|
|
25
|
+
### Response body
|
|
26
26
|
|
|
27
27
|
The response body follows the standard v2 error format:
|
|
28
28
|
|
|
@@ -37,11 +37,11 @@ The response body follows the standard v2 error format:
|
|
|
37
37
|
}
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
### Response
|
|
40
|
+
### Response headers
|
|
41
41
|
|
|
42
42
|
The API includes rate limit information in response headers for all requests (both successful and rate-limited). You can inspect these headers to monitor your current rate limit status and determine when you can make additional requests.
|
|
43
43
|
|
|
44
|
-
#### Standard
|
|
44
|
+
#### Standard headers (RFC Draft 7)
|
|
45
45
|
|
|
46
46
|
The API includes standard rate limit headers in all responses:
|
|
47
47
|
|
|
@@ -56,12 +56,12 @@ RateLimit-Remaining: 3
|
|
|
56
56
|
RateLimit-Reset: 1704067200
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
#### Legacy
|
|
59
|
+
#### Legacy headers
|
|
60
60
|
|
|
61
61
|
For backward compatibility, the API also includes legacy `X-RateLimit-*` headers:
|
|
62
62
|
|
|
63
63
|
- **`X-RateLimit-Limit`**: The maximum number of requests allowed per window
|
|
64
|
-
- **`X-RateLimit-Remaining`**: The number of requests remaining in the current window
|
|
64
|
+
- **`X-RateLimit-Remaining`**: The number of requests remaining in the current window
|
|
65
65
|
- **`X-RateLimit-Reset`**: The time (in seconds since Unix epoch) when the rate limit window resets
|
|
66
66
|
|
|
67
67
|
Example headers:
|
|
@@ -71,7 +71,7 @@ X-RateLimit-Remaining: 3
|
|
|
71
71
|
X-RateLimit-Reset: 1704067200
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
#### Retry-After
|
|
74
|
+
#### `Retry-After` header
|
|
75
75
|
|
|
76
76
|
When you receive a `429 Too Many Requests` response, the API includes a `Retry-After` header indicating how many seconds you should wait before retrying:
|
|
77
77
|
|
|
@@ -81,12 +81,12 @@ Retry-After: 45
|
|
|
81
81
|
|
|
82
82
|
This value represents the time until the rate limit window resets (rounded up to the nearest second).
|
|
83
83
|
|
|
84
|
-
> [!TIP] Using Retry-After
|
|
84
|
+
> [!TIP] Using `Retry-After`
|
|
85
85
|
> Always respect the `Retry-After` header value when implementing retry logic. Waiting for the specified duration ensures you don't waste requests on premature retries.
|
|
86
86
|
|
|
87
|
-
## Monitoring
|
|
87
|
+
## Monitoring rate limits
|
|
88
88
|
|
|
89
|
-
### Reading
|
|
89
|
+
### Reading rate limit headers
|
|
90
90
|
|
|
91
91
|
You can monitor your rate limit status by inspecting the headers in every API response, even successful ones. This allows you to proactively slow down your request rate before hitting the limit.
|
|
92
92
|
|
|
@@ -95,12 +95,12 @@ You can monitor your rate limit status by inspecting the headers in every API re
|
|
|
95
95
|
```javascript
|
|
96
96
|
async function makeRequest(url, options) {
|
|
97
97
|
const response = await fetch(url, options);
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Check rate limit status from headers
|
|
100
100
|
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
|
|
101
101
|
const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
|
|
102
102
|
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
if (remaining < 5) {
|
|
105
105
|
console.warn(`Rate limit warning: ${remaining}/${limit} requests remaining`);
|
|
106
106
|
const waitTime = resetTime - Math.floor(Date.now() / 1000);
|
|
@@ -108,13 +108,13 @@ async function makeRequest(url, options) {
|
|
|
108
108
|
console.log(`Rate limit resets in ${waitTime} seconds`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
if (response.status === 429) {
|
|
113
113
|
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
|
|
114
114
|
console.error(`Rate limited! Retry after ${retryAfter} seconds`);
|
|
115
115
|
throw new Error(`Rate limited: retry after ${retryAfter}s`);
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
return response;
|
|
119
119
|
}
|
|
120
120
|
```
|
|
@@ -126,23 +126,23 @@ import time
|
|
|
126
126
|
|
|
127
127
|
def make_request(url, headers):
|
|
128
128
|
response = requests.get(url, headers=headers)
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
# Check rate limit status
|
|
131
131
|
remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
|
|
132
132
|
limit = int(response.headers.get('X-RateLimit-Limit', 0))
|
|
133
133
|
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
if remaining < 5:
|
|
136
136
|
print(f"Rate limit warning: {remaining}/{limit} requests remaining")
|
|
137
137
|
wait_time = reset_time - int(time.time())
|
|
138
138
|
if wait_time > 0:
|
|
139
139
|
print(f"Rate limit resets in {wait_time} seconds")
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
if response.status_code == 429:
|
|
142
142
|
retry_after = int(response.headers.get('Retry-After', 60))
|
|
143
143
|
print(f"Rate limited! Retry after {retry_after} seconds")
|
|
144
144
|
raise Exception(f"Rate limited: retry after {retry_after}s")
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
return response
|
|
147
147
|
```
|
|
148
148
|
|
|
@@ -150,7 +150,7 @@ def make_request(url, headers):
|
|
|
150
150
|
```bash
|
|
151
151
|
# Make a request and capture headers
|
|
152
152
|
response=$(curl -s -D /tmp/headers.txt -o /tmp/body.txt -w "%{http_code}" \
|
|
153
|
-
|
|
153
|
+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
|
154
154
|
"https://api.lunchmoney.dev/v2/me")
|
|
155
155
|
|
|
156
156
|
# Extract rate limit headers
|
|
@@ -170,7 +170,7 @@ fi
|
|
|
170
170
|
```
|
|
171
171
|
:::
|
|
172
172
|
|
|
173
|
-
### Implementing
|
|
173
|
+
### Implementing exponential backoff
|
|
174
174
|
|
|
175
175
|
When you receive a `429` response, implement exponential backoff with jitter to avoid overwhelming the API:
|
|
176
176
|
|
|
@@ -178,28 +178,28 @@ When you receive a `429` response, implement exponential backoff with jitter to
|
|
|
178
178
|
async function makeRequestWithRetry(url, options, maxRetries = 3) {
|
|
179
179
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
180
180
|
const response = await fetch(url, options);
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
if (response.status !== 429) {
|
|
183
183
|
return response;
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
// Get retry-after header or use exponential backoff
|
|
187
187
|
const retryAfter = parseInt(response.headers.get('Retry-After') || '0');
|
|
188
|
-
const waitTime = retryAfter > 0
|
|
188
|
+
const waitTime = retryAfter > 0
|
|
189
189
|
? retryAfter * 1000 // Convert seconds to milliseconds
|
|
190
190
|
: Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, 30000);
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
console.log(`Rate limited. Waiting ${waitTime}ms before retry ${attempt + 1}/${maxRetries}`);
|
|
193
193
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
throw new Error('Max retries exceeded due to rate limiting');
|
|
197
197
|
}
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
## Best
|
|
200
|
+
## Best practices
|
|
201
201
|
|
|
202
|
-
### 1. Monitor
|
|
202
|
+
### 1. Monitor headers proactively
|
|
203
203
|
|
|
204
204
|
Don't wait for a `429` response. Check rate limit headers on every request and adjust your request rate accordingly:
|
|
205
205
|
|
|
@@ -211,7 +211,7 @@ if (remaining < threshold) {
|
|
|
211
211
|
}
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
### 2. Implement
|
|
214
|
+
### 2. Implement request queuing
|
|
215
215
|
|
|
216
216
|
For applications that need to make many requests, implement a queue system that respects rate limits:
|
|
217
217
|
|
|
@@ -222,14 +222,14 @@ class RateLimitedQueue {
|
|
|
222
222
|
this.remaining = 100; // Start with max
|
|
223
223
|
this.resetTime = Date.now() + (15 * 60 * 1000);
|
|
224
224
|
}
|
|
225
|
-
|
|
225
|
+
|
|
226
226
|
async enqueue(requestFn) {
|
|
227
227
|
return new Promise((resolve, reject) => {
|
|
228
228
|
this.queue.push({ requestFn, resolve, reject });
|
|
229
229
|
this.processQueue();
|
|
230
230
|
});
|
|
231
231
|
}
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
async processQueue() {
|
|
234
234
|
if (this.queue.length === 0 || this.remaining <= 0) {
|
|
235
235
|
if (this.remaining <= 0 && this.resetTime > Date.now()) {
|
|
@@ -238,15 +238,15 @@ class RateLimitedQueue {
|
|
|
238
238
|
}
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
|
-
|
|
241
|
+
|
|
242
242
|
const { requestFn, resolve, reject } = this.queue.shift();
|
|
243
243
|
try {
|
|
244
244
|
const response = await requestFn();
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
// Update rate limit status from headers
|
|
247
247
|
this.remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
|
|
248
248
|
this.resetTime = parseInt(response.headers.get('X-RateLimit-Reset') || '0') * 1000;
|
|
249
|
-
|
|
249
|
+
|
|
250
250
|
resolve(response);
|
|
251
251
|
this.processQueue(); // Process next item
|
|
252
252
|
} catch (error) {
|
|
@@ -256,7 +256,7 @@ class RateLimitedQueue {
|
|
|
256
256
|
}
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
-
### 3. Cache
|
|
259
|
+
### 3. Cache responses when possible
|
|
260
260
|
|
|
261
261
|
Reduce the number of API calls by caching responses locally:
|
|
262
262
|
|
|
@@ -267,24 +267,24 @@ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
|
267
267
|
async function getCachedRequest(url, options) {
|
|
268
268
|
const cacheKey = `${url}:${JSON.stringify(options)}`;
|
|
269
269
|
const cached = cache.get(cacheKey);
|
|
270
|
-
|
|
270
|
+
|
|
271
271
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
272
272
|
return cached.data;
|
|
273
273
|
}
|
|
274
|
-
|
|
274
|
+
|
|
275
275
|
const response = await fetch(url, options);
|
|
276
276
|
const data = await response.json();
|
|
277
|
-
|
|
277
|
+
|
|
278
278
|
cache.set(cacheKey, {
|
|
279
279
|
data,
|
|
280
280
|
timestamp: Date.now()
|
|
281
281
|
});
|
|
282
|
-
|
|
282
|
+
|
|
283
283
|
return data;
|
|
284
284
|
}
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
-
### 4. Batch
|
|
287
|
+
### 4. Batch operations when available
|
|
288
288
|
|
|
289
289
|
Use bulk endpoints when available to reduce the number of requests:
|
|
290
290
|
|
|
@@ -309,7 +309,7 @@ await fetch('/v2/categories', {
|
|
|
309
309
|
```
|
|
310
310
|
:::
|
|
311
311
|
|
|
312
|
-
### 5. Limit
|
|
312
|
+
### 5. Limit crypto refresh polling
|
|
313
313
|
|
|
314
314
|
Crypto refresh requests can be bursty if you poll too often. For `POST /v2/crypto/synced/{id}/refresh`:
|
|
315
315
|
|
|
@@ -319,14 +319,14 @@ Crypto refresh requests can be bursty if you poll too often. For `POST /v2/crypt
|
|
|
319
319
|
|
|
320
320
|
## Troubleshooting
|
|
321
321
|
|
|
322
|
-
### Common
|
|
322
|
+
### Common issues
|
|
323
323
|
|
|
324
324
|
**Issue**: Rate limits resetting unexpectedly
|
|
325
325
|
|
|
326
326
|
**Solution**: Rate limits are tracked per IP address. If you're behind a proxy or load balancer, multiple clients may share the same IP and exhaust the shared quota.
|
|
327
327
|
|
|
328
328
|
|
|
329
|
-
## Need
|
|
329
|
+
## Need help?
|
|
330
330
|
|
|
331
331
|
If you're experiencing rate limiting issues that can't be resolved through the techniques described above:
|
|
332
332
|
|
package/docs/using-with-ai.md
CHANGED
|
@@ -73,7 +73,7 @@ const token = process.env.LUNCH_MONEY_ACCESS_TOKEN;
|
|
|
73
73
|
> [!WARNING]
|
|
74
74
|
> Avoid pasting your access token directly into an AI chat window. Treat it like a password — store it in an environment variable, a `.env` file excluded from source control, or your operating system's secret store.
|
|
75
75
|
|
|
76
|
-
## Best
|
|
76
|
+
## Best practices
|
|
77
77
|
|
|
78
78
|
1. **Start with a test budget** — API changes are permanent. Use a test budget while you're learning so your real data stays safe. See the [Getting Started guide](/getting-started) for instructions.
|
|
79
79
|
|
|
@@ -97,7 +97,7 @@ If you want to experiment, here are a few low-risk starting points:
|
|
|
97
97
|
|
|
98
98
|
Start small, and use a test budget while you're learning.
|
|
99
99
|
|
|
100
|
-
## Need
|
|
100
|
+
## Need help?
|
|
101
101
|
|
|
102
102
|
- Join the <a href="https://lunchmoney.app/discord" target="_blank" rel="noopener noreferrer">Lunch Money Discord</a> and ask in the **#developer-api** channel
|
|
103
103
|
- [Email our developer advocate](mailto:jp@lunchmoney.app)
|
package/package.json
CHANGED
package/v2/docs/intro-to-v2.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# v2 API
|
|
1
|
+
# v2 API overview
|
|
2
2
|
|
|
3
3
|
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](./migration-guide.md).
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ All API requests require a Bearer token in the `Authorization` header:
|
|
|
10
10
|
Authorization: Bearer YOUR_ACCESS_TOKEN
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Get your access token from the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Lunch Money
|
|
13
|
+
Get your access token from the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Lunch Money Developers page</a>. See the [Getting started guide](/getting-started) for a step-by-step walkthrough including how to create a test budget.
|
|
14
14
|
|
|
15
15
|
## Base URL
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ Get your access token from the <a href="https://my.lunchmoney.app/developers" ta
|
|
|
18
18
|
https://api.lunchmoney.dev/v2
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
## Response
|
|
21
|
+
## Response shapes
|
|
22
22
|
|
|
23
23
|
The v2 API uses consistent HTTP status codes and response bodies across all endpoints.
|
|
24
24
|
|
|
@@ -39,7 +39,7 @@ All errors return an appropriate 4XX status code — the v2 API never returns an
|
|
|
39
39
|
| `401` | Unauthorized — missing or invalid Bearer token |
|
|
40
40
|
| `404` | Not Found — the requested object does not exist |
|
|
41
41
|
| `422` | Unprocessable Content — request is valid but cannot be fulfilled (e.g., deleting a category with dependents) |
|
|
42
|
-
| `429` | Too Many Requests — rate limit exceeded; response includes a `Retry-After` header. See the [Rate
|
|
42
|
+
| `429` | Too Many Requests — rate limit exceeded; response includes a `Retry-After` header. See the [Rate limiting guide](/rate-limits). |
|
|
43
43
|
|
|
44
44
|
All error responses share a consistent body format:
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ All error responses share a consistent body format:
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
> [!NOTE] Atomic
|
|
55
|
+
> [!NOTE] Atomic operations
|
|
56
56
|
> When a request fails, no part of it is applied to your data. For example, if one transaction in a bulk insert has a validation error, none of the transactions in that request are inserted. Fix the errors and retry — you don't need to worry about partial duplicates.
|
|
57
57
|
>
|
|
58
58
|
> 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.
|
|
@@ -61,7 +61,7 @@ All error responses share a consistent body format:
|
|
|
61
61
|
|
|
62
62
|
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.
|
|
63
63
|
|
|
64
|
-
## Naming
|
|
64
|
+
## Naming conventions
|
|
65
65
|
|
|
66
66
|
Object properties follow consistent rules across the entire API:
|
|
67
67
|
|
|
@@ -72,11 +72,11 @@ Object properties follow consistent rules across the entire API:
|
|
|
72
72
|
- Child objects of the same type are always in a `children` property
|
|
73
73
|
- All `id` fields are `integer` type (not `number`)
|
|
74
74
|
|
|
75
|
-
## Amounts,
|
|
75
|
+
## Amounts, balances & sign convention
|
|
76
76
|
|
|
77
|
-
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 &
|
|
77
|
+
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](/amounts-and-balances) for a full explanation of how these properties work, including the sign convention and multi-currency support.
|
|
78
78
|
|
|
79
|
-
## Hydrated vs.
|
|
79
|
+
## Hydrated vs. non-hydrated responses
|
|
80
80
|
|
|
81
81
|
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.
|
|
82
82
|
|
|
@@ -93,17 +93,17 @@ In v2, responses are **non-hydrated**: related objects are returned as IDs only.
|
|
|
93
93
|
|
|
94
94
|
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.
|
|
95
95
|
|
|
96
|
-
## Updating
|
|
96
|
+
## Updating objects (PUT)
|
|
97
97
|
|
|
98
98
|
Most objects in the v2 API are updatable via `PUT /endpoint/{id}`. The rules are consistent:
|
|
99
99
|
|
|
100
100
|
- Request bodies may contain any mix of **user-definable** properties (will be updated) and **system-defined** properties like `id` or `created_at` (accepted but ignored).
|
|
101
101
|
- Any unexpected property that is neither user-definable nor system-defined will fail validation and return `400`.
|
|
102
102
|
|
|
103
|
-
> [!WARNING] Overwrite
|
|
103
|
+
> [!WARNING] Overwrite behavior
|
|
104
104
|
> Complex properties — objects and arrays — are **replaced entirely** by whatever is in the request body. To add a tag to a transaction without removing existing tags, first fetch the transaction, add the new tag ID to the existing `tag_ids` array, then PUT the full updated array.
|
|
105
105
|
|
|
106
|
-
> [!TIP] Recommended
|
|
106
|
+
> [!TIP] Recommended pattern
|
|
107
107
|
> Because system-defined properties are tolerated in PUT requests, you can safely GET an object, modify the properties you want to change, and PUT the whole thing back without stripping system fields first.
|
|
108
108
|
|
|
109
109
|
## Versioning
|
|
@@ -99,7 +99,7 @@ One change worth noting: all `id` fields are now explicitly `integer` type (not
|
|
|
99
99
|
|
|
100
100
|
#### New Properties
|
|
101
101
|
|
|
102
|
-
- `debits_as_negative`: A user preference shown in the Lunch Money app. This no longer changes how transaction amounts are represented in the v2 API, but apps may use this setting to display amounts and balances in the way the user prefers. See the [Amounts &
|
|
102
|
+
- `debits_as_negative`: A user preference shown in the Lunch Money app. This no longer changes how transaction amounts are represented in the v2 API, but apps may use this setting to display amounts and balances in the way the user prefers. See the [Amounts & balances guide](/amounts-and-balances) for details.
|
|
103
103
|
|
|
104
104
|
<a href="./changelog-visual#user-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 differences</a>
|
|
105
105
|
|
|
@@ -386,7 +386,7 @@ GET /v2/transactions
|
|
|
386
386
|
> [!WARNING] Behavior Change
|
|
387
387
|
> In the v1 API the optional `debit_as_negative` query parameter could change how transaction amounts were represented. In the v2 API, transaction amount signs are fixed: positive values are debits and negative values are credits. Existing applications that assume positive values are credits need to be updated.
|
|
388
388
|
|
|
389
|
-
See the [Amounts &
|
|
389
|
+
See the [Amounts & balances guide](/amounts-and-balances) for the full sign convention and how `to_base` works for multi-currency accounts.
|
|
390
390
|
|
|
391
391
|
|
|
392
392
|
> [!TIP] Migration Tip
|
|
@@ -5,11 +5,10 @@ The Lunch Money API spec uses a modified version of SEMVER for its versioning me
|
|
|
5
5
|
- The minor version represents the number of main endpoints the current version of the spec supports. For example, a version of the API that supports the /me, /categories, and /transactions endpoints would have a minor version of 3.
|
|
6
6
|
- The revision number represents the number of updates since the last endpoint was added. For example, each time changes are made to one of the existing three APIs as described above, the revision number will be bumped.
|
|
7
7
|
|
|
8
|
-
## v2.11.1 -
|
|
8
|
+
## v2.11.1 - Sep 28, 2026
|
|
9
9
|
- Add `GET /me/account/settings` and `PUT /me/account/settings` for account-level settings
|
|
10
10
|
- Add `GET /me/user/settings` and `PUT /me/user/settings` for user-level display and formatting preferences
|
|
11
11
|
- Add `GET /me/user/account/settings` and `PUT /me/user/account/settings` for settings specific to a user and budgeting account
|
|
12
|
-
from `GET /me/account/settings` and `PUT /me/account/settings` to `GET /me/user/account/settings` and `PUT /me/user/account/settings`
|
|
13
12
|
- Add `include_pending_in_totals` to `GET /me/account/settings` and `PUT /me/account/settings`
|
|
14
13
|
- Add `default_manual_account_id` to `GET /me/user/account/settings` and `PUT /me/user/account/settings`
|
|
15
14
|
- Document `GET /budgets/settings` response schema publicly; change `budget_period_quantity` to integer
|