@lunch-money/developer-docs 2.11.2-preview.11 → 2.11.2-preview.13

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:
@@ -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.
@@ -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,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).
@@ -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
 
@@ -49,6 +49,8 @@ Open the [new OAuth client form](/oauth/applications/new) in the Developer Porta
49
49
 
50
50
  You can own up to 25 non-deleted OAuth clients. If you reach the limit, delete a client you no longer use before registering another.
51
51
 
52
+ The support email must be confirmed before you can submit the client for review. If it matches your own verified Lunch Money account email, Lunch Money confirms it automatically. For any other address—including one used by another Lunch Money account—Lunch Money sends a confirmation link from `support@lunchmoney.app` to that mailbox. The link is valid for 72 hours, so use an address you can access and follow the link promptly.
53
+
52
54
  After registration, record the client ID in your application configuration. A client ID identifies the registration but is not a secret.
53
55
 
54
56
  If you registered a **Confidential web client**, create a client secret from its details page. Lunch Money shows the secret value only once. Copy it immediately into a secret manager or protected server configuration; it cannot be retrieved later. Never commit it or expose it in browser or mobile code.
@@ -23,7 +23,7 @@ Submitting a client for review is how you make your application authorizable by
23
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
- - a monitored support email where users and Lunch Money can reach you;
26
+ - a confirmed, 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
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;
@@ -32,7 +32,7 @@ Before requesting review, make sure the client has:
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
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
 
@@ -40,11 +40,16 @@ Open-source applications are welcome. If your source code is public, include the
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
 
43
+ Confirm the support email from the mailbox before you submit the client. The Developer Portal's readiness checklist shows whether it is confirmed and prevents submission until it is. If you change the address before approval, Lunch Money clears its confirmation; confirm the new address before the client can be submitted or approved.
44
+
43
45
  ### Native applications
44
46
 
45
47
  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
48
 
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.
49
+ 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.
50
+
51
+ > [!NOTE] Prepare for Apple App Review
52
+ > 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
53
 
49
54
  Native clients may be reviewed with loopback or private-use scheme redirects.
50
55
 
@@ -68,9 +73,11 @@ There is no review comment thread, so use the new request's additional context t
68
73
 
69
74
  An `active` client can be authorized by other users. Keep its configuration current in the Developer Portal. You do not need to contact Lunch Money to rename the application, move hosting, or update a support address.
70
75
 
71
- Changes save immediately, and the client stays `active`. Redirect URIs, support email, description, and developer name take effect as soon as you save them. An approved confidential web client must keep at least one public HTTPS redirect.
76
+ Changes save immediately, and the client stays `active`. Redirect URIs, description, and developer name take effect as soon as you save them. An approved confidential web client must keep at least one public HTTPS redirect.
77
+
78
+ Name, logo, support email, homepage URL, and privacy policy URL also save immediately, but Lunch Money users continue to see the last approved identity on the consent screen and Connected Apps page until Lunch Money reviews the update. The Developer Portal shows that as an identity update in review; it is not a return to `rejected`, and existing authorizations keep working.
72
79
 
73
- Name, logo, homepage URL, and privacy policy URL also save immediately, but Lunch Money users continue to see the last approved identity on the consent screen and Connected Apps page until Lunch Money reviews the update. The Developer Portal shows that as an identity update in review; it is not a return to `rejected`, and existing authorizations keep working.
80
+ When you change the support email, confirm the new address before Lunch Money can approve the identity update. Until it is confirmed and approved, users continue to see the previous approved address, and Lunch Money does not send notifications to the new address. Lunch Money tells the previously confirmed support address that the client was changed.
74
81
 
75
82
  Client-secret rotation and revocation remain available subject to lifecycle and security checks. Scopes and client type cannot be changed after registration. A scope change requires a replacement client and user reauthorization.
76
83
 
@@ -65,6 +65,10 @@ The [confidential Node.js sample](/oauth/sample-applications#understand-the-conf
65
65
 
66
66
  Choose only the resource scopes needed for expected features. Add `offline_access` only for genuine unattended operation. Remember that scope changes require a replacement client and reauthorization; they cannot be requested dynamically.
67
67
 
68
+ ## Keep the support address confirmed
69
+
70
+ Use a monitored support mailbox that your team controls, and keep it current in the Developer Portal. Lunch Money sends client notifications to a support address only after someone with access to that mailbox confirms it. Changing the address clears its confirmation, so follow the new confirmation link before relying on that mailbox for notifications.
71
+
68
72
  ## Operate credentials safely
69
73
 
70
74
  Rotate client secrets on a schedule appropriate for your application's risk and immediately when one may have been exposed. A client can have up to three non-revoked secrets, so confirm that it has room before starting a rotation. Expired secrets still count toward this limit until you revoke them. Use overlapping active secrets during a controlled rotation: create the replacement, deploy and verify it, and then revoke the old secret. Coordinate refreshes so two workers do not replay the same rotating token, and atomically persist every complete replacement credential set. Production storage needs database transactions, distributed per-connection locking, recovery when persistence fails, multi-instance coordination, and tenant isolation. Make disconnect invalidate continuing refresh access as well as clear local state.
@@ -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
 
@@ -35,7 +35,7 @@ Match on `error` when it is returned, verify `state`, and treat `error_descripti
35
35
 
36
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
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.
38
+ Those users are shown the support email registered with your client so they can reach you. For an active client with a support-email update under review, Lunch Money shows the last approved address rather than the newly saved one. 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
39
 
40
40
  ### Redirect URI problems
41
41
 
@@ -48,7 +48,7 @@ The page displays one of two codes, a UTC timestamp, and the application name an
48
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
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. |
50
50
 
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.
51
+ The page offers the user a prefilled message addressed to the support email registered with your client. For an active client with a support-email update under review, this is the last approved address, which can differ from the address currently saved in the Developer Portal. The message 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.
52
52
 
53
53
  ## Callback validation failures
54
54
 
@@ -135,17 +135,11 @@ The `scope` parameter names every scope the endpoint requires, including any the
135
135
 
136
136
  Adding `scope` to an authorization URL cannot upgrade or narrow a grant. Lunch Money ignores the requested value and uses the client's complete registered scope set. To change scopes, create a replacement client, test all functionality, complete any required review, update the deployed client ID and credentials, and send every existing user through authorization again.
137
137
 
138
- ## Client-management failures
138
+ ## Support email is not confirmed
139
139
 
140
- The Developer Portal can return structured errors when you register clients, create client secrets, or submit a client for review. These errors use the Lunch Money response format, not OAuth protocol errors such as `invalid_grant`.
140
+ You cannot submit a client for review until its support email is confirmed. Open the client in the [Developer Portal](/oauth/applications), check that the address is correct, and resend the confirmation email if needed. Check the mailbox's spam or junk folder for a message from `support@lunchmoney.app`.
141
141
 
142
- | Status and code | Meaning | What to do |
143
- | --- | --- | --- |
144
- | `422 quota_exceeded`, field `clients` | You already own 25 non-deleted OAuth clients. | Delete a client you no longer use before registering another. |
145
- | `422 quota_exceeded`, field `secrets` | The client already has three non-revoked secrets. Expired secrets still count. | Revoke an obsolete or expired secret before creating another. |
146
- | `429 review_quota_exceeded` | You have made five initial review submissions across your clients in the rolling 24-hour window. | Wait until the time shown in the Developer Portal before submitting again. API clients should also respect the `Retry-After` response header. |
147
-
148
- Cancelled submissions, rejected-client resubmissions, and submissions for clients later deleted still count toward the review limit. Reviews opened automatically after an active client's identity changes do not count.
142
+ Each confirmation link expires after 72 hours. You can resend at most once per hour; the Developer Portal shows when another resend is available. If the link reports that it is invalid or expired, return to the client and request a new one. Changing the support address also invalidates any link sent to the previous address.
149
143
 
150
144
  ## Local callback failures
151
145
 
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunch-money/developer-docs",
3
- "version": "2.11.2-preview.11",
3
+ "version": "2.11.2-preview.13",
4
4
  "description": "Developer documentation content for Lunch Money APIs",
5
5
  "exports": {
6
6
  ".": "./package.json",
@@ -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":