@lunch-money/developer-docs 2.11.0-preview.10
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/README.md +65 -0
- package/docs/README.md +16 -0
- package/docs/amounts-and-balances.md +85 -0
- package/docs/currencies.md +277 -0
- package/docs/getting-started.md +104 -0
- package/docs/introduction.md +63 -0
- package/docs/pagination.md +517 -0
- package/docs/rate-limiting.md +335 -0
- package/docs/using-with-ai.md +103 -0
- package/manifest.json +225 -0
- package/package.json +32 -0
- package/v1/_assets.md +188 -0
- package/v1/_budget.md +256 -0
- package/v1/_categories.md +407 -0
- package/v1/_changelog.md +102 -0
- package/v1/_crypto.md +105 -0
- package/v1/_plaid_accounts.md +117 -0
- package/v1/_recurring_expenses.md +117 -0
- package/v1/_recurring_items.md +240 -0
- package/v1/_tags.md +41 -0
- package/v1/_transactions.md +648 -0
- package/v1/_user.md +39 -0
- package/v2/docs/changelog-visual.html +506 -0
- package/v2/docs/intro-to-v2.md +117 -0
- package/v2/docs/migration-guide.md +1143 -0
- package/v2/docs/version-history.md +381 -0
- package/v2/images/CopyCategoriesAndTags.png +0 -0
- package/v2/images/DemoData.png +0 -0
- package/v2/images/PasteBearer.png +0 -0
- package/v2/images/RequestAccessToken.png +0 -0
- package/v2/images/TestRequest.png +0 -0
- package/v2/images/bearer.png +0 -0
- package/v2/images/green_budget.png +0 -0
- package/v2/images/new_budget.png +0 -0
- package/v2/images/server-dropdown.png +0 -0
- package/v2/spec/lunch-money-api-v2.yaml +11779 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Rate Limiting
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
Rate limiting is applied to all requests to the `/v1/` and `/v2/` endpoints and limits request from a single source to 100 requests per minute.
|
|
9
|
+
|
|
10
|
+
If you exceed this limit, you will receive a `429 Too Many Requests` response.
|
|
11
|
+
|
|
12
|
+
> [!NOTE] Rate Limit Scope
|
|
13
|
+
> Rate limiting is applied per IP address. Requests from the same IP address share the same rate limit quotas.
|
|
14
|
+
|
|
15
|
+
## Rate Limit Response
|
|
16
|
+
|
|
17
|
+
When you exceed a rate limit, the API returns a `429 Too Many Requests` response:
|
|
18
|
+
|
|
19
|
+
### HTTP Status Code
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
429 Too Many Requests
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Response Body
|
|
26
|
+
|
|
27
|
+
The response body follows the standard v2 error format:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"message": "Too Many Requests",
|
|
32
|
+
"errors": [
|
|
33
|
+
{
|
|
34
|
+
"errMsg": "Too many requests, please try again later."
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Response Headers
|
|
41
|
+
|
|
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
|
+
|
|
44
|
+
#### Standard Headers (RFC Draft 7)
|
|
45
|
+
|
|
46
|
+
The API includes standard rate limit headers in all responses:
|
|
47
|
+
|
|
48
|
+
- **`RateLimit-Limit`**: The maximum number of requests allowed per window
|
|
49
|
+
- **`RateLimit-Remaining`**: The number of requests remaining in the current window
|
|
50
|
+
- **`RateLimit-Reset`**: The time (in seconds since Unix epoch) when the rate limit window resets
|
|
51
|
+
|
|
52
|
+
Example headers:
|
|
53
|
+
```
|
|
54
|
+
RateLimit-Limit: 100
|
|
55
|
+
RateLimit-Remaining: 3
|
|
56
|
+
RateLimit-Reset: 1704067200
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Legacy Headers
|
|
60
|
+
|
|
61
|
+
For backward compatibility, the API also includes legacy `X-RateLimit-*` headers:
|
|
62
|
+
|
|
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
|
|
65
|
+
- **`X-RateLimit-Reset`**: The time (in seconds since Unix epoch) when the rate limit window resets
|
|
66
|
+
|
|
67
|
+
Example headers:
|
|
68
|
+
```
|
|
69
|
+
X-RateLimit-Limit: 100
|
|
70
|
+
X-RateLimit-Remaining: 3
|
|
71
|
+
X-RateLimit-Reset: 1704067200
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Retry-After Header
|
|
75
|
+
|
|
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
|
+
|
|
78
|
+
```
|
|
79
|
+
Retry-After: 45
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This value represents the time until the rate limit window resets (rounded up to the nearest second).
|
|
83
|
+
|
|
84
|
+
> [!TIP] Using Retry-After
|
|
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
|
+
|
|
87
|
+
## Monitoring Rate Limits
|
|
88
|
+
|
|
89
|
+
### Reading Rate Limit Headers
|
|
90
|
+
|
|
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
|
+
|
|
93
|
+
:::tabs
|
|
94
|
+
@tab JavaScript/Node.js
|
|
95
|
+
```javascript
|
|
96
|
+
async function makeRequest(url, options) {
|
|
97
|
+
const response = await fetch(url, options);
|
|
98
|
+
|
|
99
|
+
// Check rate limit status from headers
|
|
100
|
+
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
|
|
101
|
+
const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
|
|
102
|
+
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
|
|
103
|
+
|
|
104
|
+
if (remaining < 5) {
|
|
105
|
+
console.warn(`Rate limit warning: ${remaining}/${limit} requests remaining`);
|
|
106
|
+
const waitTime = resetTime - Math.floor(Date.now() / 1000);
|
|
107
|
+
if (waitTime > 0) {
|
|
108
|
+
console.log(`Rate limit resets in ${waitTime} seconds`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (response.status === 429) {
|
|
113
|
+
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
|
|
114
|
+
console.error(`Rate limited! Retry after ${retryAfter} seconds`);
|
|
115
|
+
throw new Error(`Rate limited: retry after ${retryAfter}s`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return response;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
@tab Python
|
|
123
|
+
```python
|
|
124
|
+
import requests
|
|
125
|
+
import time
|
|
126
|
+
|
|
127
|
+
def make_request(url, headers):
|
|
128
|
+
response = requests.get(url, headers=headers)
|
|
129
|
+
|
|
130
|
+
# Check rate limit status
|
|
131
|
+
remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
|
|
132
|
+
limit = int(response.headers.get('X-RateLimit-Limit', 0))
|
|
133
|
+
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
|
|
134
|
+
|
|
135
|
+
if remaining < 5:
|
|
136
|
+
print(f"Rate limit warning: {remaining}/{limit} requests remaining")
|
|
137
|
+
wait_time = reset_time - int(time.time())
|
|
138
|
+
if wait_time > 0:
|
|
139
|
+
print(f"Rate limit resets in {wait_time} seconds")
|
|
140
|
+
|
|
141
|
+
if response.status_code == 429:
|
|
142
|
+
retry_after = int(response.headers.get('Retry-After', 60))
|
|
143
|
+
print(f"Rate limited! Retry after {retry_after} seconds")
|
|
144
|
+
raise Exception(f"Rate limited: retry after {retry_after}s")
|
|
145
|
+
|
|
146
|
+
return response
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
@tab cURL
|
|
150
|
+
```bash
|
|
151
|
+
# Make a request and capture headers
|
|
152
|
+
response=$(curl -s -D /tmp/headers.txt -o /tmp/body.txt -w "%{http_code}" \
|
|
153
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
154
|
+
"https://api.lunchmoney.dev/v2/me")
|
|
155
|
+
|
|
156
|
+
# Extract rate limit headers
|
|
157
|
+
remaining=$(grep -i "^X-RateLimit-Remaining" /tmp/headers.txt | cut -d' ' -f2 | tr -d '\r')
|
|
158
|
+
limit=$(grep -i "^X-RateLimit-Limit" /tmp/headers.txt | cut -d' ' -f2 | tr -d '\r')
|
|
159
|
+
reset=$(grep -i "^X-RateLimit-Reset" /tmp/headers.txt | cut -d' ' -f2 | tr -d '\r')
|
|
160
|
+
|
|
161
|
+
echo "Status: $response"
|
|
162
|
+
echo "Rate Limit: $remaining/$limit remaining"
|
|
163
|
+
echo "Resets at: $reset"
|
|
164
|
+
|
|
165
|
+
# Check if rate limited
|
|
166
|
+
if [ "$response" = "429" ]; then
|
|
167
|
+
retry_after=$(grep -i "^Retry-After" /tmp/headers.txt | cut -d' ' -f2 | tr -d '\r')
|
|
168
|
+
echo "Rate limited! Wait $retry_after seconds before retrying"
|
|
169
|
+
fi
|
|
170
|
+
```
|
|
171
|
+
:::
|
|
172
|
+
|
|
173
|
+
### Implementing Exponential Backoff
|
|
174
|
+
|
|
175
|
+
When you receive a `429` response, implement exponential backoff with jitter to avoid overwhelming the API:
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
async function makeRequestWithRetry(url, options, maxRetries = 3) {
|
|
179
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
180
|
+
const response = await fetch(url, options);
|
|
181
|
+
|
|
182
|
+
if (response.status !== 429) {
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get retry-after header or use exponential backoff
|
|
187
|
+
const retryAfter = parseInt(response.headers.get('Retry-After') || '0');
|
|
188
|
+
const waitTime = retryAfter > 0
|
|
189
|
+
? retryAfter * 1000 // Convert seconds to milliseconds
|
|
190
|
+
: Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, 30000);
|
|
191
|
+
|
|
192
|
+
console.log(`Rate limited. Waiting ${waitTime}ms before retry ${attempt + 1}/${maxRetries}`);
|
|
193
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
throw new Error('Max retries exceeded due to rate limiting');
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Best Practices
|
|
201
|
+
|
|
202
|
+
### 1. Monitor Headers Proactively
|
|
203
|
+
|
|
204
|
+
Don't wait for a `429` response. Check rate limit headers on every request and adjust your request rate accordingly:
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// Pseudo-code example
|
|
208
|
+
if (remaining < threshold) {
|
|
209
|
+
const waitTime = (resetTime - currentTime) * 1000;
|
|
210
|
+
await sleep(waitTime);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 2. Implement Request Queuing
|
|
215
|
+
|
|
216
|
+
For applications that need to make many requests, implement a queue system that respects rate limits:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
class RateLimitedQueue {
|
|
220
|
+
constructor() {
|
|
221
|
+
this.queue = [];
|
|
222
|
+
this.remaining = 100; // Start with max
|
|
223
|
+
this.resetTime = Date.now() + (15 * 60 * 1000);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async enqueue(requestFn) {
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
this.queue.push({ requestFn, resolve, reject });
|
|
229
|
+
this.processQueue();
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async processQueue() {
|
|
234
|
+
if (this.queue.length === 0 || this.remaining <= 0) {
|
|
235
|
+
if (this.remaining <= 0 && this.resetTime > Date.now()) {
|
|
236
|
+
// Wait until reset
|
|
237
|
+
setTimeout(() => this.processQueue(), this.resetTime - Date.now());
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const { requestFn, resolve, reject } = this.queue.shift();
|
|
243
|
+
try {
|
|
244
|
+
const response = await requestFn();
|
|
245
|
+
|
|
246
|
+
// Update rate limit status from headers
|
|
247
|
+
this.remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
|
|
248
|
+
this.resetTime = parseInt(response.headers.get('X-RateLimit-Reset') || '0') * 1000;
|
|
249
|
+
|
|
250
|
+
resolve(response);
|
|
251
|
+
this.processQueue(); // Process next item
|
|
252
|
+
} catch (error) {
|
|
253
|
+
reject(error);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 3. Cache Responses When Possible
|
|
260
|
+
|
|
261
|
+
Reduce the number of API calls by caching responses locally:
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const cache = new Map();
|
|
265
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
266
|
+
|
|
267
|
+
async function getCachedRequest(url, options) {
|
|
268
|
+
const cacheKey = `${url}:${JSON.stringify(options)}`;
|
|
269
|
+
const cached = cache.get(cacheKey);
|
|
270
|
+
|
|
271
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
272
|
+
return cached.data;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const response = await fetch(url, options);
|
|
276
|
+
const data = await response.json();
|
|
277
|
+
|
|
278
|
+
cache.set(cacheKey, {
|
|
279
|
+
data,
|
|
280
|
+
timestamp: Date.now()
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return data;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 4. Batch Operations When Available
|
|
288
|
+
|
|
289
|
+
Use bulk endpoints when available to reduce the number of requests:
|
|
290
|
+
|
|
291
|
+
:::tabs
|
|
292
|
+
@tab Instead of multiple requests
|
|
293
|
+
```javascript
|
|
294
|
+
// ❌ Inefficient: 5 separate requests
|
|
295
|
+
for (const category_id of categories_in_transactions) {
|
|
296
|
+
await fetch(`/v2/categories/{category_id}`, {
|
|
297
|
+
method: 'GET',
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
@tab Use bulk endpoints
|
|
303
|
+
```javascript
|
|
304
|
+
// ✅ Efficient: Single request
|
|
305
|
+
await fetch('/v2/categories', {
|
|
306
|
+
method: 'GET',
|
|
307
|
+
});
|
|
308
|
+
// Process response and then map ids to categories
|
|
309
|
+
```
|
|
310
|
+
:::
|
|
311
|
+
|
|
312
|
+
### 5. Limit Crypto Refresh Polling
|
|
313
|
+
|
|
314
|
+
Crypto refresh requests can be bursty if you poll too often. For `POST /v2/crypto/synced/{id}/refresh`:
|
|
315
|
+
|
|
316
|
+
- Prefer event- or schedule-based refreshes over tight loops
|
|
317
|
+
- Space refresh calls for the same synced connection to avoid unnecessary retries
|
|
318
|
+
- Use `Retry-After` and remaining rate limit headers to back off quickly when approaching the limit
|
|
319
|
+
|
|
320
|
+
## Troubleshooting
|
|
321
|
+
|
|
322
|
+
### Common Issues
|
|
323
|
+
|
|
324
|
+
**Issue**: Rate limits resetting unexpectedly
|
|
325
|
+
|
|
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
|
+
|
|
328
|
+
|
|
329
|
+
## Need Help?
|
|
330
|
+
|
|
331
|
+
If you're experiencing rate limiting issues that can't be resolved through the techniques described above:
|
|
332
|
+
|
|
333
|
+
- Review your application's request patterns and implement caching where appropriate
|
|
334
|
+
- Consider implementing request queuing to better manage your API usage
|
|
335
|
+
- [Email our developer advocate](mailto:jp@lunchmoney.app) if you have a legitimate need for higher rate limits
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Using the API with AI
|
|
2
|
+
|
|
3
|
+
AI tools can connect to your Lunch Money data in two ways: through an MCP server that lets you ask natural-language questions in a chat interface, or through an AI coding assistant that helps you write scripts against the API. This page explains both approaches and how to handle your access token safely in each.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> Lunch Money never shares your data with AI automatically. AI only gets access to your data when you choose to connect it.
|
|
7
|
+
|
|
8
|
+
## Option 1: Connect an AI assistant via MCP
|
|
9
|
+
|
|
10
|
+
MCP (Model Context Protocol) is a standard that lets AI assistants use external tools. A Lunch Money MCP server acts as a bridge between your AI chat app and the Lunch Money API, letting you ask questions like:
|
|
11
|
+
|
|
12
|
+
- "How much did I spend on restaurants last month?"
|
|
13
|
+
- "Show me my largest uncategorized transactions."
|
|
14
|
+
- "Which categories are trending above normal this month?"
|
|
15
|
+
|
|
16
|
+
The <a href="https://lunchmoney.app/developers#mcp-servers" target="_blank" rel="noopener noreferrer">Lunch Money developers page</a> lists community-built MCP servers. Pick one whose setup instructions and permission model you're comfortable with, and follow its documentation carefully. If you're not sure where to start, <a href="https://github.com/akutishevsky/lunchmoney-mcp#usage" target="_blank" rel="noopener noreferrer">lunchmoney-mcp</a> is a popular choice — it works well with Claude Desktop (including a one-click `.mcpb` bundle install), covers the full v2 API, and has straightforward setup instructions.
|
|
17
|
+
|
|
18
|
+
With MCP, your access token usually lives in a local configuration file on your computer — not in the chat window — which is safer than pasting it directly into a conversation.
|
|
19
|
+
|
|
20
|
+
> [!WARNING]
|
|
21
|
+
> When an AI assistant answers a question using MCP, the relevant Lunch Money data may be sent through the AI provider to generate the response. Review the MCP server's documentation and your AI provider's data-handling policy before connecting.
|
|
22
|
+
|
|
23
|
+
## Option 2: Use an AI coding assistant to build your own tools
|
|
24
|
+
|
|
25
|
+
If you're comfortable writing code, another approach is to point an AI coding assistant at the [Lunch Money OpenAPI spec](/v2/openapi) and ask it to help you build exactly what you need. For example:
|
|
26
|
+
|
|
27
|
+
- "Using the Lunch Money v2 OpenAPI spec, write a script that lists all uncategorized transactions."
|
|
28
|
+
- "Create a report of transactions over $500 from the last 90 days."
|
|
29
|
+
- "Help me make a chart of monthly spending by category."
|
|
30
|
+
|
|
31
|
+
This is a useful middle path: the AI helps write code against the public API docs, while your actual financial data stays between your computer and Lunch Money unless you explicitly share it.
|
|
32
|
+
|
|
33
|
+
> [!TIP]
|
|
34
|
+
> If an AI coding assistant is only helping you write code from the public API docs, it may not need to see any of your financial data at all. Keep data out of the chat and run the resulting scripts locally.
|
|
35
|
+
|
|
36
|
+
### Keeping your token out of the conversation
|
|
37
|
+
|
|
38
|
+
The safest way to supply your access token to a local script is to keep it out of the prompt entirely — let the program read it at runtime instead:
|
|
39
|
+
|
|
40
|
+
:::tabs
|
|
41
|
+
@tab bash / curl
|
|
42
|
+
```bash
|
|
43
|
+
# Set once in your shell or add to your shell config file
|
|
44
|
+
export LUNCH_MONEY_ACCESS_TOKEN="your_token_here"
|
|
45
|
+
|
|
46
|
+
# Scripts and curl commands read it automatically
|
|
47
|
+
curl https://api.lunchmoney.dev/v2/me \
|
|
48
|
+
-H "Authorization: Bearer $LUNCH_MONEY_ACCESS_TOKEN"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
@tab Python
|
|
52
|
+
```python
|
|
53
|
+
# .env file (add to .gitignore — do not commit to source control)
|
|
54
|
+
# LUNCH_MONEY_ACCESS_TOKEN=your_token_here
|
|
55
|
+
|
|
56
|
+
import os
|
|
57
|
+
from dotenv import load_dotenv
|
|
58
|
+
|
|
59
|
+
load_dotenv()
|
|
60
|
+
token = os.environ["LUNCH_MONEY_ACCESS_TOKEN"]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
@tab Node.js
|
|
64
|
+
```javascript
|
|
65
|
+
// .env file (add to .gitignore — do not commit to source control)
|
|
66
|
+
// LUNCH_MONEY_ACCESS_TOKEN=your_token_here
|
|
67
|
+
|
|
68
|
+
import 'dotenv/config';
|
|
69
|
+
const token = process.env.LUNCH_MONEY_ACCESS_TOKEN;
|
|
70
|
+
```
|
|
71
|
+
:::
|
|
72
|
+
|
|
73
|
+
> [!WARNING]
|
|
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
|
+
|
|
76
|
+
## Best Practices
|
|
77
|
+
|
|
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
|
+
|
|
80
|
+
2. **Prefer read-only workflows** — A workflow that only reads data is lower risk than one that can create, update, or delete records.
|
|
81
|
+
|
|
82
|
+
3. **Give tokens descriptive names** — Label each token clearly on the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Developers page</a> so you can identify and revoke them easily later.
|
|
83
|
+
|
|
84
|
+
4. **Create one token per tool** — That way you can revoke a specific integration without affecting others. You cannot unsend data that has already been shared, but you can always cut off future access.
|
|
85
|
+
|
|
86
|
+
5. **Understand what you're sharing** — The more data you send to a third-party service, the more carefully you should review that service's privacy and data-retention policies.
|
|
87
|
+
|
|
88
|
+
## Starter ideas
|
|
89
|
+
|
|
90
|
+
If you want to experiment, here are a few low-risk starting points:
|
|
91
|
+
|
|
92
|
+
1. Ask an MCP-connected assistant to summarize your spending by category for the current month.
|
|
93
|
+
2. Use an AI coding assistant to help write a script that lists all uncategorized transactions.
|
|
94
|
+
3. Generate a simple monthly cash-flow report from your API data.
|
|
95
|
+
4. Ask for patterns in merchant names that might deserve new rules or categories.
|
|
96
|
+
5. Build a reusable "money questions" notebook with prompts you revisit each month.
|
|
97
|
+
|
|
98
|
+
Start small, and use a test budget while you're learning.
|
|
99
|
+
|
|
100
|
+
## Need Help?
|
|
101
|
+
|
|
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
|
+
- [Email our developer advocate](mailto:jp@lunchmoney.app)
|
package/manifest.json
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Doc routing manifest (plans/remove_submodule.md, Phase 2). Pages, redirects, and sidebar for all documentation routes. 'file' paths are relative to the @lunch-money/developer-docs package root. Page 'aliases' are 301 redirects to the page path; 'redirects' entries carry their own status.",
|
|
3
|
+
"pages": [
|
|
4
|
+
{
|
|
5
|
+
"path": "/introduction",
|
|
6
|
+
"file": "docs/introduction.md",
|
|
7
|
+
"title": "Introduction",
|
|
8
|
+
"section": "INTRODUCTION",
|
|
9
|
+
"type": "markdown",
|
|
10
|
+
"aliases": ["/v2/introduction"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "/getting-started",
|
|
14
|
+
"file": "docs/getting-started.md",
|
|
15
|
+
"title": "Getting Started",
|
|
16
|
+
"section": "GUIDES",
|
|
17
|
+
"type": "markdown",
|
|
18
|
+
"aliases": ["/v2/getting-started"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "/pagination",
|
|
22
|
+
"file": "docs/pagination.md",
|
|
23
|
+
"title": "Pagination",
|
|
24
|
+
"section": "GUIDES",
|
|
25
|
+
"type": "markdown",
|
|
26
|
+
"aliases": ["/v2/pagination"]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "/rate-limits",
|
|
30
|
+
"file": "docs/rate-limiting.md",
|
|
31
|
+
"title": "Rate Limiting",
|
|
32
|
+
"section": "GUIDES",
|
|
33
|
+
"type": "markdown",
|
|
34
|
+
"aliases": ["/v2/rate-limits"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"path": "/currencies",
|
|
38
|
+
"file": "docs/currencies.md",
|
|
39
|
+
"title": "Supported Currencies",
|
|
40
|
+
"section": "GUIDES",
|
|
41
|
+
"type": "markdown",
|
|
42
|
+
"aliases": ["/v2/currencies"]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "/amounts-and-balances",
|
|
46
|
+
"file": "docs/amounts-and-balances.md",
|
|
47
|
+
"title": "Amounts & Balances",
|
|
48
|
+
"section": "GUIDES",
|
|
49
|
+
"type": "markdown",
|
|
50
|
+
"aliases": ["/v2/amounts-and-balances"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"path": "/using-with-ai",
|
|
54
|
+
"file": "docs/using-with-ai.md",
|
|
55
|
+
"title": "Using the API with AI",
|
|
56
|
+
"section": "GUIDES",
|
|
57
|
+
"type": "markdown",
|
|
58
|
+
"aliases": ["/v2/using-with-ai"]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"path": "/v2/overview",
|
|
62
|
+
"file": "v2/docs/intro-to-v2.md",
|
|
63
|
+
"title": "v2 API Overview",
|
|
64
|
+
"section": "REFERENCE",
|
|
65
|
+
"type": "markdown"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"path": "/v2/changelog",
|
|
69
|
+
"file": "v2/docs/version-history.md",
|
|
70
|
+
"title": "v2 API Changelog",
|
|
71
|
+
"section": "REFERENCE",
|
|
72
|
+
"type": "markdown",
|
|
73
|
+
"aliases": ["/v2/version-history"]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"path": "/v2/migration-guide",
|
|
77
|
+
"file": "v2/docs/migration-guide.md",
|
|
78
|
+
"title": "Migrating v1 to v2",
|
|
79
|
+
"section": "LEGACY APIs",
|
|
80
|
+
"type": "markdown"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"path": "/v2/changelog-visual",
|
|
84
|
+
"file": "v2/docs/changelog-visual.html",
|
|
85
|
+
"title": "v2 API Visual Changelog",
|
|
86
|
+
"section": "REFERENCE",
|
|
87
|
+
"type": "html"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"path": "/v1/changelog",
|
|
91
|
+
"file": "v1/_changelog.md",
|
|
92
|
+
"title": "v1 Changelog",
|
|
93
|
+
"section": "V1 API REFERENCE",
|
|
94
|
+
"type": "v1-slate",
|
|
95
|
+
"aliases": ["/v1/reference/changelog"]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"path": "/v1/user",
|
|
99
|
+
"file": "v1/_user.md",
|
|
100
|
+
"title": "v1 User",
|
|
101
|
+
"section": "V1 API REFERENCE",
|
|
102
|
+
"type": "v1-slate",
|
|
103
|
+
"aliases": ["/v1/reference/user"]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"path": "/v1/categories",
|
|
107
|
+
"file": "v1/_categories.md",
|
|
108
|
+
"title": "v1 Categories",
|
|
109
|
+
"section": "V1 API REFERENCE",
|
|
110
|
+
"type": "v1-slate",
|
|
111
|
+
"aliases": ["/v1/reference/categories"]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"path": "/v1/tags",
|
|
115
|
+
"file": "v1/_tags.md",
|
|
116
|
+
"title": "v1 Tags",
|
|
117
|
+
"section": "V1 API REFERENCE",
|
|
118
|
+
"type": "v1-slate",
|
|
119
|
+
"aliases": ["/v1/reference/tags"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"path": "/v1/transactions",
|
|
123
|
+
"file": "v1/_transactions.md",
|
|
124
|
+
"title": "v1 Transactions",
|
|
125
|
+
"section": "V1 API REFERENCE",
|
|
126
|
+
"type": "v1-slate",
|
|
127
|
+
"aliases": ["/v1/reference/transactions"]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"path": "/v1/recurring-expenses",
|
|
131
|
+
"file": "v1/_recurring_expenses.md",
|
|
132
|
+
"title": "v1 Recurring Expenses",
|
|
133
|
+
"section": "V1 API REFERENCE",
|
|
134
|
+
"type": "v1-slate",
|
|
135
|
+
"aliases": ["/v1/reference/recurring-expenses"]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"path": "/v1/recurring-items",
|
|
139
|
+
"file": "v1/_recurring_items.md",
|
|
140
|
+
"title": "v1 Recurring Items",
|
|
141
|
+
"section": "V1 API REFERENCE",
|
|
142
|
+
"type": "v1-slate",
|
|
143
|
+
"aliases": ["/v1/reference/recurring-items"]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"path": "/v1/budget",
|
|
147
|
+
"file": "v1/_budget.md",
|
|
148
|
+
"title": "v1 Budget",
|
|
149
|
+
"section": "V1 API REFERENCE",
|
|
150
|
+
"type": "v1-slate",
|
|
151
|
+
"aliases": ["/v1/reference/budget"]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"path": "/v1/assets",
|
|
155
|
+
"file": "v1/_assets.md",
|
|
156
|
+
"title": "v1 Assets",
|
|
157
|
+
"section": "V1 API REFERENCE",
|
|
158
|
+
"type": "v1-slate",
|
|
159
|
+
"aliases": ["/v1/reference/assets"]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"path": "/v1/plaid-accounts",
|
|
163
|
+
"file": "v1/_plaid_accounts.md",
|
|
164
|
+
"title": "v1 Plaid Accounts",
|
|
165
|
+
"section": "V1 API REFERENCE",
|
|
166
|
+
"type": "v1-slate",
|
|
167
|
+
"aliases": ["/v1/reference/plaid-accounts"]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"path": "/v1/crypto",
|
|
171
|
+
"file": "v1/_crypto.md",
|
|
172
|
+
"title": "v1 Crypto",
|
|
173
|
+
"section": "V1 API REFERENCE",
|
|
174
|
+
"type": "v1-slate",
|
|
175
|
+
"aliases": ["/v1/reference/crypto"]
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
"redirects": [
|
|
179
|
+
{ "from": "/", "to": "/introduction", "status": 302 },
|
|
180
|
+
{ "from": "/v2", "to": "/introduction", "status": 302 },
|
|
181
|
+
{ "from": "/v2/", "to": "/introduction", "status": 302 },
|
|
182
|
+
{ "from": "/v1", "to": "/v1/user", "status": 302 },
|
|
183
|
+
{ "from": "/v1/docs", "to": "/v1/user", "status": 301 }
|
|
184
|
+
],
|
|
185
|
+
"sidebar": {
|
|
186
|
+
"docs": [
|
|
187
|
+
{ "label": "Introduction", "path": "/introduction" },
|
|
188
|
+
{ "section": "GUIDES", "items": [
|
|
189
|
+
{ "label": "Getting Started", "path": "/getting-started" },
|
|
190
|
+
{ "label": "Amounts & Balances", "path": "/amounts-and-balances" },
|
|
191
|
+
{ "label": "Pagination", "path": "/pagination" },
|
|
192
|
+
{ "label": "Rate Limiting", "path": "/rate-limits" },
|
|
193
|
+
{ "label": "Supported Currencies", "path": "/currencies" },
|
|
194
|
+
{ "label": "Using the API with AI", "path": "/using-with-ai" }
|
|
195
|
+
]},
|
|
196
|
+
{ "section": "REFERENCE", "items": [
|
|
197
|
+
{ "label": "v2 API Overview", "path": "/v2/overview" },
|
|
198
|
+
{ "label": "v2 API Reference", "path": "/v2/docs", "external": true },
|
|
199
|
+
{ "label": "v2 API Changelog", "path": "/v2/changelog" }
|
|
200
|
+
]},
|
|
201
|
+
{ "section": "LEGACY APIs", "items": [
|
|
202
|
+
{ "label": "Migrating v1 → v2", "path": "/v2/migration-guide" },
|
|
203
|
+
{ "label": "v1 API Reference", "path": "/v1/user", "external": true },
|
|
204
|
+
{ "label": "v1 API Changelog", "path": "/v1/changelog" },
|
|
205
|
+
{ "label": "Legacy v1 docs", "path": "https://v1.lunchmoney.dev/", "external": true }
|
|
206
|
+
]}
|
|
207
|
+
],
|
|
208
|
+
"v1": [
|
|
209
|
+
{ "section": "V1 API REFERENCE", "items": [
|
|
210
|
+
{ "label": "User", "path": "/v1/user" },
|
|
211
|
+
{ "label": "Categories", "path": "/v1/categories" },
|
|
212
|
+
{ "label": "Tags", "path": "/v1/tags" },
|
|
213
|
+
{ "label": "Transactions", "path": "/v1/transactions" },
|
|
214
|
+
{ "label": "Recurring Expenses", "path": "/v1/recurring-expenses" },
|
|
215
|
+
{ "label": "Recurring Items", "path": "/v1/recurring-items" },
|
|
216
|
+
{ "label": "Budget", "path": "/v1/budget" },
|
|
217
|
+
{ "label": "Assets", "path": "/v1/assets" },
|
|
218
|
+
{ "label": "Plaid Accounts", "path": "/v1/plaid-accounts" },
|
|
219
|
+
{ "label": "Crypto", "path": "/v1/crypto" }
|
|
220
|
+
]},
|
|
221
|
+
{ "label": "v1 API Changelog", "path": "/v1/changelog" },
|
|
222
|
+
{ "label": "Legacy v1 docs", "path": "https://v1.lunchmoney.dev/", "external": true }
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
}
|