@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,517 @@
|
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
The Lunch Money API uses offset-based pagination for the `GET /transactions` endpoint to efficiently retrieve large sets of transaction data. This guide explains how pagination works and how to implement it in your application.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
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
|
+
|
|
9
|
+
> [!NOTE] Pagination Scope
|
|
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
|
+
|
|
12
|
+
## How Pagination Works
|
|
13
|
+
|
|
14
|
+
### Parameters
|
|
15
|
+
|
|
16
|
+
- **`limit`** (optional): The maximum number of transactions to return in a single request
|
|
17
|
+
- Default: `1000`
|
|
18
|
+
- Minimum: `1`
|
|
19
|
+
- Maximum: `2000`
|
|
20
|
+
- If omitted, defaults to 1000 transactions
|
|
21
|
+
|
|
22
|
+
- **`offset`** (optional): The number of transactions to skip before returning results
|
|
23
|
+
- Default: `0` (start from the beginning)
|
|
24
|
+
- Minimum: `0`
|
|
25
|
+
- Use this to fetch subsequent pages of results
|
|
26
|
+
|
|
27
|
+
### Response Property
|
|
28
|
+
|
|
29
|
+
- **`has_more`**: A boolean property in the response indicating whether more transactions are available
|
|
30
|
+
- `true`: More transactions match your filter criteria and are available on subsequent pages
|
|
31
|
+
- `false`: All matching transactions have been returned
|
|
32
|
+
|
|
33
|
+
### Basic Pagination Flow
|
|
34
|
+
|
|
35
|
+
1. Make your first request (optionally with `limit` specified, defaults to 1000)
|
|
36
|
+
2. Check the `has_more` property in the response
|
|
37
|
+
3. If `has_more` is `true`, make another request with `offset` set to your previous `offset + limit`
|
|
38
|
+
4. Repeat until `has_more` is `false`
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### Basic Pagination
|
|
43
|
+
|
|
44
|
+
Here's how to fetch transactions page by page:
|
|
45
|
+
|
|
46
|
+
:::tabs
|
|
47
|
+
@tab JavaScript/Node.js
|
|
48
|
+
```javascript
|
|
49
|
+
async function getAllTransactions(accessToken, limit = 1000) {
|
|
50
|
+
const allTransactions = [];
|
|
51
|
+
let offset = 0;
|
|
52
|
+
let hasMore = true;
|
|
53
|
+
|
|
54
|
+
while (hasMore) {
|
|
55
|
+
const url = `https://api.lunchmoney.dev/v2/transactions?limit=${limit}&offset=${offset}`;
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
headers: {
|
|
58
|
+
'Authorization': `Bearer ${accessToken}`
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
allTransactions.push(...data.transactions);
|
|
64
|
+
|
|
65
|
+
hasMore = data.has_more;
|
|
66
|
+
offset += limit;
|
|
67
|
+
|
|
68
|
+
console.log(`Fetched ${data.transactions.length} transactions (total: ${allTransactions.length})`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return allTransactions;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Usage
|
|
75
|
+
const transactions = await getAllTransactions('YOUR_ACCESS_TOKEN');
|
|
76
|
+
console.log(`Total transactions: ${transactions.length}`);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
@tab Python
|
|
80
|
+
```python
|
|
81
|
+
import requests
|
|
82
|
+
|
|
83
|
+
def get_all_transactions(access_token, limit=1000):
|
|
84
|
+
all_transactions = []
|
|
85
|
+
offset = 0
|
|
86
|
+
has_more = True
|
|
87
|
+
|
|
88
|
+
headers = {
|
|
89
|
+
'Authorization': f'Bearer {access_token}'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
while has_more:
|
|
93
|
+
url = f'https://api.lunchmoney.dev/v2/transactions?limit={limit}&offset={offset}'
|
|
94
|
+
response = requests.get(url, headers=headers)
|
|
95
|
+
data = response.json()
|
|
96
|
+
|
|
97
|
+
all_transactions.extend(data['transactions'])
|
|
98
|
+
has_more = data['has_more']
|
|
99
|
+
offset += limit
|
|
100
|
+
|
|
101
|
+
print(f"Fetched {len(data['transactions'])} transactions (total: {len(all_transactions)})")
|
|
102
|
+
|
|
103
|
+
return all_transactions
|
|
104
|
+
|
|
105
|
+
# Usage
|
|
106
|
+
transactions = get_all_transactions('YOUR_ACCESS_TOKEN')
|
|
107
|
+
print(f"Total transactions: {len(transactions)}")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
@tab cURL
|
|
111
|
+
```bash
|
|
112
|
+
#!/bin/bash
|
|
113
|
+
|
|
114
|
+
ACCESS_TOKEN="YOUR_ACCESS_TOKEN"
|
|
115
|
+
LIMIT=1000
|
|
116
|
+
OFFSET=0
|
|
117
|
+
HAS_MORE=true
|
|
118
|
+
ALL_TRANSACTIONS="[]"
|
|
119
|
+
|
|
120
|
+
while [ "$HAS_MORE" = "true" ]; do
|
|
121
|
+
echo "Fetching transactions with offset=$OFFSET, limit=$LIMIT..."
|
|
122
|
+
|
|
123
|
+
RESPONSE=$(curl -s -X GET \
|
|
124
|
+
"https://api.lunchmoney.dev/v2/transactions?limit=$LIMIT&offset=$OFFSET" \
|
|
125
|
+
-H "Authorization: Bearer $ACCESS_TOKEN")
|
|
126
|
+
|
|
127
|
+
TRANSACTIONS=$(echo $RESPONSE | jq -r '.transactions')
|
|
128
|
+
HAS_MORE=$(echo $RESPONSE | jq -r '.has_more')
|
|
129
|
+
|
|
130
|
+
# Merge transactions (simplified - in practice you'd want proper JSON merging)
|
|
131
|
+
COUNT=$(echo $TRANSACTIONS | jq 'length')
|
|
132
|
+
echo "Fetched $COUNT transactions"
|
|
133
|
+
|
|
134
|
+
OFFSET=$((OFFSET + LIMIT))
|
|
135
|
+
done
|
|
136
|
+
|
|
137
|
+
echo "All transactions fetched"
|
|
138
|
+
```
|
|
139
|
+
:::
|
|
140
|
+
|
|
141
|
+
### Pagination with Date Filters
|
|
142
|
+
|
|
143
|
+
Pagination works seamlessly with date range filters:
|
|
144
|
+
|
|
145
|
+
:::tabs
|
|
146
|
+
@tab JavaScript/Node.js
|
|
147
|
+
```javascript
|
|
148
|
+
async function getTransactionsByDateRange(accessToken, startDate, endDate) {
|
|
149
|
+
const allTransactions = [];
|
|
150
|
+
let offset = 0;
|
|
151
|
+
let hasMore = true;
|
|
152
|
+
const limit = 1000;
|
|
153
|
+
|
|
154
|
+
while (hasMore) {
|
|
155
|
+
const url = new URL('https://api.lunchmoney.dev/v2/transactions');
|
|
156
|
+
url.searchParams.set('start_date', startDate);
|
|
157
|
+
url.searchParams.set('end_date', endDate);
|
|
158
|
+
url.searchParams.set('limit', limit.toString());
|
|
159
|
+
url.searchParams.set('offset', offset.toString());
|
|
160
|
+
|
|
161
|
+
const response = await fetch(url, {
|
|
162
|
+
headers: {
|
|
163
|
+
'Authorization': `Bearer ${accessToken}`
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const data = await response.json();
|
|
168
|
+
allTransactions.push(...data.transactions);
|
|
169
|
+
|
|
170
|
+
hasMore = data.has_more;
|
|
171
|
+
offset += limit;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return allTransactions;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Usage: Get all transactions for January 2024
|
|
178
|
+
const janTransactions = await getTransactionsByDateRange(
|
|
179
|
+
'YOUR_ACCESS_TOKEN',
|
|
180
|
+
'2024-01-01',
|
|
181
|
+
'2024-01-31'
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
@tab Python
|
|
186
|
+
```python
|
|
187
|
+
import requests
|
|
188
|
+
from urllib.parse import urlencode
|
|
189
|
+
|
|
190
|
+
def get_transactions_by_date_range(access_token, start_date, end_date):
|
|
191
|
+
all_transactions = []
|
|
192
|
+
offset = 0
|
|
193
|
+
has_more = True
|
|
194
|
+
limit = 1000
|
|
195
|
+
|
|
196
|
+
headers = {'Authorization': f'Bearer {access_token}'}
|
|
197
|
+
|
|
198
|
+
while has_more:
|
|
199
|
+
params = {
|
|
200
|
+
'start_date': start_date,
|
|
201
|
+
'end_date': end_date,
|
|
202
|
+
'limit': limit,
|
|
203
|
+
'offset': offset
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
url = f"https://api.lunchmoney.dev/v2/transactions?{urlencode(params)}"
|
|
207
|
+
response = requests.get(url, headers=headers)
|
|
208
|
+
data = response.json()
|
|
209
|
+
|
|
210
|
+
all_transactions.extend(data['transactions'])
|
|
211
|
+
has_more = data['has_more']
|
|
212
|
+
offset += limit
|
|
213
|
+
|
|
214
|
+
return all_transactions
|
|
215
|
+
|
|
216
|
+
# Usage: Get all transactions for January 2024
|
|
217
|
+
jan_transactions = get_transactions_by_date_range(
|
|
218
|
+
'YOUR_ACCESS_TOKEN',
|
|
219
|
+
'2024-01-01',
|
|
220
|
+
'2024-01-31'
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
@tab cURL
|
|
225
|
+
```bash
|
|
226
|
+
#!/bin/bash
|
|
227
|
+
|
|
228
|
+
ACCESS_TOKEN="YOUR_ACCESS_TOKEN"
|
|
229
|
+
START_DATE="2024-01-01"
|
|
230
|
+
END_DATE="2024-01-31"
|
|
231
|
+
LIMIT=1000
|
|
232
|
+
OFFSET=0
|
|
233
|
+
HAS_MORE=true
|
|
234
|
+
|
|
235
|
+
while [ "$HAS_MORE" = "true" ]; do
|
|
236
|
+
RESPONSE=$(curl -s -X GET \
|
|
237
|
+
"https://api.lunchmoney.dev/v2/transactions?start_date=$START_DATE&end_date=$END_DATE&limit=$LIMIT&offset=$OFFSET" \
|
|
238
|
+
-H "Authorization: Bearer $ACCESS_TOKEN")
|
|
239
|
+
|
|
240
|
+
HAS_MORE=$(echo $RESPONSE | jq -r '.has_more')
|
|
241
|
+
COUNT=$(echo $RESPONSE | jq -r '.transactions | length')
|
|
242
|
+
echo "Fetched $COUNT transactions (offset=$OFFSET)"
|
|
243
|
+
|
|
244
|
+
OFFSET=$((OFFSET + LIMIT))
|
|
245
|
+
done
|
|
246
|
+
```
|
|
247
|
+
:::
|
|
248
|
+
|
|
249
|
+
### Handling the `has_more` Flag
|
|
250
|
+
|
|
251
|
+
Always check the `has_more` property to determine if you need to fetch more pages:
|
|
252
|
+
|
|
253
|
+
:::tabs
|
|
254
|
+
@tab JavaScript/Node.js
|
|
255
|
+
```javascript
|
|
256
|
+
async function fetchTransactionsPage(accessToken, limit = 1000, offset = 0) {
|
|
257
|
+
const url = `https://api.lunchmoney.dev/v2/transactions?limit=${limit}&offset=${offset}`;
|
|
258
|
+
const response = await fetch(url, {
|
|
259
|
+
headers: {
|
|
260
|
+
'Authorization': `Bearer ${accessToken}`
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const data = await response.json();
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
transactions: data.transactions,
|
|
268
|
+
hasMore: data.has_more,
|
|
269
|
+
nextOffset: data.has_more ? offset + limit : null
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Usage
|
|
274
|
+
const page1 = await fetchTransactionsPage('YOUR_ACCESS_TOKEN', 1000, 0);
|
|
275
|
+
console.log(`Page 1: ${page1.transactions.length} transactions`);
|
|
276
|
+
console.log(`Has more: ${page1.hasMore}`);
|
|
277
|
+
|
|
278
|
+
if (page1.hasMore) {
|
|
279
|
+
const page2 = await fetchTransactionsPage('YOUR_ACCESS_TOKEN', 1000, page1.nextOffset);
|
|
280
|
+
console.log(`Page 2: ${page2.transactions.length} transactions`);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
@tab Python
|
|
285
|
+
```python
|
|
286
|
+
import requests
|
|
287
|
+
|
|
288
|
+
def fetch_transactions_page(access_token, limit=1000, offset=0):
|
|
289
|
+
url = f'https://api.lunchmoney.dev/v2/transactions?limit={limit}&offset={offset}'
|
|
290
|
+
headers = {'Authorization': f'Bearer {access_token}'}
|
|
291
|
+
|
|
292
|
+
response = requests.get(url, headers=headers)
|
|
293
|
+
data = response.json()
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
'transactions': data['transactions'],
|
|
297
|
+
'has_more': data['has_more'],
|
|
298
|
+
'next_offset': offset + limit if data['has_more'] else None
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# Usage
|
|
302
|
+
page1 = fetch_transactions_page('YOUR_ACCESS_TOKEN', 1000, 0)
|
|
303
|
+
print(f"Page 1: {len(page1['transactions'])} transactions")
|
|
304
|
+
print(f"Has more: {page1['has_more']}")
|
|
305
|
+
|
|
306
|
+
if page1['has_more']:
|
|
307
|
+
page2 = fetch_transactions_page('YOUR_ACCESS_TOKEN', 1000, page1['next_offset'])
|
|
308
|
+
print(f"Page 2: {len(page2['transactions'])} transactions")
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
@tab cURL
|
|
312
|
+
```bash
|
|
313
|
+
ACCESS_TOKEN="YOUR_ACCESS_TOKEN"
|
|
314
|
+
LIMIT=1000
|
|
315
|
+
OFFSET=0
|
|
316
|
+
|
|
317
|
+
RESPONSE=$(curl -s -X GET \
|
|
318
|
+
"https://api.lunchmoney.dev/v2/transactions?limit=$LIMIT&offset=$OFFSET" \
|
|
319
|
+
-H "Authorization: Bearer $ACCESS_TOKEN")
|
|
320
|
+
|
|
321
|
+
HAS_MORE=$(echo $RESPONSE | jq -r '.has_more')
|
|
322
|
+
COUNT=$(echo $RESPONSE | jq -r '.transactions | length')
|
|
323
|
+
|
|
324
|
+
echo "Page 1: $COUNT transactions"
|
|
325
|
+
echo "Has more: $HAS_MORE"
|
|
326
|
+
|
|
327
|
+
if [ "$HAS_MORE" = "true" ]; then
|
|
328
|
+
OFFSET=$((OFFSET + LIMIT))
|
|
329
|
+
RESPONSE=$(curl -s -X GET \
|
|
330
|
+
"https://api.lunchmoney.dev/v2/transactions?limit=$LIMIT&offset=$OFFSET" \
|
|
331
|
+
-H "Authorization: Bearer $ACCESS_TOKEN")
|
|
332
|
+
COUNT=$(echo $RESPONSE | jq -r '.transactions | length')
|
|
333
|
+
echo "Page 2: $COUNT transactions"
|
|
334
|
+
fi
|
|
335
|
+
```
|
|
336
|
+
:::
|
|
337
|
+
|
|
338
|
+
## Best Practices
|
|
339
|
+
|
|
340
|
+
### 1. Use Appropriate Limit Values
|
|
341
|
+
|
|
342
|
+
Choose a `limit` value that balances performance and number of requests:
|
|
343
|
+
|
|
344
|
+
- **Small datasets (< 1000 transactions)**: Use the default limit of 1000 to fetch everything in one request
|
|
345
|
+
- **Large datasets**: Consider using smaller limits (e.g., 500) if you need to process transactions incrementally or show progress to users
|
|
346
|
+
- **Maximum efficiency**: Use the maximum limit of 2000 to minimize the number of requests, but be aware of response size and processing time
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
// Good: Use default limit for most cases
|
|
350
|
+
const response = await fetch('/v2/transactions?limit=1000');
|
|
351
|
+
|
|
352
|
+
// Also good: Use smaller limit for incremental processing
|
|
353
|
+
const response = await fetch('/v2/transactions?limit=500');
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### 2. Always Check `has_more`
|
|
357
|
+
|
|
358
|
+
Don't assume you've received all results. Always check the `has_more` property:
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
// ❌ Bad: Assumes one page is enough
|
|
362
|
+
const data = await fetch('/v2/transactions').then(r => r.json());
|
|
363
|
+
const transactions = data.transactions; // Might be incomplete!
|
|
364
|
+
|
|
365
|
+
// ✅ Good: Checks for more pages
|
|
366
|
+
let allTransactions = [];
|
|
367
|
+
let offset = 0;
|
|
368
|
+
let hasMore = true;
|
|
369
|
+
|
|
370
|
+
while (hasMore) {
|
|
371
|
+
const data = await fetch(`/v2/transactions?offset=${offset}`).then(r => r.json());
|
|
372
|
+
allTransactions.push(...data.transactions);
|
|
373
|
+
hasMore = data.has_more;
|
|
374
|
+
offset += 1000;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### 3. Combine with Date Ranges When Possible
|
|
379
|
+
|
|
380
|
+
If you know the date range you need, use `start_date` and `end_date` parameters to reduce the result set size:
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
// ✅ Good: Uses date range to limit results
|
|
384
|
+
const response = await fetch(
|
|
385
|
+
'/v2/transactions?start_date=2024-01-01&end_date=2024-01-31'
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// ❌ Less efficient: Fetches all transactions then filters client-side
|
|
389
|
+
const allTransactions = await fetchAllTransactions();
|
|
390
|
+
const janTransactions = allTransactions.filter(t =>
|
|
391
|
+
t.date >= '2024-01-01' && t.date <= '2024-01-31'
|
|
392
|
+
);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 4. Handle Edge Cases
|
|
396
|
+
|
|
397
|
+
- **Empty results**: The `transactions` array will be empty, and `has_more` will be `false`
|
|
398
|
+
- **Exact page boundary**: If the number of results is exactly divisible by your limit, `has_more` will be `false` on the last page
|
|
399
|
+
- **Offset beyond results**: If you set an offset larger than the total number of results, you'll get an empty array with `has_more: false`
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
async function safeFetchTransactions(accessToken, limit = 1000, offset = 0) {
|
|
403
|
+
const response = await fetch(
|
|
404
|
+
`https://api.lunchmoney.dev/v2/transactions?limit=${limit}&offset=${offset}`,
|
|
405
|
+
{
|
|
406
|
+
headers: { 'Authorization': `Bearer ${accessToken}` }
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
if (!response.ok) {
|
|
411
|
+
throw new Error(`API error: ${response.status}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const data = await response.json();
|
|
415
|
+
|
|
416
|
+
// Handle empty results
|
|
417
|
+
if (data.transactions.length === 0 && !data.has_more) {
|
|
418
|
+
console.log('No transactions found');
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return data;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### 5. Implement Rate Limit Awareness
|
|
427
|
+
|
|
428
|
+
When paginating through many pages, be mindful of rate limits. Consider adding delays between requests:
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
async function getAllTransactionsWithRateLimit(accessToken) {
|
|
432
|
+
const allTransactions = [];
|
|
433
|
+
let offset = 0;
|
|
434
|
+
let hasMore = true;
|
|
435
|
+
const limit = 1000;
|
|
436
|
+
|
|
437
|
+
while (hasMore) {
|
|
438
|
+
const data = await fetchTransactionsPage(accessToken, limit, offset);
|
|
439
|
+
allTransactions.push(...data.transactions);
|
|
440
|
+
hasMore = data.has_more;
|
|
441
|
+
offset += limit;
|
|
442
|
+
|
|
443
|
+
// Small delay to avoid hitting rate limits
|
|
444
|
+
if (hasMore) {
|
|
445
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return allTransactions;
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Troubleshooting
|
|
454
|
+
|
|
455
|
+
### Common Issues
|
|
456
|
+
|
|
457
|
+
**Issue**: Getting duplicate transactions across pages
|
|
458
|
+
|
|
459
|
+
**Solution**: Ensure you're incrementing `offset` by the same value as your `limit`. If you use `limit=1000`, increment `offset` by 1000 for each subsequent page.
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
// ❌ Wrong: Offset not incremented correctly
|
|
463
|
+
let offset = 0;
|
|
464
|
+
const page1 = await fetch(`/v2/transactions?limit=1000&offset=${offset}`);
|
|
465
|
+
offset += 500; // Wrong! Should be 1000
|
|
466
|
+
const page2 = await fetch(`/v2/transactions?limit=1000&offset=${offset}`);
|
|
467
|
+
|
|
468
|
+
// ✅ Correct: Offset matches limit
|
|
469
|
+
let offset = 0;
|
|
470
|
+
const page1 = await fetch(`/v2/transactions?limit=1000&offset=${offset}`);
|
|
471
|
+
offset += 1000; // Correct!
|
|
472
|
+
const page2 = await fetch(`/v2/transactions?limit=1000&offset=${offset}`);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Issue**: `has_more` is always `true` in a loop
|
|
476
|
+
|
|
477
|
+
**Solution**: Make sure you're updating both `hasMore` and `offset` in your loop condition:
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
// ❌ Wrong: Infinite loop
|
|
481
|
+
let offset = 0;
|
|
482
|
+
while (true) { // Never updates hasMore!
|
|
483
|
+
const data = await fetch(`/v2/transactions?offset=${offset}`);
|
|
484
|
+
// Missing: hasMore = data.has_more
|
|
485
|
+
offset += 1000;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ✅ Correct: Proper loop condition
|
|
489
|
+
let offset = 0;
|
|
490
|
+
let hasMore = true;
|
|
491
|
+
while (hasMore) {
|
|
492
|
+
const data = await fetch(`/v2/transactions?offset=${offset}`);
|
|
493
|
+
hasMore = data.has_more; // Update the condition
|
|
494
|
+
offset += 1000;
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Issue**: Getting fewer transactions than expected
|
|
499
|
+
|
|
500
|
+
**Solution**: Remember that filters (date ranges, account IDs, etc.) are applied before pagination. If you're filtering, the total number of transactions will be smaller:
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
// If you have 5000 total transactions but filter to a date range with only 200 transactions,
|
|
504
|
+
// you'll get 200 transactions total, not 5000
|
|
505
|
+
const data = await fetch('/v2/transactions?start_date=2024-01-01&end_date=2024-01-31');
|
|
506
|
+
// data.transactions.length might be 200, and has_more will be false
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Need Help?
|
|
510
|
+
|
|
511
|
+
If you're experiencing pagination issues that can't be resolved through the techniques described above:
|
|
512
|
+
|
|
513
|
+
- Review your pagination logic to ensure `offset` is being incremented correctly
|
|
514
|
+
- Verify that you're checking the `has_more` property on each response
|
|
515
|
+
- Consider using date range filters to reduce the result set size
|
|
516
|
+
- [Email our developer advocate](mailto:jp@lunchmoney.app) if you need assistance with pagination implementation
|
|
517
|
+
|