@lunch-money/developer-docs 2.11.2-preview.10 → 2.11.2-preview.12

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.
@@ -17,6 +17,8 @@ For Node.js and TypeScript, [`openid-client`](https://github.com/panva/openid-cl
17
17
 
18
18
  The [confidential Node.js sample application](/oauth/sample-applications#understand-the-confidential-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
19
 
20
+ Lunch Money requires PKCE with the `S256` challenge method for every authorization-code flow, including confidential clients that also authenticate with a client secret.
21
+
20
22
  ## Configure Lunch Money with discovery
21
23
 
22
24
  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:
@@ -65,6 +67,18 @@ Send a form-encoded request to the discovered token endpoint with `grant_type=au
65
67
 
66
68
  Keep the returned credentials in temporary server-side state while you identify the authorization. Do not make an unidentified credential set available to the application or commit it to the normal connection store yet. If a `refresh_token` is returned, treat it as a high-value credential even during this temporary step.
67
69
 
70
+ The token endpoint returns a JSON object with these fields:
71
+
72
+ | Field | Type | When present |
73
+ | --- | --- | --- |
74
+ | `access_token` | string | Always. Treat it as an opaque credential. |
75
+ | `token_type` | string | Always. Lunch Money returns `Bearer`. |
76
+ | `expires_in` | integer | Always. Number of seconds until the access token is expected to expire. |
77
+ | `scope` | string | Always. Space-delimited scopes granted to the token. |
78
+ | `refresh_token` | string | When the registered client includes `offline_access`. |
79
+
80
+ Lunch Money does not return an `id_token`. A successful refresh response has the same shape and includes a replacement `refresh_token`; persist the complete replacement set together.
81
+
68
82
  ## 4. Identify and store the connection
69
83
 
70
84
  Send the access token in the header:
@@ -75,7 +89,7 @@ Host: api.lunchmoney.dev
75
89
  Authorization: Bearer YOUR_ACCESS_TOKEN
76
90
  ```
77
91
 
78
- The operation must be allowed by the token's scope and by the user's access to the selected budget account. See the [scope catalog](/oauth/scopes).
92
+ The operation must be allowed by the token's scope and by the user's access to the selected budget account. See [OAuth scopes](/oauth/scopes).
79
93
 
80
94
  Call `GET /v2/me` immediately after the token exchange, before normal credential persistence. It requires `me:read`. Strictly validate the response: `id` identifies the Lunch Money user, `account_id` identifies the selected budget account, and `budget_name` is its display name. When reconnecting, use this response to establish the account identity rather than assuming the user selected the same budget. See [users, budget accounts, and access tokens](/oauth/concepts#users-budget-accounts-and-access-tokens).
81
95
 
@@ -14,7 +14,7 @@ These terms describe who participates in Lunch Money OAuth and what each credent
14
14
 
15
15
  A **Lunch Money user** is the person who signs in to Lunch Money. One user can have access to multiple **budget accounts**, such as a personal budget, a household budget, and a test budget. Each budget account contains its own financial data. Here, “budget account” means that collection of data, not an individual bank or manually managed account within it.
16
16
 
17
- During OAuth authorization, the user selects one budget account and approves your client's permissions. The resulting **OAuth access token is specific to that Lunch Money user and budget account pair**, for your client. It does not grant access to every budget account the user can access.
17
+ During OAuth authorization, the user selects one budget account and approves your client's scopes. The resulting **OAuth access token is specific to that Lunch Money user and budget account pair**, for your client. It does not grant access to every budget account the user can access.
18
18
 
19
19
  For example, Alex can access both “Household” and “API testing.” If Alex selects “API testing” during authorization, your application's access token operates on that budget account. It cannot use the same token to read or update “Household.” Scopes determine which operations the application may perform within the selected account; they do not add access to other budget accounts.
20
20
 
@@ -84,9 +84,9 @@ The developer registers each permitted redirect URI in advance. For a confidenti
84
84
 
85
85
  ## Scopes and least privilege
86
86
 
87
- Resource scopes such as `transactions:read` authorize specific ways of working with Lunch Money data. A scope generally applies to a particular kind of action—such as reading or updating a resource—and only to the relevant API endpoints. Permissions are independent: for example, `transactions:update` does not also grant `transactions:read`.
87
+ Scopes are named permissions that define what an OAuth client may do with Lunch Money data. Resource scopes such as `transactions:read` authorize a particular kind of action—such as reading or updating a resource—and only for the relevant API endpoints. Scopes are independent: for example, `transactions:update` does not also grant `transactions:read`.
88
88
 
89
- The [V2 API reference](/v2/docs) identifies the scope required by each API endpoint. Use it together with the [scope catalog](/oauth/scopes) to select the permissions your application's features require.
89
+ The [V2 API reference](/v2/docs) identifies the OAuth scope requirements for each operation. An operation may require one scope, several scopes together, or additional scopes when a request enables broader behavior. Review the operation and relevant parameter descriptions, then use the [OAuth scopes](/oauth/scopes) page to select the scopes your application's features require.
90
90
 
91
91
  Scopes are selected when the client is created and cannot be edited later. Every user who authorizes that client sees and grants all—and only—the scopes the developer selected. To change them, create and test a replacement client, complete review when applicable, update your integration, and ask existing users to authorize again. Existing authorizations and tokens do not move to the replacement client.
92
92
 
@@ -7,6 +7,8 @@ The goal now is to test that complete workflow—including its failure and recov
7
7
  > [!NOTE] Development access is owner-only
8
8
  > While an OAuth client is in development, only the Lunch Money user who owns it can authorize the application. Your team should have the developer doing most of the initial Lunch Money work create and own the client so they can test the complete flow with their budget accounts. Other team members cannot authorize the application until the client has been reviewed and approved.
9
9
 
10
+ Owner-only testing is enough to submit a client for review. You do not need beta-tester coverage before submission; test the complete flow and recovery behavior with the owner's Lunch Money user and test budget accounts.
11
+
10
12
  ## Set up a safe test environment
11
13
 
12
14
  Use a separate Lunch Money test budget account when exercising create, update, or delete operations. The [Getting Started guide](/getting-started#create-a-test-budget-account) walks through creating one and adding sample data.
@@ -45,7 +47,7 @@ Test with one Lunch Money user authorizing multiple budget accounts, then with a
45
47
  1. Complete authorization. For each attempt, your application should create a random `state` value, save it with the application user's session, and verify that Lunch Money returns the same value to the callback. Rejecting a missing, changed, or reused value prevents a callback started elsewhere from being attached to the wrong session.
46
48
  2. Keep the PKCE verifier with that same authorization attempt and use it when exchanging the returned code. Confirm that an exchange with a missing or changed verifier fails; this prevents someone who intercepts the code from using it.
47
49
  3. Read the token response's `scope` field and confirm that it contains the complete scope set registered for the client. Do not inspect or parse the access token itself.
48
- 4. Call every scope-dependent feature, including negative permission cases.
50
+ 4. Call every scope-dependent feature, including insufficient-scope cases.
49
51
  5. Test user denial, missing or changed `state`, callback errors, and interrupted authorization attempts. Confirm that denial or a failed `/v2/me` validation leaves all existing connections unchanged.
50
52
  6. Make `GET /v2/me` fail or return an invalid shape after a successful exchange. Confirm that the application saves no unidentified or partial connection.
51
53
  7. Reauthorize an already connected Lunch Money user/account pair. Confirm that the application atomically replaces that connection's credentials, makes it active, and does not create a duplicate.
@@ -66,6 +68,6 @@ Your application should discard the unusable token for that connection and start
66
68
 
67
69
  After reauthorization works, clients registered with `offline_access` can add and test refresh-token handling. Verify replacement-token storage, concurrent-refresh protection, revocation, and terminal refresh recovery before relying on unattended operation. See [token lifecycle and recovery](/oauth/tokens).
68
70
 
69
- Finally, add a production HTTPS redirect and complete the [review checklist](/oauth/review-and-approval).
71
+ Finally, complete the [review checklist](/oauth/review-and-approval). A confidential web client needs a production HTTPS redirect before review. A native client may be submitted with an eligible native redirect and an accessible pre-release build.
70
72
 
71
73
  Next: [Operate securely](/oauth/security).
@@ -2,7 +2,7 @@
2
2
 
3
3
  OAuth lets your application access Lunch Money on behalf of a user without asking that person to copy and share a personal access token. **If you are building an application for anyone other than yourself, use OAuth.** It gives each user a familiar Lunch Money sign-in and consent experience while keeping their personal access token out of your application.
4
4
 
5
- ![OAuth authorization-code flow across your application, the Lunch Money user, and Lunch Money. The application redirects the user's browser, Lunch Money handles sign-in and presents the budget-account and permission choices, the user approves access, Lunch Money returns an authorization code, and the application exchanges it for tokens, calls GET /v2/me, saves the identified connection, and calls the API for that account.](/v2/images/oauth-authorization-flow.svg)
5
+ ![OAuth authorization-code flow across your application, the Lunch Money user, and Lunch Money. The application redirects the user's browser, Lunch Money handles sign-in and presents the budget-account and scope choices, the user approves access, Lunch Money returns an authorization code, and the application exchanges it for tokens, calls GET /v2/me, saves the identified connection, and calls the API for that account.](/v2/images/oauth-authorization-flow.svg)
6
6
 
7
7
  ## Choose OAuth or a personal access token
8
8
 
@@ -10,16 +10,16 @@ OAuth lets your application access Lunch Money on behalf of a user without askin
10
10
  | --- | --- | --- |
11
11
  | Whose data can it access? | One budget account belonging to the user who created the token | One budget 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
- | 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 |
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 scopes 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 budget account, with no fine-grained permission controls | Only the permissions selected when the OAuth client was registered |
15
+ | Scopes | Full access to the Lunch Money APIs available for that budget account, with no fine-grained scope controls | Only the scopes 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
- 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.
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 scope set before they approve access. Choose only the scopes the application's features require. A focused scope 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
  One Lunch Money user can have multiple budget accounts and multiple connections to the same OAuth client. Each OAuth access token represents that user and the single budget account they selected during authorization. It does not provide access to all of their budgets. Read [users, budget accounts, and access tokens](/oauth/concepts#users-budget-accounts-and-access-tokens) for an example and how to identify the connected account.
25
25
 
@@ -27,4 +27,4 @@ One Lunch Money user can have multiple budget accounts and multiple connections
27
27
 
28
28
  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.
29
29
 
30
- 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.
30
+ 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 [OAuth scopes](/oauth/scopes) to plan the client's scopes and [troubleshooting](/oauth/troubleshooting) when a flow fails.
@@ -2,6 +2,8 @@
2
2
 
3
3
  Native apps are public clients: they cannot keep a client secret. Use authorization code with PKCE and launch Lunch Money sign-in and consent in a trusted platform authentication browser—not an embedded WebView.
4
4
 
5
+ Lunch Money requires PKCE with the `S256` challenge method for every client, public or confidential. Native clients send no client secret.
6
+
5
7
  ## Recommended libraries
6
8
 
7
9
  - iOS and macOS: [`AppAuth-iOS`](https://github.com/openid/AppAuth-iOS), presented through `ASWebAuthenticationSession`.
@@ -13,13 +15,22 @@ Use Lunch Money's authorization-server discovery document and configure `token_e
13
15
 
14
16
  ## Redirects
15
17
 
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.
18
+ Use a redirect mechanism that returns control to your app and that your platform can bind to it. Claimed HTTPS redirects 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.
19
+
20
+ Lunch Money accepts a well-formed non-loopback HTTPS redirect URI but does not fetch or validate an Apple `apple-app-site-association` file or Android `assetlinks.json` file. Platform association remains your responsibility:
21
+
22
+ - For AppAuth-iOS HTTPS redirects, use iOS 17.4 or later, add an Associated Domains entitlement for the redirect host with the `webcredentials` service, and publish an `apple-app-site-association` file that lists the app under `webcredentials`. An `applinks` association by itself is not sufficient for AppAuth-iOS.
23
+ - For Android App Links, add an HTTPS intent filter with `android:autoVerify="true"` for the redirect host and publish `https://HOST/.well-known/assetlinks.json` with the app package name and signing-certificate fingerprint.
24
+
25
+ Test the installed application with the production-signed association before release. Successful client registration proves only that the redirect URI is syntactically eligible; it does not prove that the operating system will return the authorization response to your app.
17
26
 
18
27
  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. The loopback hosts are not interchangeable: a client registered with `http://127.0.0.1/cb` cannot authorize with `http://localhost:4000/cb` or `http://[::1]:4000/cb`, so register the host your app actually binds to.
19
28
 
20
29
  ## Store and renew tokens
21
30
 
22
- Store tokens in Keychain on Apple platforms. On Android, use current Keystore-backed storage guidance from your maintained library; do not start new work with deprecated `EncryptedSharedPreferences` APIs. Coordinate refresh-token use, persist replacements, and fall back to interactive authorization after terminal failure.
31
+ Store tokens in Keychain on Apple platforms. If an app and its extensions both refresh, place the complete credential set in a Keychain access group they can share. On Android, use current Keystore-backed storage guidance from your maintained library; do not start new work with deprecated `EncryptedSharedPreferences` APIs.
32
+
33
+ Serialize refreshes with a cross-process lock for each connection. After acquiring the lock, re-read the stored credential set because another process may already have refreshed it. Persist the complete replacement set before using the new access token or releasing the lock. If a successful refresh response is lost before it is saved, do not retry with the consumed refresh token: replay revokes the token family, so the user must authorize again.
23
34
 
24
35
  After every code exchange, call and strictly validate `GET /v2/me` before adding the credentials to the app's normal connection store. For an app without its own user accounts, identify each local connection by the returned Lunch Money user `id` and budget-account `account_id` pair. Atomically create a record for a new pair or replace the credential set for an existing pair. Two Lunch Money users may authorize the same shared `account_id`; their credentials must remain in separate records. If identification or persistence fails, discard the temporary credentials instead of saving a partial or unidentified connection. Retain `budget_name` for display, but never use it as a key because different budgets can share a name.
25
36
 
@@ -45,6 +56,34 @@ Sending tokens to an application backend introduces server-side credential stora
45
56
 
46
57
  Platform code should delegate protocol validation to the maintained library while your application supplies safe configuration, lifecycle storage, API calls, revocation, and user-facing recovery.
47
58
 
48
- Lunch Money does not currently provide complete Swift or Kotlin samples; the upstream AppAuth projects provide maintained platform examples. Expo developers can use the [native Expo sample](/oauth/sample-applications#native-expo-sample) for a complete Lunch Money walkthrough and reference implementation.
59
+ ## AppAuth-iOS outline
60
+
61
+ Use AppAuth-iOS to discover Lunch Money's authorization-server metadata from `https://api.lunchmoney.dev`, then create an `OIDAuthorizationRequest` with the discovered configuration, your client ID, no client secret, the registered redirect URI, and the authorization-code response type. AppAuth-iOS presents the request with `ASWebAuthenticationSession`, generates an `S256` PKCE challenge, handles the callback, and exchanges the code when you use its authorization-state convenience flow.
62
+
63
+ The following outline is illustrative; adapt ownership, error handling, and persistence to your application:
64
+
65
+ ```swift
66
+ let issuer = URL(string: "https://api.lunchmoney.dev")!
67
+
68
+ OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in
69
+ guard let configuration else { return handle(error) }
70
+ let request = OIDAuthorizationRequest(
71
+ configuration: configuration,
72
+ clientId: clientID,
73
+ clientSecret: nil,
74
+ scopes: nil,
75
+ redirectURL: redirectURL,
76
+ responseType: OIDResponseTypeCode,
77
+ additionalParameters: nil
78
+ )
79
+ authorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: viewController) { state, error in
80
+ // Temporarily hold state, then validate GET /v2/me before saving it.
81
+ }
82
+ }
83
+ ```
84
+
85
+ After exchange, call `GET /v2/me` with the access token and strictly validate the Lunch Money user and budget-account identity before saving the credential set. For refresh, acquire the connection's cross-process lock, re-read the Keychain access-group record, ask `OIDAuthState` for a fresh access token only if the stored state still needs refresh, save the complete replacement state, and then make the API request. Treat a terminal refresh failure as a request for interactive authorization.
86
+
87
+ This is integration guidance, not a complete Swift sample. The upstream AppAuth-iOS project remains the source for current library APIs and platform setup. Expo developers can use the [native Expo sample](/oauth/sample-applications#native-expo-sample) for a complete Lunch Money walkthrough and reference implementation.
49
88
 
50
89
  Next: [Review OAuth security guidance](/oauth/security).
@@ -1,19 +1,19 @@
1
1
  # Register an OAuth client
2
2
 
3
- Before you add OAuth to your application code, register an OAuth client with Lunch Money. The client identifies your application during authorization and tells Lunch Money who is requesting access, where to return the user, and which permissions the application needs.
3
+ Before you add OAuth to your application code, register an OAuth client with Lunch Money. The client identifies your application during authorization and tells Lunch Money who is requesting access, where to return the user, and which scopes the application needs.
4
4
 
5
5
  [Register a new OAuth client](/oauth/applications/new) in the Developer Portal when you are ready to begin.
6
6
 
7
7
  > [!NOTE] Development access is owner-only
8
8
  > A newly registered client starts in development. Only the Lunch Money user who owns it can authorize it until the client has been reviewed and approved.
9
9
 
10
- ## Plan the permissions
10
+ ## Plan the scopes
11
11
 
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.
12
+ List the Lunch Money features your application will provide, then use the [OAuth scopes](/oauth/scopes) page to choose the smallest complete set of scopes 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
14
  If your application may support more than one Lunch Money user or budget account, always include `me:read`. After authorization, `GET /v2/me` lets your application record both the Lunch Money user's `id` and the selected budget account's `account_id`, then store the connection under the complete application-owner and Lunch Money user/account identity. The same scope is also required when your application only needs to identify one connected budget account.
15
15
 
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.
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 scopes—settled before you submit the client for review.
17
17
 
18
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.
19
19
 
@@ -30,7 +30,7 @@ The client type cannot be changed after registration. See [OAuth concepts](/oaut
30
30
 
31
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.
32
32
 
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.
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 non-loopback HTTPS link. Lunch Money checks that the HTTPS URI is well formed but does not verify an Apple or Android association file when you register it. Configure the URI as a claimed HTTPS redirect on the target platform before relying on it; see the [native redirect guidance](/oauth/native-apps#redirects).
34
34
 
35
35
  After the client is approved, you can add or replace redirect URIs in the Developer Portal without submitting another initial review. An approved confidential web client must keep a production HTTPS callback. See [after approval](/oauth/review-and-approval#after-approval).
36
36
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Every new OAuth client starts in development, where only its owner can authorize it. This gives you time to build and test the application without making an unfinished integration available to other Lunch Money users.
4
4
 
5
- When the application is ready, submit its client for review in the Developer Portal. A Lunch Money employee reviews the application information, requested permissions, redirect configuration, and other readiness requirements. Approval moves the client to `active`, allowing other Lunch Money users to authorize the application.
5
+ When the application is ready, submit its client for review in the Developer Portal. A Lunch Money employee reviews the application information, requested scopes, redirect configuration, and other readiness requirements. Approval moves the client to `active`, allowing other Lunch Money users to authorize the application.
6
6
 
7
7
  ## Lifecycle states
8
8
 
@@ -27,16 +27,16 @@ Before requesting review, make sure the client has:
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
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
- - a recognized, supported scope set and an explanation of why the application needs each permission; and
30
+ - a recognized, supported scope set and an explanation of why the application needs each scope; and
31
31
  - acceptance of the current Lunch Money API Terms of Use.
32
32
 
33
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
34
 
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.
35
+ Fully test the client before submission. Testing by the client owner is sufficient; submission does not require beta-tester coverage. A confidential web client also needs at least one active, non-expired secret; a native or public client does not.
36
36
 
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.
37
+ Explain which features use each requested scope, 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.
38
38
 
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.
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 scopes it requests. Publishing source code is optional and is not required for approval.
40
40
 
41
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
42
 
@@ -44,7 +44,10 @@ A confidential web client may retain owner-only loopback callbacks alongside its
44
44
 
45
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
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.
47
+ Lunch Money can approve a native client before its public App Store or Google Play release when the reviewer can access a TestFlight, other beta, or pre-release build. 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
+ > [!NOTE] Prepare for Apple App Review
50
+ > If the iOS app requires Lunch Money authorization, provide Apple with a dedicated test Lunch Money account in the App Store Connect review information rather than a personal account. Make sure the account and its test budget remain available throughout review. Two-factor authentication can prevent the reviewer from signing in unless you also provide a usable authentication code or another review-ready access path in the review notes.
48
51
 
49
52
  Native clients may be reviewed with loopback or private-use scheme redirects.
50
53
 
@@ -1,13 +1,13 @@
1
1
  # OAuth scopes
2
2
 
3
- Choose the smallest complete set of permissions your application needs. This page is generated from the published `@lunch-money/v2-api-spec/oauth-scopes` catalog; do not edit it by hand.
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 permissions later, create and review a replacement client, update your integration, and ask every user to authorize it.
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 extra permissions. 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.
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
 
@@ -20,7 +20,9 @@ The [confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your
20
20
 
21
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.
22
22
 
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.
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. When more than one process can refresh the same connection, use a shared credential store and a cross-process lock. After acquiring the lock, re-read the stored refresh token because another process may already have replaced it. Persist the complete replacement set before using the new access token or releasing the lock.
24
+
25
+ Lunch Money rotates refresh tokens on every successful refresh. A consumed token cannot be retried: replay revokes the token family. If the response to a successful refresh is lost before the replacement set is saved, require the user to authorize again rather than retrying the previous token. Treat other 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.
24
26
 
25
27
  ## Revoke access
26
28
 
@@ -129,7 +129,7 @@ WWW-Authenticate: Bearer error="insufficient_scope", scope="transactions:read"
129
129
  }
130
130
  ```
131
131
 
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 the [scope catalog](/oauth/scopes) to confirm its requirements.
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.
133
133
 
134
134
  ### Replace a client to change scopes
135
135
 
@@ -12,6 +12,12 @@ If you exceed this limit, you will receive a `429 Too Many Requests` response.
12
12
  > [!NOTE] Rate limit scope
13
13
  > Rate limiting is applied per IP address. Requests from the same IP address share the same rate limit quotas.
14
14
 
15
+ ## OAuth token requests
16
+
17
+ Requests to `/oauth/token`, including refresh requests, use a separate per-IP OAuth limit shared with `/oauth/introspect`. The default is 300 requests per window, and the default window is one minute. These requests do not count against the API limit applied to `/v1/` and `/v2/` resource requests.
18
+
19
+ Because this limit is keyed by IP address, unrelated users behind the same office, mobile-carrier, or other network address translation (NAT) gateway can share it. If the token endpoint returns `429 Too Many Requests`, respect `Retry-After` and back off with jitter instead of repeatedly sending the token request.
20
+
15
21
  ## Rate limit response
16
22
 
17
23
  When you exceed a rate limit, the API returns a `429 Too Many Requests` response:
package/manifest.json CHANGED
@@ -319,7 +319,7 @@
319
319
  { "label": "Troubleshooting", "path": "/oauth/troubleshooting" },
320
320
  { "label": "Review and Approval", "path": "/oauth/review-and-approval" },
321
321
  { "label": "Native Apps", "path": "/oauth/native-apps" },
322
- { "label": "Scope Catalog", "path": "/oauth/scopes" }
322
+ { "label": "Scopes", "path": "/oauth/scopes" }
323
323
  ]},
324
324
  { "section": "REFERENCE", "items": [
325
325
  { "label": "v2 API Overview", "path": "/v2/overview" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunch-money/developer-docs",
3
- "version": "2.11.2-preview.10",
3
+ "version": "2.11.2-preview.12",
4
4
  "description": "Developer documentation content for Lunch Money APIs",
5
5
  "exports": {
6
6
  ".": "./package.json",
@@ -1,6 +1,6 @@
1
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 budget-account selector and requested permissions, 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>
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>
@@ -23,7 +23,7 @@
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="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 permissions</text>
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
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>
@@ -400,7 +400,7 @@ components:
400
400
  children:
401
401
  type: array
402
402
  nullable: false
403
- description: List of categories to include in the new category group. This field should only be set if `is_group` is also set to true.<br> You may provide existing category objects, existing category IDs, or names for new categories to add to the group. Categories or IDs must already exist and cannot be category groups. Categories that already belong to another group will be moved. If strings are provided, they will be used as names for new categories added to the group. The request will fail if any provided name already exists.<br> You may mix category objects, IDs, and new category names in the same request.
403
+ description: Categories to include in the new category group. Set only when `is_group` is `true`.<br> You may provide existing category objects, existing category IDs, or names for new categories. IDs and objects must refer to existing categories and cannot refer to category groups. Existing categories are added to the new group; categories that already belong to another group are moved. Each string is used as the name of a new category, and the request fails if that name already exists.<br> You may mix category objects, IDs, and new category names in the same request. Adding or moving an existing category updates it, so OAuth clients that include at least one existing category ID or object in `children` must have both `categories:create` and `categories:update`; without both scopes, the request returns a `403` `insufficient_scope` error. If every child is specified by name, `categories:create` alone is sufficient.
404
404
  items:
405
405
  oneOf:
406
406
  - type: integer
@@ -5456,12 +5456,15 @@ paths:
5456
5456
  tags:
5457
5457
  - categories
5458
5458
  summary: Create a new category or category group
5459
- description: Creates a new category with the given name.<br> If the `is_group` attribute is set to true, a category group is created. In this case, the `children` attribute may be set to an array of category IDs to add to the newly created category group.
5459
+ description: Creates a new category with the given name.<br> If `is_group` is `true`, creates a category group; use `children` to add categories to it. OAuth clients that set `children` may also need `categories:update`; see the `children` property for the exact requirement.
5460
5460
  operationId: createCategory
5461
5461
  security:
5462
5462
  - bearerSecurity: []
5463
5463
  - oauth2Security:
5464
5464
  - categories:create
5465
+ - oauth2Security:
5466
+ - categories:create
5467
+ - categories:update
5465
5468
  requestBody:
5466
5469
  required: true
5467
5470
  content:
@@ -5554,6 +5557,8 @@ paths:
5554
5557
  - errMsg: Cannot specify a 'group_id' in request body if 'is_group' is also true
5555
5558
  "401":
5556
5559
  $ref: "#/components/responses/unauthorizedToken"
5560
+ "403":
5561
+ $ref: "#/components/responses/insufficientScope"
5557
5562
  "429":
5558
5563
  $ref: "#/components/responses/rateLimited"
5559
5564
  "500":