@lunch-money/developer-docs 2.11.2-preview.1 → 2.11.2-preview.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/STYLE_GUIDE.md +4 -0
- package/docs/branding-your-app.md +1 -1
- package/docs/getting-started.md +5 -5
- package/docs/introduction.md +1 -1
- package/docs/oauth/authorization-code.md +22 -6
- package/docs/oauth/concepts.md +62 -5
- package/docs/oauth/development.md +22 -8
- package/docs/oauth/index.md +8 -6
- package/docs/oauth/native-apps.md +26 -2
- package/docs/oauth/register-client.md +15 -5
- package/docs/oauth/review-and-approval.md +15 -7
- package/docs/oauth/sample-applications.md +263 -0
- package/docs/oauth/scopes.md +7 -7
- package/docs/oauth/security.md +32 -2
- package/docs/oauth/tokens.md +11 -3
- package/docs/oauth/troubleshooting.md +17 -3
- package/manifest.json +10 -1
- package/package.json +1 -1
- package/v1/_user.md +2 -2
- package/v2/docs/intro-to-v2.md +1 -1
- package/v2/docs/version-history.md +4 -2
- package/v2/images/oauth-authorization-flow.svg +9 -6
- package/v2/spec/AGENTS.md +6 -0
- package/v2/spec/lunch-money-api-v2.yaml +769 -1771
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# OAuth sample applications
|
|
2
|
+
|
|
3
|
+
Use a sample application to see Lunch Money OAuth working end to end before you adapt the flow to your own architecture. Choose the confidential sample when a trusted server will hold credentials, or the native sample when an installed iOS or Android application will hold them. The two architectures have different security boundaries and are documented separately.
|
|
4
|
+
|
|
5
|
+
> [!TIP] Open-source samples
|
|
6
|
+
> - [Confidential Node.js and TypeScript sample](https://github.com/lunch-money/lm-oauth-confidential-node-example) and its [README](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/README.md)
|
|
7
|
+
> - [Native Expo sample for iOS and Android](https://github.com/lunch-money/lm-oauth-native-expo-example) and its [README](https://github.com/lunch-money/lm-oauth-native-expo-example/blob/main/README.md)
|
|
8
|
+
|
|
9
|
+
## Confidential Node.js and TypeScript sample
|
|
10
|
+
|
|
11
|
+
> [!NOTE] This walkthrough is for confidential applications
|
|
12
|
+
> The walkthrough and teaching-code guide below apply to the confidential Node.js sample, where a trusted server holds the client secret and credentials. For an installed iOS or Android application that holds its own credentials without a client secret, skip to the [Native Expo sample](#native-expo-sample).
|
|
13
|
+
|
|
14
|
+
[`lm-oauth-confidential-node-example`](https://github.com/lunch-money/lm-oauth-confidential-node-example) is a teaching sample for a **confidential, server-side web client**. It uses Node.js, TypeScript, and the maintained [`openid-client`](https://github.com/panva/openid-client) library to demonstrate:
|
|
15
|
+
|
|
16
|
+
- configuring a registered Lunch Money OAuth client and discovering authorization-server metadata;
|
|
17
|
+
- starting authorization with single-use `state` and S256 Proof Key for Code Exchange (PKCE);
|
|
18
|
+
- validating the callback and exchanging the code on the server;
|
|
19
|
+
- calling `GET /v2/me` with a server-held access token;
|
|
20
|
+
- optionally refreshing and atomically replacing the credential set for a client registered with `offline_access`;
|
|
21
|
+
- revoking the access token and verifying that Lunch Money rejects it; and
|
|
22
|
+
- resetting only the sample's local state so the flow can be repeated.
|
|
23
|
+
|
|
24
|
+
> [!WARNING] Teaching sample, not a production application
|
|
25
|
+
> The sample uses a fixed demonstration user and in-memory stores. It does not provide real application authentication, durable multi-user isolation, production credential storage, deployment configuration, or operational controls. Review its [security guidance](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/SECURITY.md) and [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/PRODUCTION_CHECKLIST.md) before adapting any part of it.
|
|
26
|
+
|
|
27
|
+
<!-- BEGIN GENERATED OAUTH SAMPLE WALKTHROUGH -->
|
|
28
|
+
<!-- Source: lunch-money/lm-oauth-confidential-node-example@567516d5e32d89576323119bec7f40a7bee007b7:docs/WALKTHROUGH.md -->
|
|
29
|
+
## Try OAuth with your Lunch Money account
|
|
30
|
+
|
|
31
|
+
Use this walkthrough to authorize a real Lunch Money OAuth client, call the Lunch Money API, optionally refresh its credentials, revoke access, and repeat the flow. The sample is a confidential, server-side web client built with Node.js and TypeScript.
|
|
32
|
+
|
|
33
|
+
[`openid-client`](https://github.com/panva/openid-client) is the recommended and supported third-party Node.js library for working with Lunch Money's OAuth interfaces. The sample uses it for discovery, authorization, callback validation, token exchange, refresh, and revocation while keeping every credential on the server.
|
|
34
|
+
|
|
35
|
+
> [!IMPORTANT]
|
|
36
|
+
> This is a real OAuth flow. You will create a real client, authorize access to a real Lunch Money budgeting account, receive real credentials, and call the real Lunch Money API. Only the automated tests use mocked Lunch Money responses.
|
|
37
|
+
|
|
38
|
+
### Before you begin
|
|
39
|
+
|
|
40
|
+
You need:
|
|
41
|
+
|
|
42
|
+
- a Lunch Money account;
|
|
43
|
+
- Node.js 20 or newer;
|
|
44
|
+
- npm 11.6.2; and
|
|
45
|
+
- Git.
|
|
46
|
+
|
|
47
|
+
While a client is in development, only the Lunch Money user who created it can authorize it. Use that same Lunch Money user when the sample sends you through authorization. Other Lunch Money users can authorize the client only after it has been [reviewed and approved](https://lunchmoney.dev/oauth/review-and-approval).
|
|
48
|
+
|
|
49
|
+
Three actions and identities are involved:
|
|
50
|
+
|
|
51
|
+
- **A real Lunch Money user creates the client.** You create a real confidential OAuth client in the Developer Portal using your Lunch Money account. While the client is in development, only that same Lunch Money user can authorize it.
|
|
52
|
+
- **That same Lunch Money user authorizes the client.** When you run the sample, you sign into Lunch Money, select one of your real budgeting accounts, and grant the client real access. The sample receives real tokens and uses them to call the real Lunch Money API.
|
|
53
|
+
- **Only the sample application's user is simulated.** Most confidential web applications have their own users and login system. They must associate each Lunch Money authorization and its credentials with the correct application user. To demonstrate that boundary without building an unrelated login system, the sample uses a fixed internal identity named `local-demo-user`. A production application replaces it with an application-specific ID obtained from its authenticated server-side session. The ID should identify the user without using an email address or other personally identifiable information.
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Your real Lunch Money account
|
|
57
|
+
creates and owns the OAuth client
|
|
58
|
+
authorizes access to a real budgeting account
|
|
59
|
+
↓
|
|
60
|
+
Sample OAuth callback
|
|
61
|
+
↓
|
|
62
|
+
Credentials stored under local-demo-user
|
|
63
|
+
(the stand-in for your application's authenticated user)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The sample uses the client you register in the steps below; it does not create or modify that client. Client management remains in the Developer Portal. The automated tests use mocked HTTP responses, but the running sample connects to the configured real Lunch Money authorization and API services.
|
|
67
|
+
|
|
68
|
+
### 1. Download and install the sample
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
git clone https://github.com/lunch-money/lm-oauth-confidential-node-example.git
|
|
72
|
+
cd lm-oauth-confidential-node-example
|
|
73
|
+
npm ci
|
|
74
|
+
npm run build
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Register a confidential client
|
|
78
|
+
|
|
79
|
+
In the Lunch Money Developer Portal, create a **Confidential web client** with:
|
|
80
|
+
|
|
81
|
+
- the exact redirect URI `http://localhost:4002/oauth/callback`;
|
|
82
|
+
- the required `me:read` scope;
|
|
83
|
+
- optionally, `offline_access` if you also want to exercise refresh; and
|
|
84
|
+
- a client secret that you save securely when Lunch Money displays it.
|
|
85
|
+
|
|
86
|
+
Lunch Money uses the complete scope set registered for the client. The sample therefore does not send a `scope` parameter during authorization.
|
|
87
|
+
|
|
88
|
+
Registered scopes cannot be changed. Decide whether you want to test refresh before creating the client: adding `offline_access` later requires a replacement client registered with both `me:read` and `offline_access`, followed by a new authorization.
|
|
89
|
+
|
|
90
|
+
You do not need `offline_access` to authorize the client, call `/v2/me`, revoke access, or authorize again.
|
|
91
|
+
|
|
92
|
+
### 3. Configure the local process
|
|
93
|
+
|
|
94
|
+
In the same terminal, set the client ID and secret from the Developer Portal:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
export OAUTH_CLIENT_ID='YOUR_CLIENT_ID'
|
|
98
|
+
export OAUTH_CLIENT_SECRET='YOUR_CLIENT_SECRET'
|
|
99
|
+
export OAUTH_REDIRECT_URI='http://localhost:4002/oauth/callback'
|
|
100
|
+
export LUNCH_MONEY_API_BASE_URL='{{LUNCH_MONEY_API_BASE_URL}}'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The Developer Portal replaces `{{LUNCH_MONEY_API_BASE_URL}}` with the API base URL for the environment where you registered the client, so the alpha guide configures alpha explicitly. If you are reading this walkthrough in the sample repository with a production client, omit the `LUNCH_MONEY_API_BASE_URL` export; the sample defaults to `https://api.lunchmoney.dev`. For a beta or alpha client, use the exact URL supplied by Lunch Money. The client registration and API base URL must belong to the same environment. The sample uses that value for OAuth discovery, token operations, and Lunch Money API requests. A real application should also use [OAuth discovery](https://lunchmoney.dev/oauth/authorization-code#configure-lunch-money-with-discovery) instead of hard-coding authorization, token, and revocation endpoint URLs. `PORT` is optional and defaults to `4002`.
|
|
104
|
+
|
|
105
|
+
`SESSION_SECRET` is also optional for this local sample. If it is absent, the process creates a new random cookie-signing secret when it starts. Because all browser sessions, authorization attempts, and credentials are held in memory and cleared on restart, the generated secret can be cleared at the same time. A production application must instead provide a strong `SESSION_SECRET`, keep it stable across restarts, and not rotate it for each OAuth authorization.
|
|
106
|
+
|
|
107
|
+
Alternatively, copy [`env.example`](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/567516d5e32d89576323119bec7f40a7bee007b7/env.example) to `.env` as described in [Load settings from a `.env` file](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/567516d5e32d89576323119bec7f40a7bee007b7/README.md#load-settings-from-a-env-file).
|
|
108
|
+
|
|
109
|
+
> [!WARNING]
|
|
110
|
+
> **Keep credentials private**
|
|
111
|
+
> Keep the client secret in a private local environment or secret manager. Never paste it into documentation, AI chats or prompts, committed files, screenshots, browser code, logs, support requests, or commands retained in shared shell history.
|
|
112
|
+
|
|
113
|
+
### 4. Start the sample
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
npm run dev
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Open [http://localhost:4002](http://localhost:4002) in your browser.
|
|
120
|
+
|
|
121
|
+
If a form reports **Invalid CSRF token**, reload the page and try again. This commonly happens when the development server restarts and clears its in-memory browser session while an older page remains open. Simply leaving the unchanged running sample open does not expire the form token.
|
|
122
|
+
|
|
123
|
+
### 5. Authorize and call `/v2/me`
|
|
124
|
+
|
|
125
|
+
1. Choose **Connect Lunch Money**.
|
|
126
|
+
2. Sign into Lunch Money as the user who created the development client.
|
|
127
|
+
3. Select one of that user's budgeting accounts and approve access.
|
|
128
|
+
4. After the browser returns to `http://localhost:4002/oauth/callback`, expand **How this authorization was processed** to follow the callback, server-side code exchange, `/v2/me` identity check, and resulting active-user decision. Then choose **Call /v2/me**.
|
|
129
|
+
|
|
130
|
+
The sample calls `GET /v2/me` from its Node.js server using the server-held access token. It validates the response against the documented `userObject` schema and displays the returned profile fields. The access token is never sent to the browser.
|
|
131
|
+
|
|
132
|
+
The processing panel links each teaching step to its implementation. It deliberately does not reveal credential values. To see the normalized credential response while developing locally, set a debugger breakpoint immediately after `exchangeCallback()` returns in `completeAuthorization`, then inspect `tokenSet`. Do not print tokens in browser output or logs.
|
|
133
|
+
|
|
134
|
+
If you want to verify the access token outside the sample, copy it from the local debugger and read it silently into a temporary shell variable. This keeps the credential itself out of shell history:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
read -s LM_ACCESS_TOKEN
|
|
138
|
+
curl --fail-with-body \
|
|
139
|
+
--header "Authorization: Bearer ${LM_ACCESS_TOKEN}" \
|
|
140
|
+
"${LUNCH_MONEY_API_BASE_URL}/v2/me"
|
|
141
|
+
unset LM_ACCESS_TOKEN
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Press <kbd>Enter</kbd> after pasting the token at the hidden prompt. Treat it as a live secret: use a private local terminal, do not paste it directly into a command, and clear the variable when finished. This debugger workflow is for understanding and testing the sample. A production application should inspect encrypted credential records only through privileged operational tooling that redacts secret values.
|
|
145
|
+
|
|
146
|
+
Open Lunch Money's [Connected Apps](https://my.lunchmoney.app/connected-apps) page in another tab, keeping the sample available so you can return to it. The client you just authorized should appear with the details you registered. If you plan to exercise refresh, return to the sample without revoking access yet; revocation ends this authorization's continuing access.
|
|
147
|
+
|
|
148
|
+
### 6. Optionally refresh access
|
|
149
|
+
|
|
150
|
+
If you registered the client with `offline_access`, choose **Refresh access token**, then choose **Call /v2/me** again.
|
|
151
|
+
|
|
152
|
+
Lunch Money replaces both the access token and refresh token after a successful refresh. The sample stores the complete replacement credential set on the server before reporting success. Calling `/v2/me` again confirms that it is using the replacement access token.
|
|
153
|
+
|
|
154
|
+
If you registered only `me:read`, Lunch Money does not issue a refresh token and the refresh button does not appear. Skip this step.
|
|
155
|
+
|
|
156
|
+
The sample allows an immediate refresh so you can observe the flow. A production application normally decides when to refresh using expiration information from the token response or after an API authentication failure.
|
|
157
|
+
|
|
158
|
+
### 7. Disconnect the active budget
|
|
159
|
+
|
|
160
|
+
Choose **Disconnect active budget**.
|
|
161
|
+
|
|
162
|
+
- If the connection has a refresh token, the sample revokes it to revoke the entire grant.
|
|
163
|
+
- If the connection has only an access token, the sample revokes that access token.
|
|
164
|
+
|
|
165
|
+
The sample then calls `/v2/me` with the old access token, requires Lunch Money to return `401`, and deletes its locally stored credential. This verification demonstrates that the old access token no longer works.
|
|
166
|
+
|
|
167
|
+
To repeat the flow, choose **Connect Lunch Money** and authorize the client again.
|
|
168
|
+
|
|
169
|
+
### 8. Try user-initiated revocation
|
|
170
|
+
|
|
171
|
+
To see what happens when a Lunch Money user disconnects the client:
|
|
172
|
+
|
|
173
|
+
1. Authorize the sample again.
|
|
174
|
+
2. Open [Connected Apps](https://my.lunchmoney.app/connected-apps). Keep the sample available in another tab so you can return to it after revoking the authorization.
|
|
175
|
+
3. Revoke the client's access there.
|
|
176
|
+
4. Return to the sample and choose **Call /v2/me**.
|
|
177
|
+
|
|
178
|
+
The request should fail because Lunch Money no longer accepts the stored access token. Choose **Forget local credential only** to remove the now-unusable local credential before starting again.
|
|
179
|
+
|
|
180
|
+
### Local reset is not revocation
|
|
181
|
+
|
|
182
|
+
**Forget local credential only** clears the active budget's locally stored credential after confirmation. It does not contact Lunch Money and does not revoke active access.
|
|
183
|
+
|
|
184
|
+
When access is still active, revoke it first through **Disconnect active budget** or Lunch Money's Connected Apps page. Forget the local credential by itself only when the remote authorization has already been revoked or you deliberately want to clear this disposable local demonstration.
|
|
185
|
+
|
|
186
|
+
### Why `localhost` works
|
|
187
|
+
|
|
188
|
+
> [!NOTE]
|
|
189
|
+
> Lunch Money redirects your browser to the registered callback, and your browser connects to the sample running on your computer. Lunch Money's server does not initiate a connection to `localhost`, so this walkthrough does not require a public deployment or tunnel.
|
|
190
|
+
|
|
191
|
+
### Keep the sample local
|
|
192
|
+
|
|
193
|
+
> [!WARNING]
|
|
194
|
+
> The sample uses a fixed application identity and keeps browser sessions, authorization attempts, credentials, and refresh coordination in memory. Run it locally and do not expose it as a public application. Use a test budgeting account when practical. Restarting the sample clears its in-memory state.
|
|
195
|
+
|
|
196
|
+
After the flow succeeds, connect each action you performed to the code that implemented it, then decide how those responsibilities fit into your own application.
|
|
197
|
+
|
|
198
|
+
Before adapting the code, read the repository's [OAuth code guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/567516d5e32d89576323119bec7f40a7bee007b7/src/oauth/README.md), [security model](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/567516d5e32d89576323119bec7f40a7bee007b7/SECURITY.md), [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/567516d5e32d89576323119bec7f40a7bee007b7/PRODUCTION_CHECKLIST.md), and [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/567516d5e32d89576323119bec7f40a7bee007b7/TROUBLESHOOTING.md).
|
|
199
|
+
<!-- END GENERATED OAUTH SAMPLE WALKTHROUGH -->
|
|
200
|
+
|
|
201
|
+
## Understand the confidential teaching code
|
|
202
|
+
|
|
203
|
+
The open-source sample you just ran is designed to make the Lunch Money integration code easy to find, understand, and adapt. The modules under `src/oauth` show the OAuth responsibilities that a confidential server-side application needs: discovery, authorization attempts, callback validation, credential ownership and storage, API access, optional refresh, revocation, and safe error handling.
|
|
204
|
+
|
|
205
|
+
The sample represents an authenticated application user with a fixed local demo identity. After authorization, it uses the Lunch Money user `id` returned by `GET /v2/me` to choose which saved budgets are visible; credentials retained for another Lunch Money user remain hidden. A production confidential application replaces the fixed demo identity with the application user from its authenticated server-side session and identifies each owned connection by the returned Lunch Money user `id` and budget-account `account_id` pair.
|
|
206
|
+
|
|
207
|
+
Treat these modules as a working reference, not as a package to copy unchanged into production. The in-memory implementation demonstrates serialized refresh and complete credential replacement for one `applicationUserId` and `connectionId`, but it does not provide durable encrypted storage, database transactions, distributed per-connection locking, failed-persistence recovery across restarts, multi-instance coordination, or tenant isolation. The sample keeps those application-specific decisions outside the teaching code so you can see exactly where your implementation must provide them.
|
|
208
|
+
|
|
209
|
+
When refresh succeeds, the sample stores the replacement access token, refresh token, scope, and expiration metadata together without displaying token values. Your refresh-capable application must preserve that boundary, serialize refreshes for each application user and connection, and atomically store the complete rotated credential set. Retry transient failures according to your application's policy, but treat `invalid_grant` as terminal: stop refreshing, discard unusable credentials, and require reauthorization. Disconnect must invalidate the refresh token as well as the current access token so continuing access cannot resume.
|
|
210
|
+
|
|
211
|
+
### Follow the flow through the source
|
|
212
|
+
|
|
213
|
+
Start with the [`src/oauth` guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/src/oauth/README.md). It provides the maintained reading order for the framework-independent OAuth modules and connects each file to the stage of the flow it implements.
|
|
214
|
+
|
|
215
|
+
The OAuth teaching code is isolated under `src/oauth`. Hono, sessions, cookies, pages, and other runnable presentation code live under `src/scaffolding`, so you can replace the framework without obscuring the protocol flow. The sample's [architecture guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/ARCHITECTURE.md) explains this boundary, and its [OAuth tests](https://github.com/lunch-money/lm-oauth-confidential-node-example/tree/main/tests/oauth) mirror the recommended reading order, including focused [refresh tests](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/tests/oauth/refresh.test.ts). If your result differs, compare the stage that failed with the sample's [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/TROUBLESHOOTING.md) and the public [OAuth troubleshooting guide](/oauth/troubleshooting).
|
|
216
|
+
|
|
217
|
+
## Native Expo sample
|
|
218
|
+
|
|
219
|
+
Choose [`lm-oauth-native-expo-example`](https://github.com/lunch-money/lm-oauth-native-expo-example) for an iOS or Android application installed
|
|
220
|
+
on the user's device when there is no trusted backend that can hold a client
|
|
221
|
+
secret. It uses Expo, the platform authentication browser, authorization code
|
|
222
|
+
with S256 PKCE, and platform secure storage as a native/public client. Its
|
|
223
|
+
optional `offline_access` path demonstrates rotated refresh credentials and
|
|
224
|
+
terminal reauthorization without introducing a backend or client secret.
|
|
225
|
+
|
|
226
|
+
Do not choose it merely because your interface is responsive. Choose it because
|
|
227
|
+
the installed application performs OAuth and owns its credential. If a trusted
|
|
228
|
+
server performs the code exchange and stores credentials, use the confidential
|
|
229
|
+
Node.js sample above instead.
|
|
230
|
+
|
|
231
|
+
Unlike the confidential sample, the native sample has no separate application
|
|
232
|
+
account or server-side application-user boundary. It identifies local
|
|
233
|
+
connections directly by the Lunch Money user `id` and budget-account
|
|
234
|
+
`account_id` returned by `GET /v2/me`. It can retain connections for more than
|
|
235
|
+
one Lunch Money user on the device, but only the active user's budgets are
|
|
236
|
+
visible or selectable. Applications that add their own sign-in system must put
|
|
237
|
+
that pair inside the authenticated application user's storage and ownership
|
|
238
|
+
boundary.
|
|
239
|
+
|
|
240
|
+
The native sample defaults to the production API at
|
|
241
|
+
`https://api.lunchmoney.dev`. This Developer Portal is configured for
|
|
242
|
+
`{{LUNCH_MONEY_API_BASE_URL}}`. If this differs from production, set the
|
|
243
|
+
sample's API-base override before starting it:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
export EXPO_PUBLIC_LUNCH_MONEY_API_BASE_URL='{{LUNCH_MONEY_API_BASE_URL}}'
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Register the OAuth client in the same environment as that API base URL. A
|
|
250
|
+
client registration from one environment cannot be used with another
|
|
251
|
+
environment's authorization server and API.
|
|
252
|
+
|
|
253
|
+
The native repository's [walkthrough](https://github.com/lunch-money/lm-oauth-native-expo-example/blob/main/docs/WALKTHROUGH.md) is the canonical hands-on guide;
|
|
254
|
+
complete the registration, development-build, authorization, `/v2/me`,
|
|
255
|
+
optional refresh, revocation, and local-reset walkthrough there.
|
|
256
|
+
|
|
257
|
+
### Follow the native flow through the source
|
|
258
|
+
|
|
259
|
+
Start with the native sample's [`src/oauth` guide](https://github.com/lunch-money/lm-oauth-native-expo-example/blob/main/src/oauth/README.md). It provides the maintained reading order from public configuration and Expo's system-browser adapter through callback validation, secure credential storage, optional refresh, revocation, and the workflow connected to the sample screen.
|
|
260
|
+
|
|
261
|
+
The OAuth teaching code is isolated under `src/oauth`; React Native UI and platform storage adapters live under `src/scaffolding`. Use the sample's [OAuth tests](https://github.com/lunch-money/lm-oauth-native-expo-example/tree/main/tests) to follow the expected success and failure behavior, then review its [security guidance](https://github.com/lunch-money/lm-oauth-native-expo-example/blob/main/SECURITY.md) and [production checklist](https://github.com/lunch-money/lm-oauth-native-expo-example/blob/main/PRODUCTION_CHECKLIST.md) before adapting it.
|
|
262
|
+
|
|
263
|
+
Next: [Develop and test your OAuth application](/oauth/development) and [operate it securely](/oauth/security).
|
package/docs/oauth/scopes.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# OAuth scopes
|
|
2
2
|
|
|
3
|
-
Choose the smallest complete set of
|
|
3
|
+
Scopes are named permissions that define what an OAuth client may do with Lunch Money data. Choose the smallest complete set of scopes your application needs. This page is generated from the published `@lunch-money/v2-api-spec/oauth-scopes` catalog; do not edit it by hand.
|
|
4
4
|
|
|
5
5
|
> [!NOTE] Scopes are fixed when you create a client
|
|
6
|
-
> Authorization requests omit `scope`. To change
|
|
6
|
+
> Authorization requests omit `scope`. To change scopes later, create and review a replacement client, update your integration, and ask every user to authorize it.
|
|
7
7
|
|
|
8
8
|
## Scope groups
|
|
9
9
|
|
|
10
|
-
Groups are selection shortcuts, not
|
|
10
|
+
Groups are selection shortcuts, not additional scopes. Every predefined group includes `me:read` so an application can identify the authorized Lunch Money user and budget account. A group expands to the exact scopes listed below.
|
|
11
11
|
|
|
12
12
|
### Read only
|
|
13
13
|
|
|
@@ -19,25 +19,25 @@ View all supported Lunch Money data without changing it
|
|
|
19
19
|
|
|
20
20
|
Import, export, organize, and manage transactions and their attachments
|
|
21
21
|
|
|
22
|
-
[`transactions:read`](#scope-transactions-read), [`transactions:create`](#scope-transactions-create), [`transactions:update`](#scope-transactions-update), [`transactions:delete`](#scope-transactions-delete), [`transaction_attachments:read`](#scope-transaction-attachments-read), [`transaction_attachments:create`](#scope-transaction-attachments-create), [`transaction_attachments:delete`](#scope-transaction-attachments-delete), [`categories:read`](#scope-categories-read), [`tags:read`](#scope-tags-read), [`manual_accounts:read`](#scope-manual-accounts-read), [`plaid_accounts:read`](#scope-plaid-accounts-read), [`recurring_items:read`](#scope-recurring-items-read)
|
|
22
|
+
[`me:read`](#scope-me-read), [`transactions:read`](#scope-transactions-read), [`transactions:create`](#scope-transactions-create), [`transactions:update`](#scope-transactions-update), [`transactions:delete`](#scope-transactions-delete), [`transaction_attachments:read`](#scope-transaction-attachments-read), [`transaction_attachments:create`](#scope-transaction-attachments-create), [`transaction_attachments:delete`](#scope-transaction-attachments-delete), [`categories:read`](#scope-categories-read), [`tags:read`](#scope-tags-read), [`manual_accounts:read`](#scope-manual-accounts-read), [`plaid_accounts:read`](#scope-plaid-accounts-read), [`recurring_items:read`](#scope-recurring-items-read)
|
|
23
23
|
|
|
24
24
|
### Budgeting
|
|
25
25
|
|
|
26
26
|
Analyze and manage budgets, categories, and tags
|
|
27
27
|
|
|
28
|
-
[`summary:read`](#scope-summary-read), [`budgets:read`](#scope-budgets-read), [`budgets:update`](#scope-budgets-update), [`budgets:delete`](#scope-budgets-delete), [`categories:read`](#scope-categories-read), [`categories:create`](#scope-categories-create), [`categories:update`](#scope-categories-update), [`categories:delete`](#scope-categories-delete), [`tags:read`](#scope-tags-read), [`tags:create`](#scope-tags-create), [`tags:update`](#scope-tags-update), [`tags:delete`](#scope-tags-delete), [`recurring_items:read`](#scope-recurring-items-read)
|
|
28
|
+
[`me:read`](#scope-me-read), [`summary:read`](#scope-summary-read), [`budgets:read`](#scope-budgets-read), [`budgets:update`](#scope-budgets-update), [`budgets:delete`](#scope-budgets-delete), [`categories:read`](#scope-categories-read), [`categories:create`](#scope-categories-create), [`categories:update`](#scope-categories-update), [`categories:delete`](#scope-categories-delete), [`tags:read`](#scope-tags-read), [`tags:create`](#scope-tags-create), [`tags:update`](#scope-tags-update), [`tags:delete`](#scope-tags-delete), [`recurring_items:read`](#scope-recurring-items-read)
|
|
29
29
|
|
|
30
30
|
### Accounts
|
|
31
31
|
|
|
32
32
|
Manage accounts, balances, and balance history
|
|
33
33
|
|
|
34
|
-
[`manual_accounts:read`](#scope-manual-accounts-read), [`manual_accounts:create`](#scope-manual-accounts-create), [`manual_accounts:update`](#scope-manual-accounts-update), [`manual_accounts:delete`](#scope-manual-accounts-delete), [`plaid_accounts:read`](#scope-plaid-accounts-read), [`plaid_accounts:update`](#scope-plaid-accounts-update), [`crypto_manual:read`](#scope-crypto-manual-read), [`crypto_manual:create`](#scope-crypto-manual-create), [`crypto_manual:update`](#scope-crypto-manual-update), [`crypto_manual:delete`](#scope-crypto-manual-delete), [`crypto_synced:read`](#scope-crypto-synced-read), [`crypto_synced:update`](#scope-crypto-synced-update), [`balance_history:read`](#scope-balance-history-read), [`balance_history:update`](#scope-balance-history-update), [`balance_history:delete`](#scope-balance-history-delete)
|
|
34
|
+
[`me:read`](#scope-me-read), [`manual_accounts:read`](#scope-manual-accounts-read), [`manual_accounts:create`](#scope-manual-accounts-create), [`manual_accounts:update`](#scope-manual-accounts-update), [`manual_accounts:delete`](#scope-manual-accounts-delete), [`plaid_accounts:read`](#scope-plaid-accounts-read), [`plaid_accounts:update`](#scope-plaid-accounts-update), [`crypto_manual:read`](#scope-crypto-manual-read), [`crypto_manual:create`](#scope-crypto-manual-create), [`crypto_manual:update`](#scope-crypto-manual-update), [`crypto_manual:delete`](#scope-crypto-manual-delete), [`crypto_synced:read`](#scope-crypto-synced-read), [`crypto_synced:update`](#scope-crypto-synced-update), [`balance_history:read`](#scope-balance-history-read), [`balance_history:update`](#scope-balance-history-update), [`balance_history:delete`](#scope-balance-history-delete)
|
|
35
35
|
|
|
36
36
|
### Organize
|
|
37
37
|
|
|
38
38
|
Manage categories and tags used to organize transactions
|
|
39
39
|
|
|
40
|
-
[`categories:read`](#scope-categories-read), [`categories:create`](#scope-categories-create), [`categories:update`](#scope-categories-update), [`categories:delete`](#scope-categories-delete), [`tags:read`](#scope-tags-read), [`tags:create`](#scope-tags-create), [`tags:update`](#scope-tags-update), [`tags:delete`](#scope-tags-delete), [`recurring_items:read`](#scope-recurring-items-read)
|
|
40
|
+
[`me:read`](#scope-me-read), [`categories:read`](#scope-categories-read), [`categories:create`](#scope-categories-create), [`categories:update`](#scope-categories-update), [`categories:delete`](#scope-categories-delete), [`tags:read`](#scope-tags-read), [`tags:create`](#scope-tags-create), [`tags:update`](#scope-tags-update), [`tags:delete`](#scope-tags-delete), [`recurring_items:read`](#scope-recurring-items-read)
|
|
41
41
|
|
|
42
42
|
### Full access
|
|
43
43
|
|
package/docs/oauth/security.md
CHANGED
|
@@ -20,7 +20,37 @@ Keeping access tokens on the server reduces their exposure to browser code. Usin
|
|
|
20
20
|
|
|
21
21
|
In the browser-based model, the short-lived access token is also visible to the signed-in user in the request's `Authorization` header. That visibility is expected and is not, by itself, a credential leak. The tradeoff is that a malicious script running on the page—or someone with access to that browser session—could copy the bearer token and use it until it expires or is revoked.
|
|
22
22
|
|
|
23
|
-
If your browser code uses access tokens, keep them available for no longer than necessary, avoid persistent browser storage when practical, and protect the application against cross-site scripting. Regardless of the architecture, keep the client secret and refresh tokens on the server, never put bearer tokens in URLs or logs, and associate each authorization with the correct application user and Lunch Money
|
|
23
|
+
If your browser code uses access tokens, keep them available for no longer than necessary, avoid persistent browser storage when practical, and protect the application against cross-site scripting. Regardless of the architecture, keep the client secret and refresh tokens on the server, never put bearer tokens in URLs or logs, and associate each authorization with the correct application user and Lunch Money budget account.
|
|
24
|
+
|
|
25
|
+
## Associate credentials with your application user
|
|
26
|
+
|
|
27
|
+
A confidential application must authenticate its own user before starting Lunch Money OAuth. Bind each authorization attempt to that authenticated user's server-side session, then recover the owner from the consumed server-side attempt after the callback is verified. Do not let the callback choose who owns the resulting credentials.
|
|
28
|
+
|
|
29
|
+
Store each budget-account connection under a stable internal application-user ID. Within that owner, use both the Lunch Money user `id` and budget-account `account_id` returned by `GET /v2/me` as the provider identity. Your database may assign a separate internal connection ID for routing and references; it does not replace the `(application user ID, Lunch Money user id, account_id)` fields used to create or update a provider connection. Do not use an email address, callback parameter, form field, browser cookie, or other browser-supplied value to establish the credential owner or provider identity.
|
|
30
|
+
|
|
31
|
+
An application user may own connections for more than one Lunch Money user, although applications do not have to expose that capability. If your application supports only one Lunch Money identity per application user, detect a different `id` after `GET /v2/me` and handle it explicitly; never overwrite the existing identity's credentials.
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Authenticated application user
|
|
35
|
+
-> owned connection (Lunch Money user id + account_id)
|
|
36
|
+
-> encrypted credential set
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
When the user returns to your application, authenticate them through your application's own sign-in system before listing or using their saved connections. Knowing a Lunch Money `id`, `account_id`, or internal connection ID does not prove ownership. Limit connection lookups to the authenticated application user's records, and reject requests for a connection they do not own before loading its credentials.
|
|
40
|
+
|
|
41
|
+
For example, if Alex and Sam both use your application, Sam must not be able to use Alex's saved credentials by submitting Alex's connection ID. This remains true even if they both have access to the same Lunch Money budget account. Each application user must authorize their own connection. If Alex has authorized two budget accounts, both connections belong to Alex's application user and may be listed and selected within Alex's authenticated session. Each connection still uses the credential set for its own Lunch Money `id`/`account_id` pair.
|
|
42
|
+
|
|
43
|
+
Enforce this ownership and identity check for every API request, refresh, reauthorization, revocation, and deletion involving a saved connection. Background jobs must carry the connection's stored owner and identity so they use the same checks without an interactive session. Updating credentials after reauthorization must remain within the original application user's connections and match both the returned Lunch Money `id` and `account_id`. A different Lunch Money user authorizing the same shared account must not silently overwrite an existing connection.
|
|
44
|
+
|
|
45
|
+
Durable credentials must be encrypted at rest, and backend access should be limited to the services and operators that need it. Encryption protects stored secrets; your application must still enforce ownership when it reads or uses them. Never return refresh tokens or client secrets to browser JavaScript. List connections using non-secret metadata such as `budget_name`, and keep their credentials in the protected store.
|
|
46
|
+
|
|
47
|
+
Apply API calls, refresh, revocation, local deletion, and caches only to the active Lunch Money user and budget-account connection. Disconnecting a budget account should revoke continuing access for that connection and remove its locally stored credentials while keeping the active user's other connections intact. If the active user's final connection is removed, enter a disconnected state rather than falling back to another Lunch Money user's hidden connection.
|
|
48
|
+
|
|
49
|
+
On application sign-out or Lunch Money user switching, clear session-specific selections and displayed or cached data so the newly active identity cannot see the previous identity's information. An application may retain the previous Lunch Money user's encrypted credentials when its ownership model permits it, but those connections must remain hidden and unavailable until that identity becomes active again. It may instead delete credentials during its own sign-out or switching lifecycle; that is an application policy.
|
|
50
|
+
|
|
51
|
+
Keep remote revocation and local credential deletion separate. A local-only “forget” action does not end the remote authorization, so confirm that action and explain that access remains active at Lunch Money. Signing out of your application, hiding a retained connection, forgetting a local credential, and disconnecting Lunch Money are distinct lifecycle events.
|
|
52
|
+
|
|
53
|
+
The [confidential Node.js sample](/oauth/sample-applications#understand-the-confidential-teaching-code) makes this boundary explicit in its [`CredentialStore`](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/src/oauth/tokens.ts) abstraction. Its [security guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/SECURITY.md) explains the threat model, and its [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/PRODUCTION_CHECKLIST.md) lists the storage, tenant-isolation, lifecycle, and operational work the teaching sample deliberately omits. Those boundaries matter, but their implementation and database schema are specific to your application.
|
|
24
54
|
|
|
25
55
|
## Secure authorization and callbacks
|
|
26
56
|
|
|
@@ -37,7 +67,7 @@ Choose only the resource scopes needed for expected features. Add `offline_acces
|
|
|
37
67
|
|
|
38
68
|
## Operate credentials safely
|
|
39
69
|
|
|
40
|
-
Rotate client secrets on a schedule appropriate for your application's risk and immediately when one may have been exposed. Use overlapping active secrets during a controlled rotation: create the replacement, deploy and verify it, and then revoke the old secret. Coordinate refreshes so two workers do not replay the same rotating token.
|
|
70
|
+
Rotate client secrets on a schedule appropriate for your application's risk and immediately when one may have been exposed. A client can have up to three non-revoked secrets, so confirm that it has room before starting a rotation. Expired secrets still count toward this limit until you revoke them. Use overlapping active secrets during a controlled rotation: create the replacement, deploy and verify it, and then revoke the old secret. Coordinate refreshes so two workers do not replay the same rotating token, and atomically persist every complete replacement credential set. Production storage needs database transactions, distributed per-connection locking, recovery when persistence fails, multi-instance coordination, and tenant isolation. Make disconnect invalidate continuing refresh access as well as clear local state.
|
|
41
71
|
|
|
42
72
|
If a credential may have been exposed, stop using it, revoke it, rotate the relevant client secret, review logs without copying secret values, and require reauthorization when the grant can no longer be trusted.
|
|
43
73
|
|
package/docs/oauth/tokens.md
CHANGED
|
@@ -8,19 +8,23 @@ Teal boxes show the normal token lifecycle. The amber box marks a terminal failu
|
|
|
8
8
|
|
|
9
9
|
## Determine expiration
|
|
10
10
|
|
|
11
|
+
Keep each token set associated with the application user and the authorized Lunch Money user and budget account pair. Renewing credentials does not change that pair; selecting another budget account requires a new authorization. See [users, budget accounts, and access tokens](/oauth/concepts#users-budget-accounts-and-access-tokens).
|
|
12
|
+
|
|
11
13
|
Access tokens are deliberately short-lived. Record when the token response arrives and add its `expires_in` duration to determine the expected expiry. Refresh slightly before that point while accounting for clock skew. Do not parse the opaque access token or assume a fixed lifetime.
|
|
12
14
|
|
|
13
15
|
## Refresh access
|
|
14
16
|
|
|
15
17
|
A refresh token is issued after a successful authorization-code exchange only when the registered client includes `offline_access`. It lasts longer than an access token, but it is not permanent.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
The [confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account) keeps refresh optional within its first-run walkthrough. Register the sample client with `offline_access` to exercise the refresh branch, or omit it to focus on authorization, `GET /v2/me`, revocation, and reauthorization.
|
|
20
|
+
|
|
21
|
+
Send a form-encoded `grant_type=refresh_token` request to the discovered token endpoint. Confidential clients authenticate with `client_secret_basic`; public clients send their client ID without a secret. A refresh never expands or switches the grant's registered scopes, Lunch Money user, or budget-account identity.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
Atomically persist the complete replacement token set before discarding the previous value, and serialize refreshes for each active application-user and Lunch Money user/account connection. Lunch Money rotates refresh tokens; replay of a consumed token can invalidate the token family. Treat transient failures according to a bounded retry policy, but treat `invalid_grant` as terminal: delete unusable credentials for that connection and start a fresh authorization.
|
|
20
24
|
|
|
21
25
|
## Revoke access
|
|
22
26
|
|
|
23
|
-
Send the token to the discovered revocation endpoint using the authentication method appropriate to the client. Revocation returns success without revealing unnecessary token state. Locally discard
|
|
27
|
+
Send the active connection's token to the discovered revocation endpoint using the authentication method appropriate to the client. Revocation returns success without revealing unnecessary token state. Locally discard that connection's access token, refresh token, and related session state even if the network response is ambiguous. Keep other user/account connections unchanged.
|
|
24
28
|
|
|
25
29
|
> [!TIP] Revocation endpoint
|
|
26
30
|
> The currently resolved endpoint is `https://api.lunchmoney.dev/oauth/revoke`. Use authorization-server discovery to configure deployed applications so they receive the current endpoint automatically.
|
|
@@ -55,6 +59,10 @@ Do not paste a real client secret or token directly into a command where it may
|
|
|
55
59
|
|
|
56
60
|
Revocation is different from expiration: expiration ends one credential naturally, while revocation deliberately removes access. Deleting or disabling an application and loss of user access can also make tokens unusable.
|
|
57
61
|
|
|
62
|
+
A Lunch Money user can also revoke an application's access from the [Connected Apps page](https://my.lunchmoney.app/connected-apps). User revocation applies to the selected Lunch Money user and budget-account authorization. Your application should treat the resulting authentication failure as terminal for that connection and ask the user to authorize it again when they want to reconnect. Keep every stored Lunch Money user/account connection independent, including when two users have authorized the same shared `account_id`.
|
|
63
|
+
|
|
64
|
+
Remote revocation is different from local credential deletion. If an application offers a local-only “forget” action, confirm it and explain that the remote authorization remains active. Removing the active user's final local connection should leave the application disconnected rather than expose a hidden connection belonging to another Lunch Money user.
|
|
65
|
+
|
|
58
66
|
## Recovery rules
|
|
59
67
|
|
|
60
68
|
- A rejected API request may mean the access token expired, was revoked, lacks the required scope, targets the wrong resource, or no longer maps to an accessible account. The `401` response does not identify which cause applies. Follow the [API failure decision tree](/oauth/troubleshooting#api-failures).
|
|
@@ -8,6 +8,8 @@ OAuth problems are usually easiest to resolve once you identify where the flow s
|
|
|
8
8
|
|
|
9
9
|
When collecting diagnostic details, preserve nonsensitive information such as timestamps and error names, but leave credentials and full callback URLs out of logs and support messages.
|
|
10
10
|
|
|
11
|
+
For a runnable comparison, [try OAuth with the confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account). Reproduce the same stage in its end-to-end flow, then compare the result with the sample repository's [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/TROUBLESHOOTING.md) without copying credentials or full callback URLs.
|
|
12
|
+
|
|
11
13
|
> [!NOTE] Two error formats
|
|
12
14
|
> OAuth protocol endpoints under `/oauth/` use lower-case OAuth error names such as `invalid_grant`. The v2 API uses the Lunch Money response format, with a `message` and an `errors` array whose entries contain `errMsg`.
|
|
13
15
|
|
|
@@ -82,12 +84,12 @@ If a successful authorization-code exchange does not include a refresh token, co
|
|
|
82
84
|
Treat an OAuth `401` as an unknown access-token failure unless a refresh request establishes what happened. When an eligible refresh token is available, attempt one refresh and handle the token endpoint's result, which uses the OAuth format shown above rather than the v2 API format:
|
|
83
85
|
|
|
84
86
|
1. On success, save the replacement tokens and retry the API request once. The access token had merely expired.
|
|
85
|
-
2. On `invalid_grant`, treat
|
|
87
|
+
2. On `invalid_grant`, treat that connection as revoked: mark the connection for the selected user/account as inactive and discard its tokens. Prompt the user to authorize that connection again the next time they need it rather than repeatedly interrupting them. Leave the user's other budget-account connections unchanged.
|
|
86
88
|
3. On `invalid_client`, stop requests for every user of the client and contact [developer support](mailto:developer-support@lunchmoney.app). Lunch Money may have disabled the client; retrying or asking users to authorize again will not help.
|
|
87
89
|
|
|
88
90
|
If no eligible refresh token is available, discard the rejected access token and ask the user to authorize again.
|
|
89
91
|
|
|
90
|
-
An OAuth access token can fail because it expired, access was revoked, the client was disabled, the
|
|
92
|
+
An OAuth access token can fail because it expired, access was revoked, the client was disabled, the token was revoked or rotated, or the user lost access to the budget account. Every OAuth access-token failure returns the same generic `401` response:
|
|
91
93
|
|
|
92
94
|
```http
|
|
93
95
|
HTTP/1.1 401
|
|
@@ -127,12 +129,24 @@ WWW-Authenticate: Bearer error="insufficient_scope", scope="transactions:read"
|
|
|
127
129
|
}
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
The `scope` parameter names every scope the endpoint requires, including any the client may already have. It is not a list of only the missing scopes. Check the operation in the [V2 API reference](/v2/docs) or
|
|
132
|
+
The `scope` parameter names every scope the endpoint requires, including any the client may already have. It is not a list of only the missing scopes. Check the operation in the [V2 API reference](/v2/docs) or [OAuth scopes](/oauth/scopes) to confirm its requirements.
|
|
131
133
|
|
|
132
134
|
### Replace a client to change scopes
|
|
133
135
|
|
|
134
136
|
Adding `scope` to an authorization URL cannot upgrade or narrow a grant. Lunch Money ignores the requested value and uses the client's complete registered scope set. To change scopes, create a replacement client, test all functionality, complete any required review, update the deployed client ID and credentials, and send every existing user through authorization again.
|
|
135
137
|
|
|
138
|
+
## Client-management failures
|
|
139
|
+
|
|
140
|
+
The Developer Portal can return structured errors when you register clients, create client secrets, or submit a client for review. These errors use the Lunch Money response format, not OAuth protocol errors such as `invalid_grant`.
|
|
141
|
+
|
|
142
|
+
| Status and code | Meaning | What to do |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `422 quota_exceeded`, field `clients` | You already own 25 non-deleted OAuth clients. | Delete a client you no longer use before registering another. |
|
|
145
|
+
| `422 quota_exceeded`, field `secrets` | The client already has three non-revoked secrets. Expired secrets still count. | Revoke an obsolete or expired secret before creating another. |
|
|
146
|
+
| `429 review_quota_exceeded` | You have made five initial review submissions across your clients in the rolling 24-hour window. | Wait until the time shown in the Developer Portal before submitting again. API clients should also respect the `Retry-After` response header. |
|
|
147
|
+
|
|
148
|
+
Cancelled submissions, rejected-client resubmissions, and submissions for clients later deleted still count toward the review limit. Reviews opened automatically after an active client's identity changes do not count.
|
|
149
|
+
|
|
136
150
|
## Local callback failures
|
|
137
151
|
|
|
138
152
|
For local development, use a supported loopback host: `localhost`, `127.0.0.1`, or `[::1]`. Matching depends on the client type:
|
package/manifest.json
CHANGED
|
@@ -105,6 +105,14 @@
|
|
|
105
105
|
"type": "markdown",
|
|
106
106
|
"aliases": ["/v2/oauth/authorization-code"]
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
"path": "/oauth/sample-applications",
|
|
110
|
+
"file": "docs/oauth/sample-applications.md",
|
|
111
|
+
"title": "OAuth Sample Applications",
|
|
112
|
+
"section": "OAUTH",
|
|
113
|
+
"type": "markdown",
|
|
114
|
+
"aliases": ["/v2/oauth/sample-applications"]
|
|
115
|
+
},
|
|
108
116
|
{
|
|
109
117
|
"path": "/oauth/development",
|
|
110
118
|
"file": "docs/oauth/development.md",
|
|
@@ -304,13 +312,14 @@
|
|
|
304
312
|
{ "label": "Concepts", "path": "/oauth/concepts" },
|
|
305
313
|
{ "label": "Register a Client", "path": "/oauth/register-client" },
|
|
306
314
|
{ "label": "Implement Authorization", "path": "/oauth/authorization-code" },
|
|
315
|
+
{ "label": "Sample Applications", "path": "/oauth/sample-applications" },
|
|
307
316
|
{ "label": "Develop and Test", "path": "/oauth/development" },
|
|
308
317
|
{ "label": "Token Lifecycle", "path": "/oauth/tokens" },
|
|
309
318
|
{ "label": "Security", "path": "/oauth/security" },
|
|
310
319
|
{ "label": "Troubleshooting", "path": "/oauth/troubleshooting" },
|
|
311
320
|
{ "label": "Review and Approval", "path": "/oauth/review-and-approval" },
|
|
312
321
|
{ "label": "Native Apps", "path": "/oauth/native-apps" },
|
|
313
|
-
{ "label": "
|
|
322
|
+
{ "label": "Scopes", "path": "/oauth/scopes" }
|
|
314
323
|
]},
|
|
315
324
|
{ "section": "REFERENCE", "items": [
|
|
316
325
|
{ "label": "v2 API Overview", "path": "/v2/overview" },
|
package/package.json
CHANGED
package/v1/_user.md
CHANGED
|
@@ -11,8 +11,8 @@ This API (v1) will not progress to a generally available state. For new projects
|
|
|
11
11
|
| user_id | number | false | Unique identifier for user |
|
|
12
12
|
| user_name | string | false | User's name |
|
|
13
13
|
| user_email | string | false | User's email |
|
|
14
|
-
| account_id | number | false | Unique identifier for the associated
|
|
15
|
-
| budget_name | string | false | Name of the associated
|
|
14
|
+
| account_id | number | false | Unique identifier for the associated budget account |
|
|
15
|
+
| budget_name | string | false | Name of the associated budget account |
|
|
16
16
|
| primary_currency|string | false | Primary currency from user's settings |
|
|
17
17
|
| api_key_label | string | true | User-defined label of the developer API key used. Returns null if nothing has been set. |
|
|
18
18
|
|
package/v2/docs/intro-to-v2.md
CHANGED
|
@@ -10,7 +10,7 @@ All API requests require a Bearer token in the `Authorization` header:
|
|
|
10
10
|
Authorization: Bearer YOUR_ACCESS_TOKEN
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Use a personal access token for API exploration or work with your own
|
|
13
|
+
Use a personal access token for API exploration or work with your own budget account. The [Getting started guide](/getting-started) walks through creating one on the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Lunch Money Developers page</a> and making your first request. Applications built for other Lunch Money users should use an [OAuth access token issued after the user authorizes the application](/oauth).
|
|
14
14
|
|
|
15
15
|
## Base URL
|
|
16
16
|
|
|
@@ -7,13 +7,15 @@ The Lunch Money API spec uses a modified version of SEMVER for its versioning me
|
|
|
7
7
|
|
|
8
8
|
## v2.11.2 - TBD
|
|
9
9
|
- Add OAuth 2.0 bearer-token authentication to the v2 API, allowing applications to access Lunch Money on behalf of users who authorize them
|
|
10
|
-
-
|
|
10
|
+
- Document and enforce required OAuth scopes for every v2 operation and publish the scope and scope-group catalog mapped to V2 `operationId` values.
|
|
11
|
+
- Added a new `403` `insufficient_scope` response
|
|
11
12
|
- Document the OAuth 2.0 authorization-code flow used to obtain access tokens
|
|
13
|
+
- Add `include_files` query parameter to `GET /transactions/{id}`
|
|
12
14
|
|
|
13
15
|
## v2.11.1 - TBD
|
|
14
16
|
- Add `GET /me/account/settings` and `PUT /me/account/settings` for account-level settings
|
|
15
17
|
- Add `GET /me/user/settings` and `PUT /me/user/settings` for user-level display and formatting preferences
|
|
16
|
-
- Add `GET /me/user/account/settings` and `PUT /me/user/account/settings` for settings specific to a user and
|
|
18
|
+
- Add `GET /me/user/account/settings` and `PUT /me/user/account/settings` for settings specific to a user and budget account
|
|
17
19
|
- Add `include_pending_in_totals` to `GET /me/account/settings` and `PUT /me/account/settings`
|
|
18
20
|
- Add `default_manual_account_id` to `GET /me/user/account/settings` and `PUT /me/user/account/settings`
|
|
19
21
|
- Document `GET /budgets/settings` response schema publicly; change `budget_period_quantity` to integer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="1120" height="
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1120" height="960" viewBox="0 0 1120 960" role="img" aria-labelledby="title desc">
|
|
2
2
|
<title id="title">Lunch Money OAuth authorization-code flow</title>
|
|
3
|
-
<desc id="desc">A sequence diagram with three actors: your application, the Lunch Money user, and Lunch Money. The application redirects the user's browser to Lunch Money. When necessary, Lunch Money presents its sign-in screen and the user signs in directly with Lunch Money. Lunch Money then presents the
|
|
3
|
+
<desc id="desc">A sequence diagram with three actors: your application, the Lunch Money user, and Lunch Money. The application redirects the user's browser to Lunch Money. When necessary, Lunch Money presents its sign-in screen and the user signs in directly with Lunch Money. Lunch Money then presents the budget-account selector and requested scopes, the user selects an account and approves access, Lunch Money returns an authorization code, and the application exchanges it for tokens. The application calls GET /v2/me, saves the identified connection, and calls the API for that account.</desc>
|
|
4
4
|
<defs>
|
|
5
5
|
<marker id="app-arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto"><path d="M0,0 L0,6 L9,3 z" fill="#b45309"/></marker>
|
|
6
6
|
<marker id="user-arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto"><path d="M0,0 L0,6 L9,3 z" fill="#9a6700"/></marker>
|
|
@@ -18,15 +18,18 @@
|
|
|
18
18
|
<text class="actor-label" x="560" y="56" text-anchor="middle">Lunch Money user</text><text class="actor-detail" x="560" y="79" text-anchor="middle">browser, sign-in, and approval</text>
|
|
19
19
|
<rect class="actor lm" x="820" y="25" width="260" height="72" rx="12"/>
|
|
20
20
|
<text class="actor-label" x="950" y="56" text-anchor="middle">Lunch Money</text><text class="actor-detail" x="950" y="79" text-anchor="middle">authorization server and API</text>
|
|
21
|
-
<path class="lifeline" d="M170 105
|
|
21
|
+
<path class="lifeline" d="M170 105 V930"/><path class="lifeline" d="M560 105 V930"/><path class="lifeline" d="M950 105 V930"/>
|
|
22
22
|
<path class="flow app-flow" d="M170 140 H550"/><rect class="step-bg" x="278" y="118" width="180" height="28" rx="6"/><text class="step" x="368" y="137" text-anchor="middle">1. Redirect the user</text>
|
|
23
23
|
<path class="flow user-flow" d="M560 200 H940"/><rect class="step-bg" x="652" y="178" width="196" height="28" rx="6"/><text class="step" x="750" y="197" text-anchor="middle">2. Request authorization</text>
|
|
24
24
|
<path class="flow lm-flow" d="M950 260 H570"/><rect class="step-bg" x="640" y="238" width="240" height="28" rx="6"/><text class="step" x="760" y="257" text-anchor="middle">3. Show sign-in, when needed</text>
|
|
25
25
|
<path class="flow user-flow" d="M560 320 H940"/><rect class="step-bg" x="605" y="298" width="290" height="28" rx="6"/><text class="step" x="750" y="317" text-anchor="middle">4. Sign in directly with Lunch Money</text>
|
|
26
|
-
<path class="flow lm-flow" d="M950 380 H570"/><rect class="step-bg" x="
|
|
27
|
-
<path class="flow user-flow" d="M560 440 H940"/><rect class="step-bg" x="585" y="418" width="330" height="28" rx="6"/><text class="step" x="750" y="437" text-anchor="middle">6. Select
|
|
26
|
+
<path class="flow lm-flow" d="M950 380 H570"/><rect class="step-bg" x="545" y="358" width="430" height="28" rx="6"/><text class="step" x="760" y="377" text-anchor="middle">5. Present budget-account selector and scopes</text>
|
|
27
|
+
<path class="flow user-flow" d="M560 440 H940"/><rect class="step-bg" x="585" y="418" width="330" height="28" rx="6"/><text class="step" x="750" y="437" text-anchor="middle">6. Select budget account and approve</text>
|
|
28
28
|
<path class="flow lm-flow" d="M950 500 H180"/><rect class="step-bg" x="385" y="478" width="360" height="28" rx="6"/><text class="step" x="565" y="497" text-anchor="middle">7. Redirect to callback with authorization code</text>
|
|
29
29
|
<path class="flow app-flow" d="M170 580 H940"/><rect class="step-bg" x="335" y="558" width="460" height="28" rx="6"/><text class="step" x="565" y="577" text-anchor="middle">8. Exchange code with PKCE (+ client secret for confidential client)</text>
|
|
30
30
|
<path class="flow lm-flow" d="M950 640 H180"/><rect class="step-bg" x="385" y="618" width="360" height="28" rx="6"/><text class="step" x="565" y="637" text-anchor="middle">9. Return access token + optional refresh token</text>
|
|
31
|
-
<path class="flow app-flow" d="M170
|
|
31
|
+
<path class="flow app-flow" d="M170 700 H940"/><rect class="step-bg" x="430" y="678" width="270" height="28" rx="6"/><text class="step" x="565" y="697" text-anchor="middle">10. Call GET /v2/me</text>
|
|
32
|
+
<path class="flow lm-flow" d="M950 760 H180"/><rect class="step-bg" x="350" y="738" width="430" height="28" rx="6"/><text class="step" x="565" y="757" text-anchor="middle">11. Return user id, account_id, and budget_name</text>
|
|
33
|
+
<path class="flow app-flow" d="M170 800 H330 V840 H180"/><rect class="step-bg" x="285" y="798" width="330" height="28" rx="6"/><text class="step" x="450" y="817" text-anchor="middle">12. Save connection and make it active</text>
|
|
34
|
+
<path class="flow app-flow" d="M170 890 H940"/><rect class="step-bg" x="385" y="868" width="360" height="28" rx="6"/><text class="step" x="565" y="887" text-anchor="middle">13. Call the V2 API for the active account</text>
|
|
32
35
|
</svg>
|