@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,1143 @@
|
|
|
1
|
+
# Migrating from API v1 to v2
|
|
2
|
+
|
|
3
|
+
The v2 API was built with direct input from the Lunch Money developer community. Features and design decisions were shaped by feedback including:
|
|
4
|
+
|
|
5
|
+
- <a href="https://lunchmoney.canny.io/developer-api/p/openapi-spec" target="_blank" rel="noopener noreferrer">OpenAPI Spec</a>
|
|
6
|
+
- <a href="https://lunchmoney.canny.io/developer-api/p/delete-transaction-endpoint" target="_blank" rel="noopener noreferrer">Delete transaction endpoint</a>
|
|
7
|
+
- <a href="https://feedback.lunchmoney.app/developer-api/p/expose-api-for-the-v2-budget-feature" target="_blank" rel="noopener noreferrer">Expose API for the v2 Budget feature</a>
|
|
8
|
+
- <a href="https://lunchmoney.canny.io/developer-api/p/create-tags-endpoint" target="_blank" rel="noopener noreferrer">Create Tags Endpoint</a>
|
|
9
|
+
- <a href="https://lunchmoney.canny.io/developer-api/p/allow-partial-transaction-insertions-or-upserts" target="_blank" rel="noopener noreferrer">Insert Transactions even when there are some duplicates</a>
|
|
10
|
+
- <a href="https://lunchmoney.canny.io/developer-api/p/improve-error-messaging-in-api" target="_blank" rel="noopener noreferrer">Improve error messaging in API</a>
|
|
11
|
+
- <a href="https://lunchmoney.canny.io/developer-api/p/created-at-updated-at" target="_blank" rel="noopener noreferrer">Created At + Updated At for all objects</a>
|
|
12
|
+
- <a href="https://feedback.lunchmoney.app/developer-api/p/special-characters-in-names-arent-handled-well-by-some-apis" target="_blank" rel="noopener noreferrer">Special Characters in names aren't handled well by some APIs</a>
|
|
13
|
+
- <a href="https://feedback.lunchmoney.app/developer-api/p/add-last-updated-after-filter-to-transactions-api" target="_blank" rel="noopener noreferrer">Add filters for created_at and updated_at to GET Transactions API</a>
|
|
14
|
+
- <a href="https://feedback.lunchmoney.app/developer-api/p/account-metadata-hash" target="_blank" rel="noopener noreferrer">Custom Metadata on Manual Accounts</a>
|
|
15
|
+
|
|
16
|
+
This guide covers what changed from v1 to v2 and how to update your integration. For a general introduction to v2 concepts — authentication, response shapes, naming conventions, error handling, and hydration — see the [v2 API Overview](/v2/overview).
|
|
17
|
+
|
|
18
|
+
## Getting Started
|
|
19
|
+
|
|
20
|
+
Before you begin:
|
|
21
|
+
|
|
22
|
+
1. Review the [v2 API Overview](/v2/overview) for general concepts
|
|
23
|
+
2. Test your migration in a development environment
|
|
24
|
+
3. Keep your v1 integration working as a fallback until migration is complete
|
|
25
|
+
|
|
26
|
+
> [!TIP] Migration Strategy
|
|
27
|
+
> Start with read operations first, then gradually migrate write operations. This allows you to test v2 responses while still relying on v1 for writes during the transition.
|
|
28
|
+
|
|
29
|
+
## Base URL Changes
|
|
30
|
+
|
|
31
|
+
The base URL for all API requests changes from:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
https://api.lunchmoney.dev/v1/
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
to:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
https://api.lunchmoney.dev/v2
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## General Changes
|
|
44
|
+
|
|
45
|
+
### Error Handling
|
|
46
|
+
|
|
47
|
+
> [!WARNING] Breaking Change
|
|
48
|
+
> The v1 API often returns `200 OK` even when the request fails — the error is in the response body. Update your error handling to check HTTP status codes instead of inspecting 2XX response bodies.
|
|
49
|
+
|
|
50
|
+
The v2 API returns consistent 4XX status codes for errors and validates all requests strictly, including query parameters and request body properties that v1 would silently ignore. See [Error Responses](/v2/overview#error-responses) in the v2 API Overview for the full status code table and error body format.
|
|
51
|
+
|
|
52
|
+
### Response Codes
|
|
53
|
+
|
|
54
|
+
Two status code changes affect all endpoints:
|
|
55
|
+
|
|
56
|
+
- **POST requests** now return `201 Created` (was `200 OK`)
|
|
57
|
+
- **DELETE requests** now return `204 No Content` with no response body (was `200 OK` with `true`)
|
|
58
|
+
|
|
59
|
+
See [Response Shapes](/v2/overview#response-shapes) in the v2 API Overview for the complete table.
|
|
60
|
+
|
|
61
|
+
### Updating Objects
|
|
62
|
+
|
|
63
|
+
> [!WARNING] Property Replacement Behavior
|
|
64
|
+
> Complex properties — objects and arrays — are **replaced entirely** by what you send in the PUT body. To add a tag without removing existing ones, fetch the object first, update the array, then PUT the full array back.
|
|
65
|
+
|
|
66
|
+
The v2 PUT pattern (GET → modify → PUT the full object) and all other PUT rules are covered in [Updating Objects](/v2/overview#updating-objects-put) in the v2 API Overview.
|
|
67
|
+
|
|
68
|
+
### Property Naming Conventions
|
|
69
|
+
|
|
70
|
+
Property names are standardized across all v2 objects. The specific renames are listed in the endpoint sections below. See [Naming Conventions](/v2/overview#naming-conventions) in the v2 API Overview for the general rules.
|
|
71
|
+
|
|
72
|
+
One change worth noting: all `id` fields are now explicitly `integer` type (not `number`). This should not affect most apps.
|
|
73
|
+
|
|
74
|
+
## Object and Endpoint Specific Changes
|
|
75
|
+
|
|
76
|
+
### User Object
|
|
77
|
+
|
|
78
|
+
#### Property Renames
|
|
79
|
+
|
|
80
|
+
:::tabs
|
|
81
|
+
@tab v1
|
|
82
|
+
```javascript
|
|
83
|
+
{
|
|
84
|
+
"user_id": 123,
|
|
85
|
+
"user_name": "John Doe",
|
|
86
|
+
"user_email": "john@example.com"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
@tab v2
|
|
91
|
+
```javascript
|
|
92
|
+
{
|
|
93
|
+
"id": 123,
|
|
94
|
+
"name": "John Doe",
|
|
95
|
+
"email": "john@example.com"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
:::
|
|
99
|
+
|
|
100
|
+
#### New Properties
|
|
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 & Balances guide](/v2/amounts-and-balances) for details.
|
|
103
|
+
|
|
104
|
+
<a href="./changelog-visual#user-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 differences</a>
|
|
105
|
+
|
|
106
|
+
<a href="/v2/docs#model/userObject">View v2 User Object Docs</a>
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
**Migration Tips**:
|
|
110
|
+
- Update all references to `user_id`, `user_name`, and `user_email` in your code to use the new property names.
|
|
111
|
+
- Do not use the user's `debits_as_negative` setting to interpret v2 transaction amounts. In the v2 API, positive `amount` values are always debits and negative `amount` values are always credits.
|
|
112
|
+
|
|
113
|
+
### Categories
|
|
114
|
+
|
|
115
|
+
#### Endpoint Changes
|
|
116
|
+
|
|
117
|
+
#### Creating Categories
|
|
118
|
+
|
|
119
|
+
v1 had separate endpoints for categories and category groups. v2 uses a single endpoint:
|
|
120
|
+
|
|
121
|
+
:::tabs
|
|
122
|
+
@tab v1 - Create category
|
|
123
|
+
```javascript
|
|
124
|
+
POST /v1/categories
|
|
125
|
+
{ "name": "Groceries", ... }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
@tab v1 - Create category group
|
|
129
|
+
```javascript
|
|
130
|
+
POST /v1/categories/group
|
|
131
|
+
{ "name": "Food & Drink", "category_ids": [...], ... }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
@tab v2 - Single endpoint
|
|
135
|
+
```javascript
|
|
136
|
+
// Regular category
|
|
137
|
+
POST /v2/categories
|
|
138
|
+
{ "name": "Groceries", ... }
|
|
139
|
+
|
|
140
|
+
// Category group
|
|
141
|
+
POST /v2/categories
|
|
142
|
+
{ "name": "Food & Drink", "is_group": true, "children": [...] }
|
|
143
|
+
```
|
|
144
|
+
:::
|
|
145
|
+
|
|
146
|
+
#### Adding to Category Groups
|
|
147
|
+
|
|
148
|
+
v1 had a unique endpoint for adding children to an existing category group.
|
|
149
|
+
v2 allows children to be modified via the `PUT /categories` endpoint.
|
|
150
|
+
|
|
151
|
+
:::tabs
|
|
152
|
+
@tab v1
|
|
153
|
+
```javascript
|
|
154
|
+
POST /v1/categories/group/:group_id/add
|
|
155
|
+
{ "category_ids": [1, 2, 3] }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
@tab v2
|
|
159
|
+
```javascript
|
|
160
|
+
PUT /v2/categories/:id
|
|
161
|
+
{ "children": [1, 2, 3] } // Replaces all children
|
|
162
|
+
```
|
|
163
|
+
:::
|
|
164
|
+
|
|
165
|
+
#### Deleting Categories
|
|
166
|
+
When attempting to delete a category, both the v1 and v2 APIs will return a `dependents` object if other Lunch Money elements (ie: transactions, rules, etc) depend on the category. Both versions also provide a way to force delete the category when this happens but the syntax is slightly different:
|
|
167
|
+
|
|
168
|
+
:::tabs
|
|
169
|
+
@tab v1
|
|
170
|
+
```javascript
|
|
171
|
+
DELETE /v1/categories/:id/force
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
@tab v2
|
|
175
|
+
```javascript
|
|
176
|
+
DELETE /v2/categories/:id?force=true
|
|
177
|
+
```
|
|
178
|
+
:::
|
|
179
|
+
|
|
180
|
+
#### Response Changes
|
|
181
|
+
|
|
182
|
+
- `POST /categories` now returns the complete category object
|
|
183
|
+
- (v1 returns just the id, ie: `{ "category_id": 123 }`)
|
|
184
|
+
- `PUT /categories/:id` returns the complete updated category object (not just `true`)
|
|
185
|
+
- `DELETE /categories/:id` returns `204 No Content` (not `true`)
|
|
186
|
+
|
|
187
|
+
#### Object Property Changes
|
|
188
|
+
|
|
189
|
+
- `children` property of a category group is never `null` - it's an empty array `[]` for groups with no children
|
|
190
|
+
- There is no `children` property in (non group) categories
|
|
191
|
+
- `group_category_name` has been removed
|
|
192
|
+
- `group_name` is now present on all grouped categories (not just in certain responses)
|
|
193
|
+
- `collapsed` is now present on all category objects. When set to `true` on a category group it will appear collapsed in the Web UI.
|
|
194
|
+
|
|
195
|
+
<a href="./changelog-visual#category-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 differences</a><br>
|
|
196
|
+
<a href="/v2/docs#model/categoryObject">View v2 Category Object Docs</a>
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
##### Query Parameters
|
|
201
|
+
|
|
202
|
+
Categories in the v2 nested response are ordered by the `order` property. When `order` is not set on a category, it falls back to alphabetical ordering.
|
|
203
|
+
|
|
204
|
+
:::tabs
|
|
205
|
+
@tab v1
|
|
206
|
+
```javascript
|
|
207
|
+
GET /v1/categories?format=nested // or "flattened" - default: flattened
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
@tab v2
|
|
211
|
+
```javascript
|
|
212
|
+
GET /v2/categories // Default: nested ordered list
|
|
213
|
+
GET /v2/categories?format=flattened // All categories including duplicates
|
|
214
|
+
GET /v2/categories?is_group=false // Only assignable categories (no groups)
|
|
215
|
+
GET /v2/categories?is_group=true // Only category groups
|
|
216
|
+
```
|
|
217
|
+
:::
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
**Migration**:
|
|
222
|
+
- Add the `format` query parameter to GET /v2/categories requests as needed, the default has changed
|
|
223
|
+
- Update category and category group creation logic to use a single endpoint with `is_group` property
|
|
224
|
+
- Update group management to use `PUT` with `children` array
|
|
225
|
+
- Handle the new query parameters for filtering categories
|
|
226
|
+
- Update code that checks `children === null` to check for empty array instead
|
|
227
|
+
- Update code to use created or modified categories returned directly from `POST` and `PUT` requests.
|
|
228
|
+
|
|
229
|
+
### Tags
|
|
230
|
+
|
|
231
|
+
#### New Properties
|
|
232
|
+
|
|
233
|
+
The tag object now includes:
|
|
234
|
+
- `created_at`
|
|
235
|
+
- `updated_at`
|
|
236
|
+
- `archived_at`
|
|
237
|
+
|
|
238
|
+
<a href="./changelog-visual#tags-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 differences</a><br>
|
|
239
|
+
<a href="/v2/docs#model/tagObject">View v2 Tag Object Docs</a>
|
|
240
|
+
|
|
241
|
+
#### New Endpoints
|
|
242
|
+
|
|
243
|
+
v2 adds full CRUD operations for tags:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// Get single tag
|
|
247
|
+
GET /v2/tags/:id
|
|
248
|
+
|
|
249
|
+
// Create tag
|
|
250
|
+
POST /v2/tags
|
|
251
|
+
{ "name": "Work Expense", ... }
|
|
252
|
+
|
|
253
|
+
// Update tag
|
|
254
|
+
PUT /v2/tags/:id
|
|
255
|
+
{ "name": "Business Expense", ... }
|
|
256
|
+
|
|
257
|
+
// Delete tag
|
|
258
|
+
DELETE /v2/tags/:id
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### List Ordering
|
|
262
|
+
|
|
263
|
+
`GET /tags` now returns tags in alphabetical order (matching the GUI).
|
|
264
|
+
|
|
265
|
+
**Migration**: Take advantage of the new tag management endpoints if you need to create or modify tags programmatically.
|
|
266
|
+
### Transactions
|
|
267
|
+
The transaction object has changed significantly in v2. A transaction has properties that reference other objects in Lunch Money such as categories, accounts, and tags. In v1 certain details for these related objects were "hydrated". In the context of RESTful APIs, "hydration" generally refers to populating an object with all its data. In the Lunch Money APIs, hydration generally relates to providing some details of associated objects that are also referred to by their IDs in the response body.
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
> [!NOTE] Breaking Change: Dehydrated Responses
|
|
271
|
+
> To optimize the responsiveness of the v2 Lunch Money APIs, the transaction object will no longer be hydrated. Categories, accounts, and tags are now returned as IDs only. Details of these objects can be retrieved by calling the appropriate endpoint using the supplied ID. Developers are encouraged to maintain a local cache of these objects to reduce the number of API calls.
|
|
272
|
+
|
|
273
|
+
:::tabs
|
|
274
|
+
@tab v1 - Hydrated
|
|
275
|
+
```javascript
|
|
276
|
+
{
|
|
277
|
+
"id": 123,
|
|
278
|
+
"category_id": 456,
|
|
279
|
+
"category_name": "Groceries", // ❌ Not in v2
|
|
280
|
+
"category_group_id": 789, // ❌ Not in v2
|
|
281
|
+
"category_group_name": "Food", // ❌ Not in v2
|
|
282
|
+
"is_income": false, // ❌ Not in v2
|
|
283
|
+
"tags": [ // ❌ Not in v2
|
|
284
|
+
{ "id": 1, "name": "Work" }
|
|
285
|
+
],
|
|
286
|
+
"asset_name": "Checking", // ❌ Not in v2
|
|
287
|
+
"plaid_account_name": "...", // ❌ Not in v2
|
|
288
|
+
...
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
@tab v2 - IDs only
|
|
293
|
+
```javascript
|
|
294
|
+
{
|
|
295
|
+
"id": 123,
|
|
296
|
+
"category_id": 456, // ✅ Still here
|
|
297
|
+
"tag_ids": [1, 2], // ✅ Array of IDs
|
|
298
|
+
"manual_account_id": 789, // ✅ Renamed from asset_id
|
|
299
|
+
"plaid_account_id": 123, // ✅ Still here
|
|
300
|
+
...
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
:::
|
|
304
|
+
|
|
305
|
+
To get full details, fetch the related objects:
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
// Get category details
|
|
309
|
+
GET /v2/categories/456
|
|
310
|
+
|
|
311
|
+
// Get account details
|
|
312
|
+
GET /v2/manual_accounts/789
|
|
313
|
+
// OR
|
|
314
|
+
GET /v2/plaid_accounts/123
|
|
315
|
+
|
|
316
|
+
// Get tag details
|
|
317
|
+
GET /v2/tags/1
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Migration Strategy**:
|
|
321
|
+
- Maintain a local cache of categories, accounts, and tags
|
|
322
|
+
- Update your code to use IDs and fetch related objects separately when needed
|
|
323
|
+
- Consider batching requests or using the bulk endpoints to reduce API calls
|
|
324
|
+
|
|
325
|
+
#### Removed Properties
|
|
326
|
+
|
|
327
|
+
The following properties found in the hydrated v1 Transaction Object are no longer present in the v2 Transaction object
|
|
328
|
+
|
|
329
|
+
These category-related properties are no longer included (get from category object):
|
|
330
|
+
- `category_name`
|
|
331
|
+
- `category_group_id`
|
|
332
|
+
- `category_group_name`
|
|
333
|
+
- `is_income`
|
|
334
|
+
- `exclude_from_budget`
|
|
335
|
+
- `exclude_from_totals`
|
|
336
|
+
- `group_category_name`
|
|
337
|
+
|
|
338
|
+
These account-related properties are no longer included (get from account object):
|
|
339
|
+
- `asset_institution_name`
|
|
340
|
+
- `asset_name`
|
|
341
|
+
- `asset_display_name`
|
|
342
|
+
- `asset_status`
|
|
343
|
+
- `plaid_account_name`
|
|
344
|
+
- `plaid_account_mask`
|
|
345
|
+
- `institution_name`
|
|
346
|
+
- `plaid_account_display_name`
|
|
347
|
+
|
|
348
|
+
#### Renamed Properties
|
|
349
|
+
The following properties have been renamed or refactored in the v2 Transaction object
|
|
350
|
+
|
|
351
|
+
- `asset_id` → `manual_account_id`
|
|
352
|
+
- `tags` (array of objects) → `tag_ids` (array of tag id integers)
|
|
353
|
+
- `has_children` → `is_split_parent`
|
|
354
|
+
- `parent_id` → `split_parent_id`
|
|
355
|
+
- `is_group` → `is_group_parent`
|
|
356
|
+
- `group_id` → `group_parent_id`
|
|
357
|
+
- `display_name` → removed (use `payee` directly)
|
|
358
|
+
- `display_notes` → removed (use `notes` directly)
|
|
359
|
+
|
|
360
|
+
These recurring-related properties have changed:
|
|
361
|
+
- `recurring_payee`, `recurring_description`, etc. are now in an `overrides` object
|
|
362
|
+
- Check the transaction's `recurring_id` and fetch from `/v2/recurring/:id` for details
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
<a href="./changelog-visual#transaction-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 differences</a><br>
|
|
366
|
+
<a href="/v2/docs#model/transactionObject">View v2 Transaction Object Docs</a>
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
#### Transaction Amounts
|
|
370
|
+
The `debit_as_negative` property has been removed as an optional query parameter and request body property in the `/transactions` endpoints. In the v2 API, positive `amount` values always indicate debit transactions and negative `amount` values always indicate credit transactions, regardless of the user's `debits_as_negative` setting in the Lunch Money app.
|
|
371
|
+
|
|
372
|
+
:::tabs
|
|
373
|
+
@tab v1
|
|
374
|
+
```javascript
|
|
375
|
+
GET /v1/transactions?debit_as_negative=false // Query parameter
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
@tab v2
|
|
379
|
+
```javascript
|
|
380
|
+
GET /v2/transactions
|
|
381
|
+
// Positive amount values indicate debit transactions
|
|
382
|
+
// Negative amount values indicate credit transactions
|
|
383
|
+
```
|
|
384
|
+
:::
|
|
385
|
+
|
|
386
|
+
> [!WARNING] Behavior Change
|
|
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
|
+
|
|
389
|
+
See the [Amounts & Balances guide](/v2/amounts-and-balances) for the full sign convention and how `to_base` works for multi-currency accounts.
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
> [!TIP] Migration Tip
|
|
393
|
+
> Remove `debit_as_negative` query parameters and update your code to interpret positive transaction amounts as debits and negative amounts as credits.
|
|
394
|
+
|
|
395
|
+
#### Status Values
|
|
396
|
+
|
|
397
|
+
:::tabs
|
|
398
|
+
@tab v1
|
|
399
|
+
```javascript
|
|
400
|
+
"status": "cleared" | "uncleared" | "pending" | "recurring" | "recurring_suggested"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
@tab v2
|
|
404
|
+
```javascript
|
|
405
|
+
"status": "reviewed" | "unreviewed" | "deleted_pending"
|
|
406
|
+
```
|
|
407
|
+
:::
|
|
408
|
+
|
|
409
|
+
**Migration**: Update status checks:
|
|
410
|
+
- `"cleared"` → `"reviewed"`
|
|
411
|
+
- `"uncleared"` → `"unreviewed"`
|
|
412
|
+
- Transactions with `is_pending: true` always have status `"unreviewed"`
|
|
413
|
+
- Update any code logic that checks if `status` is `pending` to use `is_pending` instead
|
|
414
|
+
|
|
415
|
+
#### Split and Grouped Transactions
|
|
416
|
+
|
|
417
|
+
**Split Transactions**:
|
|
418
|
+
- Parents of split transactions (`is_parent: true`) are not returned by default in `GET /transactions`
|
|
419
|
+
- Split transactions include `split_parent_id` pointing to the parent
|
|
420
|
+
- Use `GET /transactions/:id` to get parent with full `children` array
|
|
421
|
+
- Use `GET /transactions` with query param `include_split_parents=true` to include parents in list
|
|
422
|
+
|
|
423
|
+
**Grouped Transactions**:
|
|
424
|
+
- Transactions that have been grouped are not returned by default
|
|
425
|
+
- Only the group transaction (`is_group: true`) is returned
|
|
426
|
+
- Use `GET /transactions/:id` to get details of grouped transactions in the transaction group's `children` array.
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
**Migration**: Update code that processes split or grouped transactions to use the details endpoint when info is needed about split parent transactions, or transactions that are now part of a grouped transaction.
|
|
430
|
+
|
|
431
|
+
#### Query Parameters
|
|
432
|
+
|
|
433
|
+
:::tabs
|
|
434
|
+
@tab v1
|
|
435
|
+
```javascript
|
|
436
|
+
GET /v1/transactions?pending=true&debit_as_negative=false
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
@tab v2
|
|
440
|
+
```javascript
|
|
441
|
+
GET /v2/transactions?include_pending=true
|
|
442
|
+
// debit_as_negative removed
|
|
443
|
+
// Positive amount values indicate debit transactions
|
|
444
|
+
// Negative amount values indicate credit transactions
|
|
445
|
+
// New optional parameters:
|
|
446
|
+
// ?include_metadata=true // Include plaid_metadata, custom_metadata
|
|
447
|
+
// ?include_files=true // Include file attachments
|
|
448
|
+
// ?include_children=true // Include children for split/grouped
|
|
449
|
+
// ?include_split_parents=true // Include parent transactions
|
|
450
|
+
// ?created_since=2024-01-01 // Filter by creation timestamp (date or datetime)
|
|
451
|
+
// ?updated_since=2024-01-01T12:00:00Z // Filter by update timestamp (date or datetime)
|
|
452
|
+
```
|
|
453
|
+
:::
|
|
454
|
+
|
|
455
|
+
**Migration**: Update query parameter names on `GET /transaction`
|
|
456
|
+
- remove `debit_as_negative`
|
|
457
|
+
- add new `include_XXX` params to get additional info in the returned objects
|
|
458
|
+
- use `created_since` and `updated_since` to filter transactions by creation or update timestamps (accepts date YYYY-MM-DD or ISO 8601 datetime format)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
#### Creating Transactions
|
|
462
|
+
|
|
463
|
+
Request body changes:
|
|
464
|
+
|
|
465
|
+
:::tabs
|
|
466
|
+
@tab v1
|
|
467
|
+
```javascript
|
|
468
|
+
POST /v1/transactions
|
|
469
|
+
{
|
|
470
|
+
"transactions": [...],
|
|
471
|
+
"debit_as_negative": false, // ❌ Removed in v2
|
|
472
|
+
...
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
@tab v2
|
|
477
|
+
```javascript
|
|
478
|
+
POST /v2/transactions
|
|
479
|
+
{
|
|
480
|
+
"transactions": [
|
|
481
|
+
{
|
|
482
|
+
"asset_id": 123, // ❌ Renamed
|
|
483
|
+
"manual_account_id": 123, // ✅ New name
|
|
484
|
+
"tags": ["Work"], // ❌ Can't create tags on the fly
|
|
485
|
+
"tag_ids": [1, 2] // ✅ Must use existing tag IDs
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
:::
|
|
491
|
+
|
|
492
|
+
Response body changes (specific to duplicate detection):
|
|
493
|
+
|
|
494
|
+
:::tabs
|
|
495
|
+
@tab v1 - Success
|
|
496
|
+
```javascript
|
|
497
|
+
{
|
|
498
|
+
"ids": [new_transaction1.id, ...],
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
@tab v1 - Duplicate
|
|
503
|
+
```javascript
|
|
504
|
+
{
|
|
505
|
+
"error": [
|
|
506
|
+
"Key (user_external_id, asset_id, account_id)=(123457891, 178181, 66938) already exists."
|
|
507
|
+
]
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
@tab v2 - Success
|
|
512
|
+
```javascript
|
|
513
|
+
// New transactions ARE inserted, even if some are duplicates
|
|
514
|
+
{
|
|
515
|
+
"transactions": [
|
|
516
|
+
{
|
|
517
|
+
"id": 8
|
|
518
|
+
// rest of full transaction object
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
"id": 9,
|
|
522
|
+
// rest of full transaction object
|
|
523
|
+
}
|
|
524
|
+
],
|
|
525
|
+
"skipped_duplicates": [
|
|
526
|
+
{
|
|
527
|
+
"reason": "duplicate_external_id",
|
|
528
|
+
"request_transactions_index": 1,
|
|
529
|
+
"existing_transaction_id": 2, // id of the existing match
|
|
530
|
+
"request_transaction": { // from the request body
|
|
531
|
+
"date": "2025-06-20",
|
|
532
|
+
"amount": "250.0000",
|
|
533
|
+
"payee": "Fidelity",
|
|
534
|
+
"manual_account_id": 1,
|
|
535
|
+
"external_id": "12345",
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
:::
|
|
542
|
+
|
|
543
|
+
> [!WARNING] Important Changes
|
|
544
|
+
> - `asset_id` → `manual_account_id`
|
|
545
|
+
> - `tags` array (could include strings to create tags) → `tag_ids` array (only existing IDs)
|
|
546
|
+
> - `plaid_account_id` can now be set when creating transactions (if account allows modifications)
|
|
547
|
+
> - `debit_as_negative` removed - positive amounts are debits and negative amounts are credits
|
|
548
|
+
> - Duplicated transactions (due to `external_id` conflict, or matching `date`, `amount` and account when `skip_duplicates: true` is in the request body), no longer cause the entire request to fail. Non-duplicates are inserted, and duplicates are reported
|
|
549
|
+
|
|
550
|
+
> [!TIP] Migration Steps
|
|
551
|
+
> - Ensure that amounts are properly signed for inserted transactions. Regardless of the value of the users `debits_as_negative` property always use positive numbers for debits and negative numbers for credits.
|
|
552
|
+
> - When creating transactions with tags, create new tags first using `POST /tags` before assigning them
|
|
553
|
+
> - Update changed property names in request bodies
|
|
554
|
+
> - Modify handling of success responses to leverage the full objects for inserted transactions
|
|
555
|
+
> - Check success response for `skipped_duplicates`
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
#### Updating Transactions
|
|
559
|
+
|
|
560
|
+
:::tabs
|
|
561
|
+
@tab v1
|
|
562
|
+
```javascript
|
|
563
|
+
PUT /v1/transactions/:id
|
|
564
|
+
{
|
|
565
|
+
"transaction": { ... },
|
|
566
|
+
"split": [...], // ❌ Moved to separate endpoint
|
|
567
|
+
"debit_as_negative": false, // ❌ Removed
|
|
568
|
+
"skip_balance_update": true // ❌ Renamed
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
@tab v2 - Single transaction
|
|
573
|
+
```javascript
|
|
574
|
+
PUT /v2/transactions/:id?update_balance=false // Query parameter
|
|
575
|
+
{
|
|
576
|
+
// Just the transaction properties to update OR
|
|
577
|
+
// A full transaction object with at least one updatable property modified
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
@tab v2 - Bulk update (new)
|
|
582
|
+
```javascript
|
|
583
|
+
PUT /v2/transactions
|
|
584
|
+
{
|
|
585
|
+
"transactions": [...]
|
|
586
|
+
// Array of full or partial transaction objects to update
|
|
587
|
+
// Each object MUST include the id of the existing transaction to update
|
|
588
|
+
// The rest of the object should be at least one property to update
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
:::
|
|
592
|
+
|
|
593
|
+
**Migration**:
|
|
594
|
+
- Take advantage of new bulk update endpoint where appropriate
|
|
595
|
+
- Migrate code to split transactions to the new endpoint described below
|
|
596
|
+
|
|
597
|
+
#### Splitting and Grouping Transactions
|
|
598
|
+
These endpoints have been modified to follow the standard pattern of using `POST` to create something and `PUT` to modify something, and `DELETE` to remove something.
|
|
599
|
+
|
|
600
|
+
**Splitting Transactions**:
|
|
601
|
+
|
|
602
|
+
:::tabs
|
|
603
|
+
@tab v1
|
|
604
|
+
```javascript
|
|
605
|
+
PUT /v1/transactions/:id
|
|
606
|
+
{ "split": [...] }
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
@tab v2
|
|
610
|
+
```javascript
|
|
611
|
+
POST /v2/transactions/split/:id
|
|
612
|
+
{ "child_transactions": [
|
|
613
|
+
// objects with at least "amount" and "payee" fields
|
|
614
|
+
] }
|
|
615
|
+
```
|
|
616
|
+
:::
|
|
617
|
+
|
|
618
|
+
> [!NOTE] New Capability
|
|
619
|
+
> Child transactions in `POST /transactions/split/{id}` now support `tag_ids`.
|
|
620
|
+
|
|
621
|
+
**Unsplit Transactions**:
|
|
622
|
+
|
|
623
|
+
:::tabs
|
|
624
|
+
@tab v1
|
|
625
|
+
```javascript
|
|
626
|
+
POST /v1/transactions/unsplit
|
|
627
|
+
{ "parent_ids": [...] }
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
@tab v2
|
|
631
|
+
```javascript
|
|
632
|
+
DELETE /v2/transactions/split/:parent_id
|
|
633
|
+
```
|
|
634
|
+
:::
|
|
635
|
+
|
|
636
|
+
<a href="/v2/docs#tag/transactions-split">View v2 Split Transaction Endpoint Docs</a><br>
|
|
637
|
+
|
|
638
|
+
**Grouping Transactions**:
|
|
639
|
+
|
|
640
|
+
:::tabs
|
|
641
|
+
@tab v1
|
|
642
|
+
```javascript
|
|
643
|
+
POST /v1/transactions/group
|
|
644
|
+
{
|
|
645
|
+
"transactions": [id1, id2, ...],
|
|
646
|
+
// other properties, ie: date, payee
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
@tab v2
|
|
651
|
+
```javascript
|
|
652
|
+
POST /v2/transactions/group
|
|
653
|
+
{
|
|
654
|
+
"ids": [id1, id2,...]
|
|
655
|
+
// other properties remain the same
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
:::
|
|
659
|
+
|
|
660
|
+
**Ungroup Transactions**:
|
|
661
|
+
|
|
662
|
+
:::tabs
|
|
663
|
+
@tab v1
|
|
664
|
+
```javascript
|
|
665
|
+
DELETE /v1/transactions/group/:id
|
|
666
|
+
// Returns 200 with a transactions array of ids that were ungrouped
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
@tab v2
|
|
670
|
+
```javascript
|
|
671
|
+
DELETE /v2/transactions/group/:id
|
|
672
|
+
// Returns 204 No Content
|
|
673
|
+
```
|
|
674
|
+
:::
|
|
675
|
+
|
|
676
|
+
<a href="/v2/docs#tag/transactions-group">View v2 Group Transactions Endpoint Docs</a><br>
|
|
677
|
+
|
|
678
|
+
**Important Changes**:
|
|
679
|
+
- New method and endpoint to split transactions which returns the original transaction with children
|
|
680
|
+
- Mew method and endpoint to unsplit transactions which returns a 204 No Content
|
|
681
|
+
- `ids` property replaces `transactions` on the `POST /transactions/group` endpoint
|
|
682
|
+
- New method and endpoint to ungroup transactions which returns a 204 No Content
|
|
683
|
+
|
|
684
|
+
**Migration**:
|
|
685
|
+
- Move split and group logic to the new endpoints as needed
|
|
686
|
+
|
|
687
|
+
#### Deleting Transactions
|
|
688
|
+
> [!NOTE] New Feature
|
|
689
|
+
> It is now possible to delete transactions via the API!
|
|
690
|
+
|
|
691
|
+
:::tabs
|
|
692
|
+
@tab v1
|
|
693
|
+
```javascript
|
|
694
|
+
// No delete endpoint available
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
@tab v2 - Single
|
|
698
|
+
```javascript
|
|
699
|
+
DELETE /v2/transactions/:id // Single transaction
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
@tab v2 - Bulk
|
|
703
|
+
```javascript
|
|
704
|
+
DELETE /v2/transactions // Bulk delete
|
|
705
|
+
{ "ids": [1, 2, 3] }
|
|
706
|
+
```
|
|
707
|
+
:::
|
|
708
|
+
|
|
709
|
+
<a href="/v2/docs#tag/transactions/DELETE/transactions/{id}">View v2 Delete Transactions Endpoint Docs</a><br>
|
|
710
|
+
<a href="/v2/docs#tag/transactions-bulk/DELETE/transactions">View v2 Bulk Delete Transactions Endpoint Docs</a><br>
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
**Migration**: Update code that may have been working around the lack of delete endpoints.
|
|
714
|
+
|
|
715
|
+
> [!WARNING] Use With Caution!
|
|
716
|
+
> Deletions are permanent. Ensure apps that use these endpoints are very well tested before using on real user data.
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
#### Transaction Attachments (New)
|
|
720
|
+
|
|
721
|
+
v2 introduces the ability to view, create and delete file attachments to transactions
|
|
722
|
+
|
|
723
|
+
```javascript
|
|
724
|
+
// Upload attachment
|
|
725
|
+
POST /v2/transactions/:transaction_id/attachments
|
|
726
|
+
// multipart/form-data with file and optional notes
|
|
727
|
+
|
|
728
|
+
// Get attachment details
|
|
729
|
+
GET /v2/transactions/attachments/:file_id
|
|
730
|
+
|
|
731
|
+
// Get download URL
|
|
732
|
+
GET /v2/transactions/attachments/:file_id/url
|
|
733
|
+
|
|
734
|
+
// Delete attachment
|
|
735
|
+
DELETE /v2/transactions/attachments/:file_id
|
|
736
|
+
```
|
|
737
|
+
<a href="/v2/docs#tag/transactions-files">View v2 Transaction Attachment Endpoint Docs</a><br>
|
|
738
|
+
|
|
739
|
+
### Recurring Items
|
|
740
|
+
A new `GET v2/recurring_items/{id} endpoint to get a single recurring item has been added.
|
|
741
|
+
|
|
742
|
+
<a href="/v2/docs#tag/recurring_items">View v2 Recurring Endpoint Docs</a><br>
|
|
743
|
+
|
|
744
|
+
#### Object Structure Changes
|
|
745
|
+
|
|
746
|
+
> [!NOTE] Major Reorganization
|
|
747
|
+
> The recurring object's properties are now grouped into three nested objects: `transaction_criteria` (scheduling and amount details), `overrides` (per-occurrence customizations), and `matches` (query results). Previously these were all flat top-level properties.
|
|
748
|
+
|
|
749
|
+
:::tabs
|
|
750
|
+
@tab v1
|
|
751
|
+
```javascript
|
|
752
|
+
{
|
|
753
|
+
"id": 123,
|
|
754
|
+
"start_date": "2024-01-01",
|
|
755
|
+
"end_date": null,
|
|
756
|
+
"billing_date": "2024-01-15",
|
|
757
|
+
"payee": "Netflix",
|
|
758
|
+
"amount": "15.99",
|
|
759
|
+
"category_id": 456,
|
|
760
|
+
"category_group_id": 789, // ❌ Removed
|
|
761
|
+
"is_income": false, // ❌ Removed (get from category)
|
|
762
|
+
"exclude_from_totals": false, // ❌ Removed (get from category)
|
|
763
|
+
"granularity": "month",
|
|
764
|
+
"quantity": 1,
|
|
765
|
+
"occurrences": { ... },
|
|
766
|
+
"transactions_within_range": [...],
|
|
767
|
+
"missing_dates_within_range": [...],
|
|
768
|
+
"date": "2024-06-04"
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
@tab v2
|
|
773
|
+
```javascript
|
|
774
|
+
{
|
|
775
|
+
"id": 123,
|
|
776
|
+
"description": "Netflix subscription",
|
|
777
|
+
"source": "manual",
|
|
778
|
+
"status": "reviewed", // ✅ New: "reviewed" or "suggested"
|
|
779
|
+
"transaction_criteria": {
|
|
780
|
+
"start_date": "2024-01-01", // ✅ Moved here
|
|
781
|
+
"end_date": null, // ✅ Moved here
|
|
782
|
+
"anchor_date": "2024-01-15", // ✅ Renamed from billing_date
|
|
783
|
+
"payee": "Netflix", // ✅ Moved here
|
|
784
|
+
"amount": "15.99", // ✅ Moved here
|
|
785
|
+
"currency": "usd",
|
|
786
|
+
"granularity": "month", // ✅ Moved here
|
|
787
|
+
"quantity": 1, // ✅ Moved here
|
|
788
|
+
"manual_account_id": 123,
|
|
789
|
+
"plaid_account_id": null
|
|
790
|
+
},
|
|
791
|
+
"overrides": {
|
|
792
|
+
"payee": "Netflix Subscription", // ✅ Moved here
|
|
793
|
+
"notes": "Monthly subscription",
|
|
794
|
+
"category_id": 456 // ✅ Moved here
|
|
795
|
+
},
|
|
796
|
+
"matches": {
|
|
797
|
+
"request_start_date": "2024-06-01", // ✅ Renamed from date
|
|
798
|
+
"request_end_date": "2024-06-30", // ✅ New
|
|
799
|
+
"expected_occurrence_dates": [...], // ✅ Renamed from occurrences
|
|
800
|
+
"found_transactions": [ // ✅ Renamed from transactions_within_range
|
|
801
|
+
{ "date": "2024-06-15", "transaction_id": 789 }
|
|
802
|
+
],
|
|
803
|
+
"missing_transaction_dates": [...] // ✅ Renamed from missing_dates_within_range
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
:::
|
|
808
|
+
|
|
809
|
+
<a href="./changelog-visual#recurring-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 Recurring Object differences</a><br>
|
|
810
|
+
<a href="/v2/docs#model/recurringObject">View v2 Recurring Object Docs</a>
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
#### Query Parameters
|
|
814
|
+
|
|
815
|
+
:::tabs
|
|
816
|
+
@tab v1
|
|
817
|
+
```javascript
|
|
818
|
+
GET /v1/recurring_items?start_date=2024-06-01
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
@tab v2
|
|
822
|
+
```javascript
|
|
823
|
+
GET /v2/recurring?start_date=2024-06-01&end_date=2024-06-30 // Both required if using dates
|
|
824
|
+
GET /v2/recurring?include_suggested=true // Include suggested recurring items
|
|
825
|
+
```
|
|
826
|
+
:::
|
|
827
|
+
|
|
828
|
+
**Migration**:
|
|
829
|
+
- Update endpoint path
|
|
830
|
+
- Restructure code to access properties in their new locations
|
|
831
|
+
- Update property names (`billing_date` → `transaction_criteria.anchor_date`)
|
|
832
|
+
- Use both `start_date` and `end_date` when specifying a range
|
|
833
|
+
|
|
834
|
+
### Manual Accounts (formerly Assets)
|
|
835
|
+
The /assets endpoint was renamed to /manual_accounts to align with language used in the web client.
|
|
836
|
+
|
|
837
|
+
#### Endpoint Rename
|
|
838
|
+
|
|
839
|
+
:::tabs
|
|
840
|
+
@tab v1
|
|
841
|
+
```javascript
|
|
842
|
+
GET /v1/assets
|
|
843
|
+
POST /v1/assets
|
|
844
|
+
PUT /v1/assets/:id
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
@tab v2
|
|
848
|
+
```javascript
|
|
849
|
+
GET /v2/manual_accounts
|
|
850
|
+
POST /v2/manual_accounts
|
|
851
|
+
PUT /v2/manual_accounts/:id
|
|
852
|
+
GET /v2/manual_accounts/:id // ✅ New: Get single account
|
|
853
|
+
DELETE /v2/manual_accounts/:id // ✅ New: Delete account
|
|
854
|
+
```
|
|
855
|
+
:::
|
|
856
|
+
|
|
857
|
+
<a href="/v2/docs#tag/manual_accounts">View v2 Manual Accounts Endpoint Docs</a><br>
|
|
858
|
+
|
|
859
|
+
#### Property Renames
|
|
860
|
+
|
|
861
|
+
```javascript
|
|
862
|
+
// v1 → v2
|
|
863
|
+
"type_name" → "type"
|
|
864
|
+
"subtype_name" → "subtype"
|
|
865
|
+
"exclude_transactions" → "exclude_from_transactions"
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
> [!NOTE] Type Name Change
|
|
869
|
+
> Accounts with `type_name: "depository"` now return `type: "cash"`.
|
|
870
|
+
|
|
871
|
+
#### New Properties
|
|
872
|
+
|
|
873
|
+
- `updated_at`
|
|
874
|
+
- `external_id` (API-only, can be set/updated via API)
|
|
875
|
+
- `custom_metadata` (API-only, any valid JSON object < 4MB)
|
|
876
|
+
|
|
877
|
+
<a href="./changelog-visual#manual-account-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 Manual Account Object differences</a><br>
|
|
878
|
+
<a href="/v2/docs#model/manualAccountObject">View v2 Manual Account Object Docs</a>
|
|
879
|
+
|
|
880
|
+
**Migration**:
|
|
881
|
+
- Update all endpoint paths from `/assets` to `/manual_accounts`
|
|
882
|
+
- Update property names in your code
|
|
883
|
+
- Handle the `depository` → `cash` type change
|
|
884
|
+
- Take advantage of new endpoints for single account and delete operations
|
|
885
|
+
|
|
886
|
+
### Plaid Accounts
|
|
887
|
+
|
|
888
|
+
#### New Endpoints
|
|
889
|
+
|
|
890
|
+
```javascript
|
|
891
|
+
// v1 - Only list endpoint
|
|
892
|
+
GET /v1/plaid_accounts
|
|
893
|
+
|
|
894
|
+
// v2 - Added single account endpoint
|
|
895
|
+
GET /v2/plaid_accounts
|
|
896
|
+
GET /v2/plaid_accounts/:id
|
|
897
|
+
```
|
|
898
|
+
<a href="/v2/docs#tag/plaid_accounts">View v2 Plaid Accounts Endpoint Docs</a><br>
|
|
899
|
+
|
|
900
|
+
#### New Response Properties
|
|
901
|
+
|
|
902
|
+
- `allow_transaction_modification`: Boolean indicating if transactions can be modified (enabled by default)
|
|
903
|
+
|
|
904
|
+
<a href="./changelog-visual#plaid-account-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 Plaid Account Object differences</a><br>
|
|
905
|
+
<a href="/v2/docs#model/plaidAccountObject">View v2 Plaid Account Object Docs</a>
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
#### Fetch Endpoint Changes
|
|
909
|
+
|
|
910
|
+
:::tabs
|
|
911
|
+
@tab v1
|
|
912
|
+
```ts
|
|
913
|
+
POST /v1/plaid_accounts/fetch
|
|
914
|
+
// Returns: true
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
@tab v2
|
|
918
|
+
```ts
|
|
919
|
+
POST /v2/plaid_accounts/fetch
|
|
920
|
+
// Returns: { "plaid_accounts": [...] } // List of accounts that were fetched
|
|
921
|
+
```
|
|
922
|
+
:::
|
|
923
|
+
|
|
924
|
+
**Migration**: Update fetch response handling to expect an object with `plaid_accounts` array instead of `true`.
|
|
925
|
+
|
|
926
|
+
### Crypto Balances
|
|
927
|
+
|
|
928
|
+
The v2 API adds crypto endpoints that support the full lifecycle of manual crypto balances. It also adds endpoints for viewing and extending the list of supported manual cryptocurrencies.
|
|
929
|
+
|
|
930
|
+
The v2 API also provides new endpoints for reading and refreshing synced crypto balances. Synced crypto account linking and management are supported in the Lunch Money Web app only.
|
|
931
|
+
|
|
932
|
+
:::tabs
|
|
933
|
+
@tab v1
|
|
934
|
+
```ts
|
|
935
|
+
GET /v1/crypto //manual balances only
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
@tab v2
|
|
939
|
+
```ts
|
|
940
|
+
GET /v2/cryptocurrencies
|
|
941
|
+
POST /v2/cryptocurrencies
|
|
942
|
+
GET /v2/crypto/manual
|
|
943
|
+
GET /v2/crypto/manual/:id
|
|
944
|
+
GET /v2/crypto/synced
|
|
945
|
+
GET /v2/crypto/synced/:id // Returns one synced crypto account with nested balances
|
|
946
|
+
GET /v2/crypto/synced/:id/:symbol
|
|
947
|
+
POST /v2/crypto/synced/:id/refresh
|
|
948
|
+
POST /v2/crypto/manual
|
|
949
|
+
PUT /v2/crypto/manual/:id
|
|
950
|
+
DELETE /v2/crypto/manual/:id
|
|
951
|
+
```
|
|
952
|
+
:::
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
<a href="/v2/docs#tag/crypto-manual">View v2 Manual Crypto Docs</a><br>
|
|
956
|
+
<a href="/v2/docs#tag/crypto-synced">View v2 Synced Crypto Docs</a><br>
|
|
957
|
+
<a href="./changelog-visual#crypto-object-row" target="_blank" rel="noopener noreferrer">View v1/v2 Crypto Object differences</a>
|
|
958
|
+
|
|
959
|
+
**Migration**:
|
|
960
|
+
- Replace `GET /v1/crypto` consumers with one of:
|
|
961
|
+
- `GET /v2/crypto/manual` for manual-only balances
|
|
962
|
+
- `GET /v2/crypto/synced` for synced-only balances
|
|
963
|
+
- If your app lets users add manual crypto balances for unsupported symbols, first call `GET /v2/cryptocurrencies` to inspect the supported catalog, then `POST /v2/cryptocurrencies` with a CoinGecko coin-page URL when a symbol is missing
|
|
964
|
+
- Treat `source` as the contract-level differentiator (`manual` vs `synced`)
|
|
965
|
+
- For synced account-specific reads, update handlers to expect a single synced crypto account object with nested `balances` on `GET /v2/crypto/synced/:id`
|
|
966
|
+
- Use `GET /v2/crypto/synced/:id/:symbol` when you need one balance from a synced crypto account
|
|
967
|
+
- For manual deletes, handle `422 Unprocessable Content` when `keep_history` is not explicitly provided and history exists
|
|
968
|
+
|
|
969
|
+
### Summary Endpoint
|
|
970
|
+
A new `v2/summary` endpoint replaces the `v1/budgets` endpoint.
|
|
971
|
+
|
|
972
|
+
This endpoint significantly refactors the response and aligns with the recently released v2 Budgets feature.
|
|
973
|
+
|
|
974
|
+
> [!NOTE] `include_occurrences=true`
|
|
975
|
+
> When `include_occurrences` is set to `true`, each returned category includes an `occurrences` array.
|
|
976
|
+
> - For aligned ranges, the array includes one occurrence per budget period in range.
|
|
977
|
+
> - For non-aligned ranges, the array includes only budget periods fully contained in range.
|
|
978
|
+
> - You can inspect `start_date` and `end_date` in occurrence objects to choose a fully aligned date range for subsequent requests.
|
|
979
|
+
|
|
980
|
+
<a href="/v2/docs#tag/summary">View v2 Summary Endpoint Docs</a><br>
|
|
981
|
+
<a href="/v2/docs#model/alignedSummaryResponseObject">View v2 Summary Object Docs</a><br>
|
|
982
|
+
|
|
983
|
+
### budgets endpoints
|
|
984
|
+
|
|
985
|
+
- **`GET /budgets/settings`** has been added <a href="/v2/docs#tag/budgets/GET/budgets/settings">View Docs</a>
|
|
986
|
+
- **`PUT /budgets`** has been added <a href="/v2/docs#tag/budgets/PUT/budgets">View Docs</a>
|
|
987
|
+
- **`DELETE /budgets`** has been added <a href="/v2/docs#tag/budgets/DELETE/budgets">View Docs</a>
|
|
988
|
+
|
|
989
|
+
### balance_history endpoints (New)
|
|
990
|
+
|
|
991
|
+
- **`GET /balance_history`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
992
|
+
- **`GET /balance_history/{account_type}/{account_id}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
993
|
+
- **`GET /balance_history/crypto_synced/{account_id}/{symbol}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
994
|
+
- **`PUT /balance_history/{account_type}/{account_id}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
995
|
+
- **`PUT /balance_history/crypto_synced/{account_id}/{symbol}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
996
|
+
- **`DELETE /balance_history/{account_type}/{account_id}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
997
|
+
- **`DELETE /balance_history/crypto_synced/{account_id}/{symbol}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
998
|
+
- **`DELETE /balance_history/entries/{id}`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
999
|
+
- **`PUT /balance_history/deleted/{account_id}/details`** has been added <a href="/v2/docs#tag/balance_history">View Docs</a>
|
|
1000
|
+
|
|
1001
|
+
### Common Migration Patterns
|
|
1002
|
+
|
|
1003
|
+
#### Pattern 1: Getting object details for non hydrated responses
|
|
1004
|
+
|
|
1005
|
+
:::tabs
|
|
1006
|
+
@tab v1 - Hydrated
|
|
1007
|
+
```javascript
|
|
1008
|
+
const transaction = await getTransaction(123);
|
|
1009
|
+
console.log(transaction.category_name); // Direct access
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
@tab v2 - Fetch separately
|
|
1013
|
+
```javascript
|
|
1014
|
+
const transaction = await getTransaction(123);
|
|
1015
|
+
const category = await getCategory(transaction.category_id);
|
|
1016
|
+
console.log(category.name); // Access from category object
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
@tab v2 - With caching
|
|
1020
|
+
```javascript
|
|
1021
|
+
// Better: Cache categories
|
|
1022
|
+
const categoryCache = new Map();
|
|
1023
|
+
async function getCategoryName(id) {
|
|
1024
|
+
if (!categoryCache.has(id)) {
|
|
1025
|
+
const cat = await getCategory(id);
|
|
1026
|
+
categoryCache.set(id, cat);
|
|
1027
|
+
}
|
|
1028
|
+
return categoryCache.get(id).name;
|
|
1029
|
+
}
|
|
1030
|
+
```
|
|
1031
|
+
:::
|
|
1032
|
+
|
|
1033
|
+
#### Pattern 2: Handling Response Codes
|
|
1034
|
+
|
|
1035
|
+
:::tabs
|
|
1036
|
+
@tab v1
|
|
1037
|
+
```javascript
|
|
1038
|
+
// All success responses were 200 OK
|
|
1039
|
+
if (response.status === 200) {
|
|
1040
|
+
const data = response.data; // Might be true, an ID, an error message, or a complex response object
|
|
1041
|
+
}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
@tab v2
|
|
1045
|
+
```javascript
|
|
1046
|
+
// Proper HTTP status codes
|
|
1047
|
+
if (response.status === 201) {
|
|
1048
|
+
// POST - created object in response.body
|
|
1049
|
+
const created = response.body;
|
|
1050
|
+
} else if (response.status === 200) {
|
|
1051
|
+
// GET - requested items are returned
|
|
1052
|
+
// PUT - updated object in response.body
|
|
1053
|
+
const updated = response.body;
|
|
1054
|
+
} else if (response.status === 204) {
|
|
1055
|
+
// DELETE - no response body
|
|
1056
|
+
// Success!
|
|
1057
|
+
}
|
|
1058
|
+
```
|
|
1059
|
+
:::
|
|
1060
|
+
|
|
1061
|
+
#### Pattern 3: Error Handling
|
|
1062
|
+
|
|
1063
|
+
:::tabs
|
|
1064
|
+
@tab v1
|
|
1065
|
+
```javascript
|
|
1066
|
+
// Inconsistent error formats
|
|
1067
|
+
try {
|
|
1068
|
+
// Some errors returned as 200 with { error: "message" }
|
|
1069
|
+
// Some errors returned as 200 with { errors: ["message"] }
|
|
1070
|
+
// Some errors returned as 404
|
|
1071
|
+
} catch (e) {
|
|
1072
|
+
// Handle inconsistently
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
@tab v2
|
|
1077
|
+
```javascript
|
|
1078
|
+
// Consistent error format
|
|
1079
|
+
try {
|
|
1080
|
+
const response = await api.post('/transactions', data);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
if (error.response.status === 400) {
|
|
1083
|
+
const { message, errors } = error.response.body;
|
|
1084
|
+
errors.forEach(err => {
|
|
1085
|
+
console.error(err.errMsg);
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
```
|
|
1090
|
+
:::
|
|
1091
|
+
|
|
1092
|
+
#### Pattern 4: Creating New Tags with New Transactions
|
|
1093
|
+
|
|
1094
|
+
:::tabs
|
|
1095
|
+
@tab v1
|
|
1096
|
+
```javascript
|
|
1097
|
+
// Create tags on the fly
|
|
1098
|
+
POST /v1/transactions
|
|
1099
|
+
{
|
|
1100
|
+
"transactions": [{
|
|
1101
|
+
"tags": ["New Tag"] // Creates tag automatically
|
|
1102
|
+
}]
|
|
1103
|
+
}
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
@tab v2
|
|
1107
|
+
```javascript
|
|
1108
|
+
// Step 1: Create tag
|
|
1109
|
+
const tag = await POST /v2/tags { name: "New Tag" };
|
|
1110
|
+
|
|
1111
|
+
// Step 2: Use tag ID
|
|
1112
|
+
POST /v2/transactions
|
|
1113
|
+
{
|
|
1114
|
+
"transactions": [{
|
|
1115
|
+
"tag_ids": [tag.id]
|
|
1116
|
+
}]
|
|
1117
|
+
}
|
|
1118
|
+
```
|
|
1119
|
+
:::
|
|
1120
|
+
|
|
1121
|
+
### Testing Your Migration
|
|
1122
|
+
|
|
1123
|
+
1. **Start with Read Operations**: Update your code to read from v2 endpoints first
|
|
1124
|
+
2. **Handle Dehydration**: Ensure your code can work with IDs instead of hydrated objects
|
|
1125
|
+
3. **Update Write Operations**: Migrate create/update/delete operations
|
|
1126
|
+
4. **Test Edge Cases**:
|
|
1127
|
+
- Split transactions
|
|
1128
|
+
- Grouped transactions
|
|
1129
|
+
- Transactions with recurring items
|
|
1130
|
+
- Category groups with children
|
|
1131
|
+
5. **Verify Error Handling**: Test various error scenarios to ensure proper handling
|
|
1132
|
+
6. **Add New Capabilities**: Take advantage of new endpoints to fully manage Transactions, Tags, and Manual Accounts and more.
|
|
1133
|
+
|
|
1134
|
+
### Need Help?
|
|
1135
|
+
|
|
1136
|
+
If you run into issues during migration:
|
|
1137
|
+
|
|
1138
|
+
- Review the [v2 API documentation](../v2/)
|
|
1139
|
+
- Review the [v2 API Overview](/v2/overview) for general concepts
|
|
1140
|
+
- <a href="https://lunchmoney.app/discord" target="_blank" rel="noopener noreferrer">Join the Lunch Money community on Discord</a> and send a question in the <a href="https://discord.com/channels/842337014556262411/1134594318414389258" target="_blank" rel="noopener noreferrer">Developer API Channel</a>.
|
|
1141
|
+
- [Email our developer advocate](mailto:jp@lunchmoney.app).
|
|
1142
|
+
|
|
1143
|
+
We're here to help make your migration as smooth as possible!
|