@lunch-money/developer-docs 2.11.1-preview.9 → 2.11.2-preview.2

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.
@@ -45,7 +45,7 @@ A few common, low-friction ways to brand an integration:
45
45
 
46
46
  1. **README or docs** — a short "Built with Lunch Money" line plus the Powered by badge linking to <a href="https://lunchmoney.app/" target="_blank" rel="noopener noreferrer">lunchmoney.app</a>
47
47
  2. **App footer or about screen** — the Powered by badge next to your own branding
48
- 3. **Onboarding or connect flow** — official logo when prompting users to authorize or paste an access token
48
+ 3. **Onboarding or connect flow** — official logo when asking users to connect their Lunch Money account through OAuth. Applications intended for other users should not ask them to paste a personal access token; see the [OAuth overview](/oauth)
49
49
  4. **Community listings** — consistent naming ("My Tool for Lunch Money") so users can find and trust your project
50
50
 
51
51
  Keep your own product name primary. Lunch Money branding should signal the connection, not compete with your identity.
@@ -27,9 +27,11 @@ Choose the option to "Set up Demo". This will add categories, accounts, tags and
27
27
  ![Add Demo Data](/images/DemoData.png)
28
28
  :::
29
29
 
30
- ## Getting an access token
30
+ ## Getting a personal access token
31
31
 
32
- Lunch Money API requests are authenticated using the Bearer Token authentication method. In plain English, this means that you will pass a token associated with your budgeting account to Lunch Money. This is so we know which account to operate on and that the request came from someone who can access that account.
32
+ Lunch Money API requests are authenticated using the Bearer Token authentication method. In plain English, this means that you will pass a personal access token associated with your budgeting account to Lunch Money. This is so we know which account to operate on and that the request came from someone who can access that account.
33
+
34
+ This guide uses a personal access token so you can explore the API with your own budgeting account. If you are building an application that other people will connect to Lunch Money, start with the [OAuth overview](/oauth) instead.
33
35
 
34
36
  :::tabs
35
37
 
@@ -39,25 +41,25 @@ Navigate to <a href="https://my.lunchmoney.app/developers" target="_blank" rel="
39
41
  ![Green Budgets Icon](/images/green_budget.png)
40
42
 
41
43
  @tab 2. Request Access Token
42
- In the upper right hand corner of the page, give your access token a label and specify what you are using it for. When you're done, hit the "Request Access Token" button.
44
+ In the upper right hand corner of the page, give your personal access token a label and specify what you are using it for. When you're done, hit the "Request Access Token" button.
43
45
  ![Request Token](/images/RequestAccessToken.png)
44
46
 
45
47
  @tab 3. Save Access Token
46
- After you do this, the web page will show you a long alphanumeric string. This is your access token. Hit the copy button and save this token somewhere safe. It is only shown to you when you create it.
48
+ After you do this, the web page will show you a long alphanumeric string. This is your personal access token. Hit the copy button and save this token somewhere safe. It is only shown to you when you create it.
47
49
  :::
48
50
 
49
- Congratulations! You've got an access token and are ready to make an API request! Later, when you are more comfortable with the API, you can repeat this process to create a new access token associated with your real budget.
51
+ Congratulations! You've got a personal access token and are ready to make an API request! Later, when you are more comfortable with the API, you can repeat this process to create a new personal access token associated with your real budget.
50
52
 
51
53
  > [!WARNING]
52
- > Treat your access tokens like passwords. Don't share them with anyone you don't trust.
54
+ > Treat your personal access tokens like passwords. Don't share them with anyone you don't trust.
53
55
 
54
- If you ever worry that your access token has been compromised, you can always come back to the Developers page and hit the Revoke button.
56
+ If you ever worry that your personal access token has been compromised, you can always come back to the Developers page and hit the Revoke button.
55
57
 
56
58
  > [!NOTE]
57
- > Give your access tokens descriptive names so you can easily identify and revoke them later if needed.
59
+ > Give your personal access tokens descriptive names so you can easily identify and revoke them later if needed.
58
60
 
59
61
 
60
- ## Using your access token
62
+ ## Using your personal access token
61
63
 
62
64
  When you start to write code that makes API calls, you will need to include this token in each request using an Authorization header. Here is an example curl request:
63
65
 
@@ -68,7 +70,7 @@ curl --location 'https://api.lunchmoney.dev/v2/me' \
68
70
 
69
71
  If this doesn't make sense yet, that's OK!
70
72
 
71
- There is an easier way to use your access token to make requests. Head over to the [API Reference Documentation](/v2/docs).
73
+ There is an easier way to use your personal access token to make requests. Head over to the [API Reference Documentation](/v2/docs).
72
74
 
73
75
  :::tabs
74
76
 
@@ -85,6 +87,8 @@ This will pop up a dialog box that you can use to send an API request. This is a
85
87
 
86
88
  From here, you can explore the rest of the Lunch Money API and start thinking about what you might do with it.
87
89
 
90
+ When you are ready to build an application that other Lunch Money users can connect to, [learn how OAuth grants access without asking users to share personal access tokens](/oauth).
91
+
88
92
  ## What can you build?
89
93
 
90
94
  The Lunch Money community has already built a wide variety of tools — automations, importers, dashboards, and more. Check out the <a href="https://lunchmoney.app/developers#community-projects" target="_blank" rel="noopener noreferrer">Developer Tools & Community Projects</a> list for inspiration, or drop by the <a href="https://discord.com/channels/842337014556262411/1134597088504729780" target="_blank" rel="noopener noreferrer">Show and Tell channel</a> on Discord to see what others are sharing.
@@ -10,7 +10,7 @@ The v2 API is available at:
10
10
  https://api.lunchmoney.dev/v2
11
11
  ```
12
12
 
13
- Get your access token from the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Lunch Money app developers page</a> and include it as a Bearer token in every request:
13
+ To explore the API or work with your own budgeting account, create a personal access token on the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Developers page</a>. If you are building an application for other Lunch Money users, [use OAuth](/oauth) instead. Both kinds of access token are sent as a Bearer token with each request:
14
14
 
15
15
  ```http
16
16
  Authorization: Bearer YOUR_ACCESS_TOKEN
@@ -59,5 +59,6 @@ Join the conversation in the <a href="https://lunchmoney.app/discord" target="_b
59
59
  **Useful links:**
60
60
  - <a href="/v2/docs">Interactive API Reference</a>
61
61
  - [Getting started guide](/getting-started)
62
+ - [OAuth overview](/oauth)
62
63
  - <a href="https://github.com/lunch-money/awesome-lunchmoney" target="_blank" rel="noopener noreferrer">Awesome Lunch Money Projects</a>
63
64
  - [Contact: devsupport@lunchmoney.app](mailto:devsupport@lunchmoney.app)
@@ -15,6 +15,8 @@ OAuth libraries handle much of the protocol work for you, including generating P
15
15
 
16
16
  For Node.js and TypeScript, [`openid-client`](https://github.com/panva/openid-client) is one maintained option. Despite its name, using it does not mean that Lunch Money supports OpenID Connect identity scopes, ID tokens, or UserInfo.
17
17
 
18
+ The [confidential Node.js sample application](/oauth/sample-applications#understand-and-apply-the-teaching-code) shows this library in a complete Lunch Money flow. Begin with its [`src/oauth` guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/src/oauth/README.md), then follow the listed reading order from protocol types and discovery through authorization, callback validation, credential storage, API access, optional refresh, and revocation.
19
+
18
20
  ## Configure Lunch Money with discovery
19
21
 
20
22
  Your OAuth library needs to know where to send the user for authorization, where to exchange codes for tokens, and which protocol features Lunch Money supports. Instead of configuring each value separately, give the library the Lunch Money issuer:
@@ -7,7 +7,7 @@ These terms describe who participates in Lunch Money OAuth and what each credent
7
7
  - **User (resource owner):** the Lunch Money user who authorizes a third-party application to make API requests on their behalf for one budgeting account.
8
8
  - **Application:** the complete product or service the developer builds. It may contain many features unrelated to Lunch Money; only its Lunch Money OAuth client is managed in the Developer Portal.
9
9
  - **Client:** the application's OAuth registration with Lunch Money: its `client_id`, type, redirect URIs, selected scopes, and—when applicable—client secret. The developer must register this client in the Developer Portal before the application can ask Lunch Money users to authorize API access.
10
- - **Authorization server:** the Lunch Money-owned service that presents the Lunch Money sign-in and consent screens, records the user's decision, and issues authorization codes and tokens. The user signs in directly with Lunch Money; the third-party application never receives or sees their Lunch Money login credentials.
10
+ - **Authorization server:** the Lunch Money-owned service that presents the Lunch Money sign-in and consent screens, records the user's decision, and issues authorization codes and tokens. It also handles token requests, including exchanging authorization codes and refresh tokens for new tokens. The user signs in directly with Lunch Money; the third-party application never receives or sees their Lunch Money login credentials.
11
11
  - **Lunch Money API service:** the Lunch Money-owned V2 API, which receives the application's API requests, accepts access tokens, and checks that each request is permitted by the client's scopes.
12
12
 
13
13
  ## Client types and credentials
@@ -17,6 +17,8 @@ When a developer registers a client, they must choose a client type based on whe
17
17
  - **Confidential web client:** Runs on a server and can securely store client secrets. In OAuth terminology, this is called a *confidential client*. Choose this type for a web application—or any other application architecture with a centralized server. Its client secret helps Lunch Money verify that a token request came from the application that registered the client. The client secret and refresh tokens must stay on the server. The application must protect access tokens wherever its architecture uses them, and associate each authorization with the correct application user and authorized Lunch Money budgeting account.
18
18
  - **Native or public client:** Runs on a user-controlled device and uses PKCE without a client secret because installed application code cannot keep a secret secure. In OAuth terminology, this is called a *native public client*. Choose this type for a standalone mobile or desktop application. The app stores each user's tokens securely on that user's device—for example, in Keychain on Apple platforms or appropriate Keystore-backed storage on Android—and must avoid exposing them through backups, logs, or application data shared with other apps.
19
19
 
20
+ These client families are not interchangeable. A confidential server-side sample cannot be moved into a native application by embedding its client secret or server-held credentials; use a native-client flow and platform secure storage instead.
21
+
20
22
  Every client receives a client ID that identifies its Lunch Money registration. The client ID is not secret. Both client types also use Proof Key for Code Exchange (PKCE) to bind the authorization response to the application that started the flow.
21
23
 
22
24
  ## Redirect URIs
@@ -13,6 +13,8 @@ Use a separate Lunch Money test budgeting account when exercising create, update
13
13
 
14
14
  Keep production client secrets, tokens, and user data out of test fixtures, logs, screenshots, and bug reports.
15
15
 
16
+ If you want a working reference flow for comparison, [try OAuth with the confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account). Its `me:read` walkthrough helps validate client registration, exact local callback configuration, discovery, authorization, `GET /v2/me`, and revocation before you debug application-specific features. Add `offline_access` when registering the sample client only if you also want to exercise its optional refresh branch.
17
+
16
18
  ## Local loopback callbacks
17
19
 
18
20
  When you run a confidential web application on your own computer, its callback handler may not have a public HTTPS address yet. A loopback redirect lets the browser return the authorization result directly to the local process you are developing.
@@ -6,23 +6,23 @@ OAuth lets your application access Lunch Money on behalf of a user without askin
6
6
 
7
7
  ## Choose OAuth or a personal access token
8
8
 
9
- | Question | Personal access token | OAuth application and client |
9
+ | Question | Personal access token | OAuth client |
10
10
  | --- | --- | --- |
11
11
  | Whose data can it access? | One budgeting account belonging to the user who created the token | One budgeting account selected by each user who authorizes the application |
12
12
  | Best fit | API exploration, one-off scripts, and short-lived work on your own account | Persistent integrations and applications intended for other users |
13
13
  | How is access granted? | The user creates a token on the [Developers page](https://my.lunchmoney.app/developers) in the Lunch Money web app | The user signs in and approves the permissions requested by the application |
14
14
  | Credential handling | The user keeps the token and sends it with their own API requests; third-party applications should use OAuth instead of asking users to share one | The authorization flow delivers application-specific tokens without asking for a personal token |
15
- | Permissions | Full access to the Lunch Money APIs available for that budgeting account, with no fine-grained permission controls | Only the permissions selected when the application was created |
15
+ | Permissions | Full access to the Lunch Money APIs available for that budgeting account, with no fine-grained permission controls | Only the permissions selected when the OAuth client was registered |
16
16
  | Lifecycle | Created and revoked by the user | Short-lived access tokens and optional finite refresh access; the user can revoke the application's access |
17
17
 
18
18
  Personal access tokens remain useful for quick work on your own account. OAuth is the recommended model for applications and integrations that other Lunch Money users will connect to, as well as automations that need to run over time.
19
19
 
20
20
  ## What limits access
21
21
 
22
- When a developer creates an OAuth application, they select the scopes—the specific permissions—the application needs. Later, when a Lunch Money user authorizes the application, Lunch Money shows them that complete permission set before they approve access. Choose only the permissions the application's features require. A focused permission request is easier for users to understand and trust, and reduces the risk of the application reading or changing data it does not need.
22
+ An OAuth client is an application's registration with Lunch Money. When a developer registers the client, they select the scopes—the specific permissions—the application needs. Later, when a Lunch Money user authorizes the application, Lunch Money shows them that complete permission set before they approve access. Choose only the permissions the application's features require. A focused permission request is easier for users to understand and trust, and reduces the risk of the application reading or changing data it does not need.
23
23
 
24
24
  ## Before you start
25
25
 
26
- You need an active Lunch Money account to [register and manage an OAuth application](/oauth/applications). During development, only the application owner can authorize it. Approval is required before other Lunch Money users can authorize it.
26
+ You need an active Lunch Money account to [register and manage an OAuth client](/oauth/applications) for your application. During development, only the client owner can authorize the application. Approval is required before other Lunch Money users can authorize it.
27
27
 
28
- Start with [OAuth concepts](/oauth/concepts), [register a client](/oauth/register-client), and then [implement the authorization-code flow](/oauth/authorization-code). Use the [scope catalog](/oauth/scopes) to plan permissions and [troubleshooting](/oauth/troubleshooting) when a flow fails.
28
+ Start with [OAuth concepts](/oauth/concepts), [register a client](/oauth/register-client), and then [implement the authorization-code flow](/oauth/authorization-code). You can also run the [confidential Node.js sample application](/oauth/sample-applications) to see registration, authorization, an API call, revocation, and local reset working together. Use the [scope catalog](/oauth/scopes) to plan permissions and [troubleshooting](/oauth/troubleshooting) when a flow fails.
@@ -13,9 +13,9 @@ Use Lunch Money's authorization-server discovery document and configure `token_e
13
13
 
14
14
  ## Redirects
15
15
 
16
- Use a redirect mechanism that returns control to your app and that your platform can bind to it. Claimed Universal Links or Android App Links provide stronger app ownership than a custom URI scheme when correctly configured. Custom schemes must be distinctive and protected against interception.
16
+ Use a redirect mechanism that returns control to your app and that your platform can bind to it. Claimed Universal Links or Android App Links provide stronger app ownership than a custom URI scheme when correctly configured. A private-use scheme must contain a dot, such as `app.example.demo:/oauth/callback`, and must be protected against interception.
17
17
 
18
- The loopback callbacks supported for confidential web development do not define native or desktop redirect support. Follow the native redirect choices accepted during client registration; do not assume the confidential web-client loopback rules apply.
18
+ Native clients may use an HTTP loopback redirect with `localhost`, `127.0.0.1`, or `[::1]`. The scheme, host, path, and query string must exactly match the registered URI, but the port may differ so the application can bind an ephemeral port at runtime. The exception applies only to loopback redirects for native clients; native HTTPS and private-use scheme redirects must match every component of the registered URI exactly.
19
19
 
20
20
  ## Store and renew tokens
21
21
 
@@ -45,9 +45,9 @@ This proposal makes these additional decisions:
45
45
  - Map non-CRUD mutations by effect: Plaid and synced-crypto refresh operations
46
46
  use `:update`; transaction split/group and their reversals use
47
47
  `transactions:update`; balance-history and budget upserts use `:update`.
48
- - Let `transactions:read` expose attachment IDs and metadata, while requiring
49
- explicit `transaction_attachments:*` scopes to download, attach, or delete
50
- files.
48
+ - Keep attachment metadata and download access behind
49
+ `transaction_attachments:read`. The other `transaction_attachments:*` scopes
50
+ separately authorize attaching or deleting files.
51
51
 
52
52
  ## Package direction
53
53
 
@@ -11,6 +11,8 @@ Before you add OAuth to your application code, register an OAuth client with Lun
11
11
 
12
12
  List the Lunch Money features your application will provide, then use the [scope catalog](/oauth/scopes) to choose the smallest complete set of permissions those features require. Select `offline_access` only if the application needs to continue making API requests after the initial access token expires and while the user is away.
13
13
 
14
+ For the confidential Node.js sample, always select `me:read`. Add `offline_access` only if you want to exercise its optional refresh branch; the rest of the walkthrough does not require it.
15
+
14
16
  Scopes cannot be changed after registration. While you are developing your application, creating a replacement client is inexpensive, so let the scope set evolve as you learn what the application needs. Aim to have the application's core functionality—and its required permissions—settled before you submit the client for review.
15
17
 
16
18
  If functionality added after launch needs a scope that is not already registered, you must register a replacement client with the new complete scope set and have it reviewed before rollout. Existing authorizations do not transfer to the replacement client, so users must authorize it before they can use the new functionality. Treat that reauthorization as part of the feature launch: explain what the application can now do with their Lunch Money data and invite them to authorize the replacement client when they want to enable it.
@@ -28,17 +30,18 @@ The client type cannot be changed after registration. See [OAuth concepts](/oaut
28
30
 
29
31
  A redirect URI tells Lunch Money where to return the user after authorization. Register every callback address your application will use. Confidential web clients must match every component of a registered URI, including its scheme, host, port, path, and query string. Native-client loopback redirects use the same exact matching except that the ephemeral port is ignored.
30
32
 
31
- Web applications normally use an HTTPS callback. If you need a callback for local development, review the [loopback guidance](/oauth/development#local-loopback-callbacks) before registering one. Mobile and desktop applications may use an app-specific URL scheme or verified HTTPS link accepted by the Developer Portal.
33
+ Web applications normally use an HTTPS callback. If you need a callback for local development, review the [loopback guidance](/oauth/development#local-loopback-callbacks) before registering one. Mobile and desktop applications may use a private-use scheme containing a dot, such as `app.example.demo:/oauth/callback`, or a verified HTTPS link accepted by the Developer Portal.
32
34
 
33
35
  ## Register the OAuth client
34
36
 
35
37
  Open the [new OAuth client form](/oauth/applications/new) in the Developer Portal. Then:
36
38
 
37
39
  1. Describe the application so Lunch Money users can understand who built it and what it does.
38
- 2. Choose its client type.
39
- 3. Add at least one redirect URI.
40
- 4. Select the planned scopes.
41
- 5. Accept the current Lunch Money API Terms of Use and select **Register OAuth client**.
40
+ 2. Provide a monitored support email where users can reach you if authorization fails.
41
+ 3. Choose its client type.
42
+ 4. Add at least one redirect URI.
43
+ 5. Select the planned scopes.
44
+ 6. Accept the current Lunch Money API Terms of Use and select **Register OAuth client**.
42
45
 
43
46
  After registration, record the client ID in your application configuration. A client ID identifies the registration but is not a secret.
44
47
 
@@ -48,4 +51,6 @@ A confidential web client can have multiple active secrets. This allows you to i
48
51
 
49
52
  You now have the client settings needed to connect your application to Lunch Money.
50
53
 
54
+ To exercise those settings with a focused confidential-client implementation, [try OAuth with the Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account).
55
+
51
56
  Next: [Implement authorization](/oauth/authorization-code).
@@ -18,23 +18,35 @@ Deleted clients disappear from the owner's Developer Portal.
18
18
 
19
19
  ## Prepare for review
20
20
 
21
- Submitting a client for review is how you make your application authorizable by Lunch Money users other than yourself. You can create and test a client with only basic information, but review requires a complete, user-facing application profile and production-ready configuration.
21
+ Submitting a client for review is how you make your application authorizable by Lunch Money users other than yourself. Registration already requires basic information, including a support email, but review requires a complete, user-facing application profile and production-ready configuration.
22
22
 
23
- Before requesting review, provide:
23
+ Before requesting review, make sure the client has:
24
24
 
25
25
  - an application description, developer name, and logo so users can identify the application and who created it;
26
26
  - a monitored support email where users and Lunch Money can reach you;
27
27
  - a publicly accessible homepage URL where users can learn about the application;
28
28
  - a publicly accessible privacy policy URL explaining how the application handles user data;
29
- - at least one eligible production HTTPS redirect URI;
29
+ - at least one eligible redirect URI—a confidential web client needs a non-loopback production HTTPS redirect, while a native client may use a private-use scheme or loopback redirect;
30
30
  - a recognized, supported scope set and an explanation of why the application needs each permission; and
31
31
  - acceptance of the current Lunch Money API Terms of Use.
32
32
 
33
+ The privacy policy must be available at a direct, stable public URL and explain how the application collects, uses, stores, shares, and deletes user data. A published privacy-policy page in a public source-code repository can satisfy this requirement. An in-app page that requires installation or sign-in, or a link to source code without a clear privacy policy, does not.
34
+
33
35
  Fully test the client before submission. A confidential web client also needs at least one active, non-expired secret; a native or public client does not.
34
36
 
35
37
  Explain which features use each requested permission, how that access benefits users, and why narrower scopes are insufficient. If the client includes `offline_access`, explain why the integration must operate while the user is absent. You may also provide documentation, source-code, and demonstration URLs or context about changes made after earlier feedback.
36
38
 
37
- A client may retain owner-only loopback callbacks alongside its production HTTPS callback, but a loopback-only client is not ready for review.
39
+ Open-source applications are welcome. If your source code is public, include the repository URL in the review request's additional context. It can help the reviewer understand how your application uses Lunch Money data and the permissions it requests. Publishing source code is optional and is not required for approval.
40
+
41
+ A confidential web client may retain owner-only loopback callbacks alongside its production HTTPS callback, but it still needs a non-loopback production HTTPS redirect before review.
42
+
43
+ ### Native applications
44
+
45
+ You do not need to create a standalone marketing website for a native application. Its public Apple App Store or Google Play listing can serve as the homepage if it clearly explains what the application does and identifies the developer. A public pre-release listing, project page, or README in a public source-code repository can also work.
46
+
47
+ If you provide a pre-release listing or beta-testing link, use the review request's additional context to explain how to access it. Include any required opt-in steps, supported platforms or devices, and other information the reviewer needs to evaluate the application. Do not include passwords, client secrets, tokens, or other credentials.
48
+
49
+ Native clients may be reviewed with loopback or private-use scheme redirects.
38
50
 
39
51
  ## Submit, cancel, or revise
40
52
 
@@ -0,0 +1,199 @@
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. Samples are separated by OAuth client type and platform because confidential and native clients have different credential boundaries.
4
+
5
+ > [!TIP] Open-source sample
6
+ > [View the confidential Node.js sample on GitHub](https://github.com/lunch-money/lm-oauth-confidential-node-example) or begin with its [README](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/README.md).
7
+
8
+ ## Confidential Node.js and TypeScript sample
9
+
10
+ `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:
11
+
12
+ - configuring a registered Lunch Money OAuth client and discovering authorization-server metadata;
13
+ - starting authorization with single-use `state` and S256 Proof Key for Code Exchange (PKCE);
14
+ - validating the callback and exchanging the code on the server;
15
+ - calling `GET /v2/me` with a server-held access token;
16
+ - optionally refreshing and atomically replacing the credential set for a client registered with `offline_access`;
17
+ - revoking the access token and verifying that Lunch Money rejects it; and
18
+ - resetting only the sample's local state so the flow can be repeated.
19
+
20
+ > [!WARNING] Teaching sample, not a production application
21
+ > 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.
22
+
23
+ <!-- BEGIN GENERATED OAUTH SAMPLE WALKTHROUGH -->
24
+ <!-- Source: lunch-money/lm-oauth-confidential-node-example@fbf03343847032ef256994758adf397218d63f74:docs/WALKTHROUGH.md -->
25
+ ## Try OAuth with your Lunch Money account
26
+
27
+ 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.
28
+
29
+ [`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.
30
+
31
+ > [!IMPORTANT]
32
+ > 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.
33
+
34
+ ### Before you begin
35
+
36
+ You need:
37
+
38
+ - a Lunch Money account;
39
+ - Node.js 20 or newer;
40
+ - npm 11.6.2; and
41
+ - Git.
42
+
43
+ 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).
44
+
45
+ Three actions and identities are involved:
46
+
47
+ - **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.
48
+ - **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.
49
+ - **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.
50
+
51
+ ```text
52
+ Your real Lunch Money account
53
+ creates and owns the OAuth client
54
+ authorizes access to a real budgeting account
55
+ ↓
56
+ Sample OAuth callback
57
+ ↓
58
+ Credentials stored under local-demo-user
59
+ (the stand-in for your application's authenticated user)
60
+ ```
61
+
62
+ 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.
63
+
64
+ ### 1. Download and install the sample
65
+
66
+ ```sh
67
+ git clone https://github.com/lunch-money/lm-oauth-confidential-node-example.git
68
+ cd lm-oauth-confidential-node-example
69
+ npm ci
70
+ npm run build
71
+ ```
72
+
73
+ ### 2. Register a confidential client
74
+
75
+ In the Lunch Money Developer Portal, create a **Confidential web client** with:
76
+
77
+ - the exact redirect URI `http://localhost:4002/oauth/callback`;
78
+ - the required `me:read` scope;
79
+ - optionally, `offline_access` if you also want to exercise refresh; and
80
+ - a client secret that you save securely when Lunch Money displays it.
81
+
82
+ Lunch Money uses the complete scope set registered for the client. The sample therefore does not send a `scope` parameter during authorization.
83
+
84
+ 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.
85
+
86
+ You do not need `offline_access` to authorize the client, call `/v2/me`, revoke access, or authorize again.
87
+
88
+ ### 3. Configure the local process
89
+
90
+ In the same terminal, set the client ID and secret from the Developer Portal:
91
+
92
+ ```sh
93
+ export OAUTH_CLIENT_ID='YOUR_CLIENT_ID'
94
+ export OAUTH_CLIENT_SECRET='YOUR_CLIENT_SECRET'
95
+ export OAUTH_REDIRECT_URI='http://localhost:4002/oauth/callback'
96
+ export LUNCH_MONEY_API_BASE_URL='https://api-alpha.lunchmoney.dev/'
97
+ ```
98
+
99
+ The sample uses `LUNCH_MONEY_API_BASE_URL` 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`.
100
+
101
+ `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.
102
+
103
+ > [!WARNING]
104
+ > **Keep credentials private**
105
+ > 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.
106
+
107
+ ### 4. Start the sample
108
+
109
+ ```sh
110
+ npm run dev
111
+ ```
112
+
113
+ Open [http://localhost:4002](http://localhost:4002) in your browser.
114
+
115
+ 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.
116
+
117
+ ### 5. Authorize and call `/v2/me`
118
+
119
+ 1. Choose **Connect Lunch Money**.
120
+ 2. Sign into Lunch Money as the user who created the development client.
121
+ 3. Select one of that user's budgeting accounts and approve access.
122
+ 4. After the browser returns to `http://localhost:4002/oauth/callback`, choose **Call /v2/me**.
123
+
124
+ 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.
125
+
126
+ 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.
127
+
128
+ ### 6. Optionally refresh access
129
+
130
+ If you registered the client with `offline_access`, choose **Refresh access token**, then choose **Call /v2/me** again.
131
+
132
+ 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.
133
+
134
+ If you registered only `me:read`, Lunch Money does not issue a refresh token and the refresh button does not appear. Skip this step.
135
+
136
+ 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.
137
+
138
+ ### 7. Revoke and verify access
139
+
140
+ Choose **Revoke and verify**.
141
+
142
+ - If the connection has a refresh token, the sample revokes it to revoke the entire grant.
143
+ - If the connection has only an access token, the sample revokes that access token.
144
+
145
+ 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.
146
+
147
+ To repeat the flow, choose **Connect Lunch Money** and authorize the client again.
148
+
149
+ ### 8. Try user-initiated revocation
150
+
151
+ To see what happens when a Lunch Money user disconnects the client:
152
+
153
+ 1. Authorize the sample again.
154
+ 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.
155
+ 3. Revoke the client's access there.
156
+ 4. Return to the sample and choose **Call /v2/me**.
157
+
158
+ The request should fail because Lunch Money no longer accepts the stored access token. Choose **Local reset only** to remove the now-unusable local credential and browser session before starting again.
159
+
160
+ ### Local reset is not revocation
161
+
162
+ **Local reset only** clears the sample's browser session and locally stored credential. It does not contact Lunch Money and does not revoke active access.
163
+
164
+ When access is still active, revoke it first through **Revoke and verify** or Lunch Money's Connected Apps page. Use local reset by itself only when the remote authorization has already been revoked or you deliberately want to clear this disposable local demonstration.
165
+
166
+ ### Why `localhost` works
167
+
168
+ > [!NOTE]
169
+ > 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.
170
+
171
+ ### Keep the sample local
172
+
173
+ > [!WARNING]
174
+ > 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.
175
+
176
+ 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.
177
+
178
+ Before adapting the code, read the repository's [OAuth code guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/src/oauth/README.md), [security model](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/SECURITY.md), [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/PRODUCTION_CHECKLIST.md), and [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/TROUBLESHOOTING.md).
179
+ <!-- END GENERATED OAUTH SAMPLE WALKTHROUGH -->
180
+
181
+ ## Understand and apply the teaching code
182
+
183
+ 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.
184
+
185
+ 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.
186
+
187
+ 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.
188
+
189
+ ### Follow the flow through the source
190
+
191
+ 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.
192
+
193
+ 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).
194
+
195
+ ## Native applications
196
+
197
+ A separate native/public-client sample is planned but is not yet available. Native mobile and desktop applications use PKCE **without a client secret** and store tokens in platform secure storage. Follow the [native application guidance](/oauth/native-apps); do not adapt the confidential sample by placing its client secret or server-held credentials in installed application code.
198
+
199
+ Next: [Develop and test your OAuth application](/oauth/development) and [operate it securely](/oauth/security).
@@ -71,11 +71,11 @@ View and manage all supported Lunch Money data
71
71
  | <a id="scope-manual-accounts-delete"></a>`manual_accounts:delete` | Delete manually managed accounts | `deleteManualAccount` |
72
72
  | <a id="scope-plaid-accounts-read"></a>`plaid_accounts:read` | View accounts connected through Plaid | `getAllPlaidAccounts`, `getPlaidAccountById` |
73
73
  | <a id="scope-plaid-accounts-update"></a>`plaid_accounts:update` | Request a Plaid account refresh, which may import new transactions | `triggerPlaidAccountFetch` |
74
- | <a id="scope-transactions-read"></a>`transactions:read` | View transactions, including split and group information and attachment metadata | `getAllTransactions`, `getTransactionById` |
74
+ | <a id="scope-transactions-read"></a>`transactions:read` | View transactions, including split and group information | `getAllTransactions`, `getTransactionById` |
75
75
  | <a id="scope-transactions-create"></a>`transactions:create` | Create transactions | `createNewTransactions` |
76
76
  | <a id="scope-transactions-update"></a>`transactions:update` | Update, split, unsplit, group, and ungroup transactions | `updateTransactions`, `updateTransaction`, `groupTransactions`, `ungroupTransactions`, `splitTransaction`, `unsplitTransaction` |
77
77
  | <a id="scope-transactions-delete"></a>`transactions:delete` | Delete transactions | `deleteTransactions`, `deleteTransactionById` |
78
- | <a id="scope-transaction-attachments-read"></a>`transaction_attachments:read` | Get download access to transaction attachments | `getTransactionAttachmentUrl` |
78
+ | <a id="scope-transaction-attachments-read"></a>`transaction_attachments:read` | View transaction attachment metadata and get download access to attachments | `getTransactionAttachmentUrl` |
79
79
  | <a id="scope-transaction-attachments-create"></a>`transaction_attachments:create` | Attach files to transactions | `attachFileToTransaction` |
80
80
  | <a id="scope-transaction-attachments-delete"></a>`transaction_attachments:delete` | Delete transaction attachments | `deleteTransactionAttachment` |
81
81
  | <a id="scope-tags-read"></a>`tags:read` | View tags | `getAllTags`, `getTagById` |
@@ -22,6 +22,16 @@ In the browser-based model, the short-lived access token is also visible to the
22
22
 
23
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 budgeting account.
24
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 the verified credential set under a stable internal application-user ID. If one application user may connect more than one Lunch Money context, also assign each connection its own stable internal connection ID. These identifiers belong to your application; do not use an email address, callback parameter, form field, or any other browser-supplied value as the credential owner.
30
+
31
+ Enforce the same ownership boundary every time your backend reads credentials, replaces them after authorization or refresh, revokes them, or deletes them. Durable credentials must be encrypted at rest, and backend access should be limited to the services and operators that need it. Never return refresh tokens or client secrets to browser JavaScript.
32
+
33
+ The [confidential Node.js sample](/oauth/sample-applications#understand-and-apply-the-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.
34
+
25
35
  ## Secure authorization and callbacks
26
36
 
27
37
  - Generate unpredictable, session-bound, single-use `state` and verify it before exchanging a code.
@@ -37,7 +47,7 @@ Choose only the resource scopes needed for expected features. Add `offline_acces
37
47
 
38
48
  ## Operate credentials safely
39
49
 
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. Make logout revoke server-side credentials and clear local state.
50
+ 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, 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 logout or disconnect invalidate continuing refresh access as well as clear local state.
41
51
 
42
52
  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
53
 
@@ -14,9 +14,11 @@ Access tokens are deliberately short-lived. Record when the token response arriv
14
14
 
15
15
  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
16
 
17
+ 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.
18
+
17
19
  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.
18
20
 
19
- Persist every replacement refresh token before discarding the previous value, and serialize refreshes for a grant. Lunch Money rotates refresh tokens; replay of a consumed token can invalidate the token family. If refresh fails terminally, delete unusable credentials and start a fresh authorization.
21
+ Atomically persist the complete replacement token set before discarding the previous value, and serialize refreshes for each application-user 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 and start a fresh authorization.
20
22
 
21
23
  ## Revoke access
22
24
 
@@ -55,6 +57,8 @@ Do not paste a real client secret or token directly into a command where it may
55
57
 
56
58
  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
59
 
60
+ A Lunch Money user can also revoke an application's access from the [Connected Apps page](https://my.lunchmoney.app/connected-apps). Your application should treat the resulting authentication failure as terminal and ask the user to authorize again when they want to reconnect.
61
+
58
62
  ## Recovery rules
59
63
 
60
64
  - 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
 
@@ -29,11 +31,24 @@ Match on `error` when it is returned, verify `state`, and treat `error_descripti
29
31
  | `invalid_scope` | The client does not have a usable registered scope set | Open the client in the [Developer Portal](/oauth/applications) and review its assigned scopes. Registered clients always use that complete set. |
30
32
  | `access_denied` | The user declined or the application is unavailable to that user | Treat denial normally. During development, only the owner may authorize; other users require an active approved application. |
31
33
 
32
- ### Redirect URI mismatch
34
+ ### Users cannot authorize an unapproved client
35
+
36
+ Until a client is approved, only its owner may authorize the application. Anyone else is stopped at the consent screen and is never returned to your application, so you will see no callback and no `error` parameter for these attempts.
37
+
38
+ Those users are shown the support email registered with your client so they can reach you. If you did not expect authorization to be unavailable, check the client in the [Developer Portal](/oauth/applications). A client stays in development until it is submitted for review and approved. A rejected or disabled client also prevents other users from authorizing the application.
39
+
40
+ ### Redirect URI problems
33
41
 
34
- If the redirect URI does not exactly match one registered for the client, Lunch Money cannot safely return the browser to it. Instead, Lunch Money shows an error page explaining that it could not return the user to the application. The page displays `REDIRECT_URI_MISMATCH`, a request or reference ID, a timestamp, and the application name and public client ID when they are available.
42
+ If the callback address cannot be matched to one registered for the client, Lunch Money cannot safely return the browser to your application. It stops locally and shows the user a page explaining what happened, rather than redirecting with an `error`. Your application receives no callback for these attempts.
43
+
44
+ The page displays one of two codes, a UTC timestamp, and the application name and public client ID once the client has been resolved.
45
+
46
+ | Code | Likely cause | What to do |
47
+ | --- | --- | --- |
48
+ | `redirect_uri_mismatch` | The address your application sent is not registered for the client, or differs from a registered one | Open the client in the [Developer Portal](/oauth/applications) and compare the address with its registered redirect URIs. Web-client redirects and native HTTPS or private-use scheme redirects must match every component exactly. For a native HTTP loopback redirect, the scheme, host, path, and query string must match, but the ephemeral port may differ. See [local callback failures](#local-callback-failures). |
49
+ | `redirect_uri_missing` | No `redirect_uri` was sent, and the client has more than one registered, so Lunch Money cannot choose | Send the parameter explicitly. A client with exactly one registered address may omit it; Lunch Money then uses the registered value. |
35
50
 
36
- Ask the user to report those displayed details—not the complete attempted URL, credentials, authorization codes, tokens, cookies, or PKCE values. Open the client in the [Developer Portal](/oauth/applications), find its registered redirect URIs, and compare the complete URL with the value your application sent. The two values must be identical.
51
+ The page offers the user a prefilled message addressed to the support email registered with your client. It contains the error code, the timestamp, the application name and client ID, and the callback address your application sent, reduced to its origin and path with any query string removed. Compare that sanitized callback address with the redirect URIs registered for the client, then correct either the registered URI or the address your application sends.
37
52
 
38
53
  ## Callback validation failures
39
54
 
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,6 +312,7 @@
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" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunch-money/developer-docs",
3
- "version": "2.11.1-preview.9",
3
+ "version": "2.11.2-preview.2",
4
4
  "description": "Developer documentation content for Lunch Money APIs",
5
5
  "exports": {
6
6
  ".": "./package.json",
@@ -10,7 +10,7 @@ All API requests require a Bearer token in the `Authorization` header:
10
10
  Authorization: Bearer YOUR_ACCESS_TOKEN
11
11
  ```
12
12
 
13
- Get your access token from the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Lunch Money Developers page</a>. See the [Getting started guide](/getting-started) for a step-by-step walkthrough including how to create a test budget.
13
+ Use a personal access token for API exploration or work with your own budgeting 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
 
@@ -5,9 +5,12 @@ The Lunch Money API spec uses a modified version of SEMVER for its versioning me
5
5
  - The minor version represents the number of main endpoints the current version of the spec supports. For example, a version of the API that supports the /me, /categories, and /transactions endpoints would have a minor version of 3.
6
6
  - The revision number represents the number of updates since the last endpoint was added. For example, each time changes are made to one of the existing three APIs as described above, the revision number will be bumped.
7
7
 
8
+ ## v2.11.2 - TBD
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
+ - Enforce required OAuth scopes for every v2 operation and publish the scope and scope-group catalog mapped to V2 `operationId` values
11
+ - Document the OAuth 2.0 authorization-code flow used to obtain access tokens
12
+
8
13
  ## v2.11.1 - TBD
9
- - Add the OAuth scope and scope-group catalog mapped to V2 `operationId` values
10
- - Document OAuth 2.0 authorization-code endpoints and required OAuth scopes for every V2 operation
11
14
  - Add `GET /me/account/settings` and `PUT /me/account/settings` for account-level settings
12
15
  - Add `GET /me/user/settings` and `PUT /me/user/settings` for user-level display and formatting preferences
13
16
  - Add `GET /me/user/account/settings` and `PUT /me/user/account/settings` for settings specific to a user and budgeting account
@@ -4,9 +4,9 @@ info:
4
4
  description: |-
5
5
  ### Introduction
6
6
 
7
- Welcome to the Lunch Money v2 API reference. This is the **v2.11.1** spec.
7
+ Welcome to the Lunch Money v2 API reference. This is the **v2.11.2** spec.
8
8
 
9
- The API is available at `https://api.lunchmoney.dev/v2`. Get your access token from the [Lunch Money developers page](https://my.lunchmoney.app/developers).
9
+ The API is available at `https://api.lunchmoney.dev/v2`. Use a personal access token from the [Lunch Money Developers page](https://my.lunchmoney.app/developers) for API exploration or work with your own budgeting account. Applications built for other Lunch Money users should use an [OAuth access token issued after the user authorizes the application](https://lunchmoney.dev/oauth).
10
10
 
11
11
 
12
12
  **Try it from these docs**
@@ -42,7 +42,7 @@ info:
42
42
  license:
43
43
  name: Apache 2.0
44
44
  url: http://www.apache.org/licenses/LICENSE-2.0.html
45
- version: 2.11.1
45
+ version: 2.11.2
46
46
 
47
47
  servers:
48
48
  - url: https://api.lunchmoney.dev/v2
@@ -2712,8 +2712,11 @@ components:
2712
2712
  type: array
2713
2713
  nullable: false
2714
2714
  description: A list of objects that describe any attachments to the
2715
- transaction. This is only present when the `include_files` query
2716
- parameter is set to true.
2715
+ transaction. For OAuth clients, this property is available only
2716
+ with the `transaction_attachments:read` scope. Without that scope,
2717
+ an absent `files` property does not mean the transaction has no
2718
+ attachments. See the operation description for its attachment
2719
+ behavior.
2717
2720
  items:
2718
2721
  $ref: "#/components/schemas/transactionAttachmentObject"
2719
2722
  source:
@@ -3008,7 +3011,10 @@ components:
3008
3011
  type: array
3009
3012
  nullable: false
3010
3013
  description: A list of objects that describe any attachments to the
3011
- transaction
3014
+ transaction. OAuth clients also need the
3015
+ `transaction_attachments:read` scope. Without it, this property is
3016
+ omitted rather than returned as an empty array, so an absent
3017
+ `files` property does not mean the transaction has no attachments.
3012
3018
  items:
3013
3019
  $ref: "#/components/schemas/transactionAttachmentObject"
3014
3020
  required:
@@ -5136,11 +5142,11 @@ components:
5136
5142
  manual_accounts:delete: "Delete manually managed accounts"
5137
5143
  plaid_accounts:read: "View accounts connected through Plaid"
5138
5144
  plaid_accounts:update: "Request a Plaid account refresh, which may import new transactions"
5139
- transactions:read: "View transactions, including split and group information and attachment metadata"
5145
+ transactions:read: "View transactions, including split and group information"
5140
5146
  transactions:create: "Create transactions"
5141
5147
  transactions:update: "Update, split, unsplit, group, and ungroup transactions"
5142
5148
  transactions:delete: "Delete transactions"
5143
- transaction_attachments:read: "Get download access to transaction attachments"
5149
+ transaction_attachments:read: "View transaction attachment metadata and get download access to attachments"
5144
5150
  transaction_attachments:create: "Attach files to transactions"
5145
5151
  transaction_attachments:delete: "Delete transaction attachments"
5146
5152
  tags:read: "View tags"
@@ -9667,6 +9673,9 @@ paths:
9667
9673
  description: By default, the `files` property is not included in the
9668
9674
  response. Set to true if you'd like the responses to include a list
9669
9675
  of objects that describe any files attached to the transactions.
9676
+ OAuth clients must have both `transactions:read` and
9677
+ `transaction_attachments:read`; otherwise the request returns a
9678
+ `403` `insufficient_scope` error.
9670
9679
  - name: limit
9671
9680
  in: query
9672
9681
  schema:
@@ -9950,7 +9959,10 @@ paths:
9950
9959
  successfully inserted.<br>
9951
9960
  - `skipped_duplicates`: A list of
9952
9961
  transactions that were duplicates of existing transactions and were not
9953
- inserted.
9962
+ inserted.<p>
9963
+
9964
+ For OAuth clients, returned transactions include the `files` property
9965
+ only when the client has the `transaction_attachments:read` scope.
9954
9966
  operationId: createNewTransactions
9955
9967
  security:
9956
9968
  - bearerSecurity: []
@@ -10394,7 +10406,10 @@ paths:
10394
10406
 
10395
10407
  Each transaction in the array **must** include an `id` property to identify which transaction to update, along with at least one other property to be updated. For example, a transaction object that contains only an `id` and `category_id` property is valid.<br><br>
10396
10408
 
10397
- The request can include between 1 and 500 transactions to update in a single call.
10409
+ The request can include between 1 and 500 transactions to update in a single call.<br><br>
10410
+
10411
+ For OAuth clients, returned transactions include the `files` property
10412
+ only when the client has the `transaction_attachments:read` scope.
10398
10413
  operationId: updateTransactions
10399
10414
  security:
10400
10415
  - bearerSecurity: []
@@ -10877,7 +10892,8 @@ paths:
10877
10892
  added to transactions that were inserted or updated via the API.
10878
10893
 
10879
10894
  - `files` will be a list of objects that describe any attachments to the
10880
- transaction.
10895
+ transaction. For OAuth clients, this property is included only when the
10896
+ client has the `transaction_attachments:read` scope.
10881
10897
 
10882
10898
 
10883
10899
  If `is_group_parent` is true in the returned transaction, the object will also
@@ -11132,7 +11148,11 @@ paths:
11132
11148
  It is also possible to provide only the properties to be updated in the
11133
11149
  request body, as long as the request includes at least one of the
11134
11150
  properties that is not listed above. For example a request body that contains only
11135
- an `category_id` attribute is valid.
11151
+ an `category_id` attribute is valid.<br><br>
11152
+
11153
+ For OAuth clients, the returned transaction includes the `files`
11154
+ property only when the client has the
11155
+ `transaction_attachments:read` scope.
11136
11156
 
11137
11157
  operationId: updateTransaction
11138
11158
  security:
@@ -11348,7 +11368,11 @@ paths:
11348
11368
  transaction. The grouped transactions will
11349
11369
 
11350
11370
  be included in the `children` property of the transaction returned in
11351
- the response
11371
+ the response.<br><br>
11372
+
11373
+ For OAuth clients, the returned group and its children include the
11374
+ `files` property only when the client has the
11375
+ `transaction_attachments:read` scope.
11352
11376
  operationId: groupTransactions
11353
11377
  security:
11354
11378
  - bearerSecurity: []
@@ -11701,7 +11725,11 @@ paths:
11701
11725
 
11702
11726
  To see the details of the original parent transaction after it has been
11703
11727
  split, use the `GET /transactions/{id}` endpoint and pass the value of
11704
- the `split_parent_id` of one of the children.
11728
+ the `split_parent_id` of one of the children.<br><br>
11729
+
11730
+ For OAuth clients, the returned parent and its children include the
11731
+ `files` property only when the client has the
11732
+ `transaction_attachments:read` scope.
11705
11733
  operationId: splitTransaction
11706
11734
  security:
11707
11735
  - bearerSecurity: []