@lunch-money/developer-docs 2.11.3-preview.1 → 2.11.3-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.
@@ -1,370 +0,0 @@
1
- # OAuth scope catalog design proposal
2
-
3
- Status: first-cut ENG-616 implementation and inventory, pending consumer review.
4
-
5
- This document proposes a published, shared source of truth for OAuth scope
6
- definitions and scope-group presets published with the canonical V2 OpenAPI
7
- specification. It must still be reconciled with the server OAuth management API
8
- and database implementation before the catalog is finalized.
9
-
10
- ## Context and decisions
11
-
12
- The catalog is application code, not persistence. OAuth clients, grants, and
13
- tokens store scope-name strings. Server, Developer Portal, and authorization UI
14
- reconcile those strings with their installed catalog version.
15
-
16
- The detailed OAuth technical specification currently contains an exploratory
17
- read/write scope list and says write implies read. Those points are superseded
18
- for this proposal: the final inventory will use independent `:read`, `:create`,
19
- `:update`, and `:delete` authorities where CRUD applies, and no authority implies
20
- another. Presets express common combinations without changing authorization
21
- semantics. Names such as `resource:read` in this document are generic examples,
22
- not production decisions.
23
-
24
- This proposal makes these additional decisions:
25
-
26
- - Keep `OAuthScopeCategory` as `identity | offline | api`. The category
27
- distinguishes protocol scopes from API permissions. The published inventory
28
- includes `offline_access` for requesting continued access through refresh
29
- tokens, but does not publish OpenID Connect identity scopes (`openid`,
30
- `email`, or `profile`).
31
- - Add optional `lifecycleNotice` copy for deprecation and retirement warnings.
32
- It is display metadata, not runtime authorization policy.
33
- - Reject groups with identical member sets. This is simpler than display
34
- priorities and makes exact matching unambiguous.
35
- - Allow unmapped public operations during rollout, but require callers to make
36
- the choice explicit through an option such as
37
- `operationIdCoverage: 'allow-unmapped'`. Validation should switch to
38
- `require-all-mapped` before OAuth scope enforcement is declared complete.
39
- - Do not encode scope implication, expansion, grant rules, client approval,
40
- refresh-token policy, or consent policy in the catalog.
41
- - Use catalog `operationIds` as the authority for V2 endpoint permissions.
42
- Server decorators must be generated from or validated against this mapping so
43
- they cannot become a second source of truth.
44
- - Scope V1 of this catalog to the V2 API only.
45
- - Map non-CRUD mutations by effect: Plaid and synced-crypto refresh operations
46
- use `:update`; transaction split/group and their reversals use
47
- `transactions:update`; balance-history and budget upserts use `:update`.
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
-
52
- ## Package direction
53
-
54
- The catalog ships as a browser-safe subpath of the existing
55
- `@lunch-money/v2-api-spec` package so the catalog and V2 `operationId` values are
56
- versioned atomically:
57
-
58
- ```text
59
- src/oauth-scopes/ authored TypeScript catalog and helpers
60
- oauth-scopes/ compiled published subpath
61
- test/oauth-scopes/ catalog and V2 operationId validation
62
- ```
63
-
64
- The runtime entry point has no Node-only dependencies and performs no filesystem
65
- or YAML work. OpenAPI parsing remains in build/test tooling and is not exported
66
- from the browser-safe `@lunch-money/v2-api-spec/oauth-scopes` subpath.
67
-
68
- ## Public contract
69
-
70
- ```ts
71
- type OAuthScopeName = string
72
- type OAuthScopeCategory = 'identity' | 'offline' | 'api'
73
- type OAuthScopeLifecycle = 'active' | 'deprecated' | 'retired'
74
- type KnownOAuthScopeName = 'me:read' | /* ... */ 'budgets:delete'
75
-
76
- interface OAuthScopeDefinition {
77
- readonly name: KnownOAuthScopeName
78
- readonly description: string
79
- readonly category: OAuthScopeCategory
80
- readonly operationIds: readonly string[]
81
- readonly status: OAuthScopeLifecycle
82
- readonly lifecycleNotice?: string
83
- }
84
-
85
- interface OAuthScopeGroupDefinition {
86
- readonly id: string
87
- readonly name: string
88
- readonly description: string
89
- readonly scopes: readonly KnownOAuthScopeName[]
90
- }
91
-
92
- interface OAuthCatalogDefinition {
93
- readonly scopes: readonly OAuthScopeDefinition[]
94
- readonly groups: readonly OAuthScopeGroupDefinition[]
95
- }
96
- ```
97
-
98
- The `oauth-scopes` subpath exports:
99
-
100
- - The interfaces and aliases above, plus result and issue types.
101
- - `validateOAuthCatalog(definition, options)` returns all structured issues.
102
- - `getOAuthScopeDefinition(name)` returns a definition or `undefined`.
103
- - `getOAuthScopeForOperationId(operationId)` returns the authoritative scope
104
- definition for a V2 operation or `undefined`.
105
- - `getOAuthScopeGroup(id)` returns a group or `undefined`.
106
- - `matchOAuthScopeGroup(names)` returns a discriminated matched/no-match
107
- result.
108
- - Immutable `oauthCatalog`, `oauthScopes`, and `oauthScopeGroups` exports.
109
-
110
- `OAuthScopeName` remains `string` for persisted and API-facing input so consumers
111
- can preserve names newer than their installed library. `KnownOAuthScopeName`
112
- provides autocomplete and compile-time checking for this catalog version.
113
-
114
- ## Runtime validation and immutability
115
-
116
- The published definitions and nested arrays are frozen. Private lookup maps
117
- support explicit failed lookups, and `validateOAuthCatalog` returns structured
118
- issues for build/test tooling without importing Node-only code into the runtime.
119
-
120
- Validation checks:
121
-
122
- - stable lowercase colon-delimited scope names and lowercase underscore group IDs;
123
- - required descriptions and valid category/lifecycle values;
124
- - unique scope names and group IDs;
125
- - non-empty API `operationIds`, except retired tombstones;
126
- - no operation IDs on identity or offline scopes;
127
- - duplicate operation IDs within a scope;
128
- - conflicting operation ownership unless an ID is deliberately allowlisted;
129
- - non-empty groups with unique members;
130
- - group members exist and are not retired;
131
- - offline scopes are excluded from groups;
132
- - duplicate group member sets are rejected.
133
-
134
- The runtime has no mutable singleton. Consumers import the frozen catalog or
135
- construct catalog fixtures in tests. Public helpers return
136
- `undefined` or discriminated results rather than using non-null assertions.
137
-
138
- ## Protocol scopes
139
-
140
- `OAuthScopeCategory` includes `identity` and `offline` so validation can
141
- distinguish protocol scopes from API permissions. The published catalog ships
142
- `offline_access` alongside its API scopes.
143
-
144
- Lunch Money's authorization server exposes OAuth 2.1 authorization-server
145
- metadata, API scopes, and the `offline_access` protocol scope:
146
-
147
- - no `openid`, `email`, or `profile` identity scopes;
148
- - active `offline_access` with no V2 operation IDs;
149
- - no ID tokens, UserInfo, or public signing keys.
150
-
151
- `offline_access` represents a request for continued access when the user is not
152
- actively using the app. The authorization server still owns the decision to
153
- issue a refresh token based on the requested scope, client capability, consent,
154
- and server policy.
155
-
156
- No resource group may contain an offline-category scope. Identity scopes are
157
- not categorically banned from a group; any future identity-oriented preset
158
- should be an explicit catalog decision.
159
-
160
- ## Lifecycle and consumer behavior
161
-
162
- Lifecycle is code-only metadata:
163
-
164
- | Status | New assignment/request | Existing full replacement | Token issuance | Recognition/display |
165
- | --- | --- | --- | --- | --- |
166
- | `active` | allowed | allowed | allowed | yes |
167
- | `deprecated` | rejected if newly introduced | preserved if already assigned | allowed for existing grants per server rollout | yes, with warning |
168
- | `retired` | rejected | rejected | rejected | yes, as tombstone |
169
-
170
- The library provides state and copy; consumers enforce transitions in context.
171
- For example, only the server can know whether a deprecated name is newly added
172
- or preserved during a full replacement. Lifecycle must never be copied into a
173
- client row. A stored name absent from the installed catalog is `unknown`, not
174
- implicitly retired.
175
-
176
- Recommended reconciliation status for management APIs:
177
-
178
- ```ts
179
- type ScopeConfigurationStatus = 'valid' | 'needs_attention'
180
- ```
181
-
182
- `needs_attention` applies when stored configuration contains an unknown or
183
- retired scope. A deprecated-only configuration remains valid but returns warning
184
- metadata. The exact API response shape is a server-design coordination point.
185
-
186
- ## Compatibility baseline and change classes
187
-
188
- After the first production catalog is approved, check in a canonical JSON
189
- snapshot at `compatibility/catalog-baseline.json`. Future CI compares source
190
- against this file and does not fetch npm. The baseline updates only as part of a
191
- reviewed release and must represent the most recently published authority map.
192
- Canonical ordering makes diffs stable.
193
-
194
- Compatibility checks detect deletion, category changes/name reuse, authority
195
- broadening, operation remapping, lifecycle changes, and group membership/removal.
196
- The regular catalog validator separately detects broken references and duplicate
197
- ownership.
198
-
199
- Change classification:
200
-
201
- - Patch-compatible: descriptions, names shown to users, and lifecycle notice
202
- copy change without changing authority.
203
- - Minor-compatible: a new scope or group is added. New scopes must not be added
204
- to an existing group in the same classification; that is a membership change.
205
- - Migration-requiring: deprecation, retirement, removal of authority from a
206
- scope, any group membership change, or group removal.
207
- - Breaking/forbidden without explicit migration: deleting a known scope rather
208
- than retaining a tombstone, changing its category, reusing its name for a
209
- different authority, or adding operation IDs to an existing scope.
210
-
211
- Name reuse cannot be detected from names alone. Preserving every old definition
212
- as a retired tombstone plus comparing category and operation ownership provides
213
- the deterministic safeguard. Review policy must treat meaning changes not
214
- visible in structured data as forbidden. A future `authorityFingerprint` could
215
- make that stronger if V1 endpoints or non-operation authorities join the model.
216
-
217
- A future comparison helper should treat a retired tombstone with removed
218
- operation IDs as migration-requiring rather than breaking. Tombstones should
219
- remain small while retaining enough description to diagnose old persisted names.
220
-
221
- ## Exact-set group matching
222
-
223
- Groups are UI presets only. A selected group becomes its concrete member names;
224
- the group ID is neither sent as authority nor persisted in client, grant, code,
225
- or token configuration.
226
-
227
- Matching performs these steps:
228
-
229
- 1. Reject a candidate containing an unknown or duplicate scope name with a
230
- distinct no-match reason.
231
- 2. Canonically sort the complete candidate set.
232
- 3. Compare it with each complete canonical group member set.
233
- 4. Return exactly one matched group or `no_match`.
234
-
235
- An additional recognized scope prevents a match. The helper never searches for
236
- subsets, decomposes a set into overlapping groups, or infers historical preset
237
- selection. A changed preset causes old clients to fall back to individual scope
238
- display. Duplicate member sets are invalid, so ambiguity cannot enter a valid
239
- catalog.
240
-
241
- Product analytics may separately record that a preset was selected, but that
242
- event is historical telemetry and has no authorization meaning.
243
-
244
- ## OpenAPI operationId validation
245
-
246
- The V2 YAML is authoritative for public operation IDs. Node-only test tooling
247
- parses `v2/spec/lunch-money-api-v2.yaml`, traverses HTTP operations, and returns a set
248
- while rejecting missing or duplicate IDs. Catalog validation then:
249
-
250
- - verifies every API-category operation ID exists in the spec;
251
- - rejects conflicting ownership unless a deliberate `sharedOperationIds`
252
- allowlist contains the ID;
253
- - permits identity/offline scopes to have no operation IDs;
254
- - reports stale and unknown IDs with catalog paths;
255
- - optionally reports every unmapped public operation.
256
-
257
- The validation modes are:
258
-
259
- - `allow-unmapped`: rollout mode; catalog entries must be correct, but endpoints
260
- can remain unmapped while enforcement is staged.
261
- - `require-all-mapped`: completion mode; every public operation must have an
262
- owner or be removed from the public spec.
263
-
264
- If some public operations are intentionally unscoped (for example a protocol or
265
- health endpoint represented in the same document), add a named exclusion list
266
- with a justification per entry rather than silently omitting them. Shared
267
- operation ownership should likewise be rare and explicit. The tooling never
268
- modifies the OpenAPI document.
269
-
270
- V1 endpoints are deliberately outside this catalog. Do not overload V2
271
- operation IDs with invented V1 values.
272
-
273
- ## Consumer integration
274
-
275
- ### Server
276
-
277
- - Pin an exact catalog package version in deploys.
278
- - Persist only scope-name strings.
279
- - Validate requested/client/grant scopes against the installed catalog.
280
- - Reject newly introduced deprecated scopes and all retired/unknown scopes.
281
- - Preserve deprecated scopes on an existing client's full replacement only
282
- when they were already present.
283
- - Enforce operation authority from the catalog without implication.
284
- - Treat `offline_access` as a request for refresh-token-backed access while
285
- retaining the server-owned issuance decision based on client capability and
286
- consent policy.
287
- - Return unknown names so operators and UIs can diagnose drift.
288
-
289
- ### Developer Portal
290
-
291
- - Render active definitions and groups from the package.
292
- - Render deprecated existing selections with `lifecycleNotice`; do not offer
293
- them for new selection.
294
- - Render retired/unknown stored names as needing attention.
295
- - Submit concrete scope names, never group IDs.
296
- - Use exact matching only as a display convenience.
297
-
298
- ### Authorization/consent UI
299
-
300
- - Resolve the server-approved requested names using the same pinned catalog
301
- compatibility range.
302
- - Display groups only on exact match; otherwise display individual definitions.
303
- - Call out API mutation scopes from structured category and scope data, while
304
- keeping the actual consent/issuance decision server-owned.
305
- - Fail closed on unknown or retired requested names.
306
-
307
- Consumers must not independently copy definitions or group membership. During a
308
- rolling deploy, the server is authoritative: it must not send a scope newly
309
- introduced by a catalog version that the UI deployment cannot understand. Pinning
310
- one exact version across a coordinated release is preferred.
311
-
312
- ## Versioning and publishing
313
-
314
- Publish the catalog with `@lunch-money/v2-api-spec`. A spec-package version pins
315
- both the YAML and its catalog, preventing operation mappings from drifting.
316
- Use the change classes above when selecting the package version:
317
-
318
- - patch for copy-only and implementation fixes;
319
- - minor for additive catalog entries or backward-compatible API additions;
320
- - major only with an approved migration for a public contract break;
321
- - migration-requiring catalog changes may still use minor versions before 1.0,
322
- but release notes and server coordination are mandatory.
323
-
324
- Start with prereleases until all three consumers validate the contract. Existing
325
- spec-package release tooling builds the subpath and verifies package contents
326
- before publishing.
327
-
328
- Each server release should pin an exact package version. Portal and consent UI
329
- should use the same version or a declared compatibility window validated in CI.
330
- Never depend on an npm `latest` lookup at application startup.
331
-
332
- ## Reconciliation required with the server design
333
-
334
- Before this proposal becomes final, compare these exact artifacts:
335
-
336
- 1. Scope string grammar, especially resource separators and full CRUD verbs.
337
- 2. `identity | offline | api` categories and `active | deprecated | retired`
338
- lifecycle values.
339
- 3. Group schema and the decision to reject duplicate member sets.
340
- 4. The discriminated exact-match result and unknown-scope handling.
341
- 5. `scope_configuration_status` semantics, warning/error details, and whether
342
- deprecated-only configuration remains `valid`.
343
- 6. Management API request/response shapes: concrete scope names only, with no
344
- persisted group authority.
345
- 7. Full-replacement rules for preserving deprecated scopes.
346
- 8. `offline_access` request and refresh-token issuance semantics, and
347
- confirmation that OIDC identity scopes are rejected.
348
- 9. How server `tsoa` operation requirements consume catalog mappings without
349
- creating a second source of truth.
350
- 10. Exact package versions supported by server, Portal, and consent deployments,
351
- including rolling-deploy behavior.
352
- 11. The server database's representation of unknown names and its mapping to
353
- `valid | needs_attention`.
354
-
355
- The server OpenAPI management schemas should remain string-based so adding a
356
- catalog name does not require an API schema revision.
357
-
358
- ## Recommended ENG-616 follow-up
359
-
360
- 1. Reconcile this proposal with the server agent's OpenAPI and database artifacts.
361
- 2. Confirm the V2 operation coverage/exclusion policy.
362
- 3. Review and approve the first-cut full-CRUD inventory and descriptions; do not
363
- derive authority from the older read/write draft.
364
- 4. Check in the first canonical compatibility baseline with that inventory.
365
- 5. Add CI that builds/tests the spec package and validates the production catalog
366
- against the local V2 YAML in the chosen coverage mode.
367
- 6. Select a prerelease version, verify the tarball, and test it through local
368
- `file:` or packed dependencies in all consumers.
369
- 7. Publish only after server, Portal, and consent UI agree on the contract and
370
- rolling-version policy.
@@ -1,64 +0,0 @@
1
- # Register an OAuth client
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 scopes the application needs.
4
-
5
- [Register a new OAuth client](/oauth/applications/new) in the Developer Portal when you are ready to begin.
6
-
7
- > [!NOTE] Development access is owner-only
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
-
10
- ## Plan the scopes
11
-
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
-
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
-
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
-
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
-
20
- ## Choose the client type
21
-
22
- Select the type based on whether your application code can keep a client secret secure:
23
-
24
- - **Confidential web client:** Choose this when a trusted server can store a client secret, including for applications that are not traditional websites but use a centralized server.
25
- - **Native or public client:** Choose this for standalone applications installed on a user-controlled device. These clients use PKCE without a client secret. Review the [native application guidance](/oauth/native-apps) before choosing redirects for this type.
26
-
27
- The client type cannot be changed after registration. See [OAuth concepts](/oauth/concepts#client-types-and-credentials) for more detail about the two types.
28
-
29
- ## Register redirect URIs
30
-
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
-
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
-
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
-
37
- Each client can have up to 10 redirect URIs, and each URI can be up to 2,048 characters. If you reach the limit, remove a callback the application no longer uses before adding another.
38
-
39
- ## Register the OAuth client
40
-
41
- Open the [new OAuth client form](/oauth/applications/new) in the Developer Portal. Then:
42
-
43
- 1. Describe the application so Lunch Money users can understand who built it and what it does.
44
- 2. Provide a monitored support email where users can reach you if authorization fails.
45
- 3. Choose its client type.
46
- 4. Add at least one redirect URI.
47
- 5. Select the planned scopes.
48
- 6. Accept the current Lunch Money API Terms of Use and select **Register OAuth client**.
49
-
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
-
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
-
54
- After registration, record the client ID in your application configuration. A client ID identifies the registration but is not a secret.
55
-
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.
57
-
58
- A confidential web client can have up to three non-revoked secrets. This allows you to introduce a replacement secret, update and verify the deployed application, and then revoke the old secret without interrupting authorization. Expired secrets still count toward the limit until you revoke them. Revoking an obsolete secret frees a slot, and revoked secrets no longer appear in the client's secret list. See [OAuth security guidance](/oauth/security#operate-credentials-safely) when planning ongoing rotation.
59
-
60
- You now have the client settings needed to connect your application to Lunch Money.
61
-
62
- 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).
63
-
64
- Next: [Implement authorization](/oauth/authorization-code).
@@ -1,84 +0,0 @@
1
- # OAuth client review and approval
2
-
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
-
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
-
7
- ## Lifecycle states
8
-
9
- | State | What it means | Owner action |
10
- | --- | --- | --- |
11
- | `development` | The client has not been submitted for review; only its owner can authorize it | Configure and test, then request review. |
12
- | `pending_review` | Review has been requested and is waiting for or receiving review; review-critical fields are frozen | Continue owner testing or cancel the request. |
13
- | `active` | The review was approved; other Lunch Money users may authorize the application | Keep configuration current; identity updates reach users after Lunch Money reviews them. |
14
- | `rejected` | Changes were requested | Read feedback, revise editable configuration, and resubmit. |
15
- | `disabled` | Lunch Money disabled the client; its owner is notified, authorization is unavailable, and existing grants no longer work | Review the reason provided and contact developer support when appropriate. |
16
-
17
- Deleted clients disappear from the owner's Developer Portal.
18
-
19
- ## Prepare for review
20
-
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
-
23
- Before requesting review, make sure the client has:
24
-
25
- - an application description, developer name, and logo so users can identify the application and who created it;
26
- - a confirmed, monitored support email where users and Lunch Money can reach you;
27
- - a publicly accessible homepage URL where users can learn about the application;
28
- - a publicly accessible privacy policy URL explaining how the application handles user data;
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 scope; and
31
- - acceptance of the current Lunch Money API Terms of Use.
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
-
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
-
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
-
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
-
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
- 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
-
45
- ### Native applications
46
-
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.
48
-
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 account 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.
53
-
54
- Native clients may be reviewed with loopback or private-use scheme redirects.
55
-
56
- ## Submit, cancel, or revise
57
-
58
- Submitting the request moves the client to `pending_review` and temporarily freezes review-critical fields. Reviews are usually completed within a couple of business days. You can continue developing and testing the application as the client owner while you wait. If you need to change a frozen field, cancel the request to return the client to `development`, make the change, and submit it again.
59
-
60
- You can make up to five initial review submissions across all your clients in a rolling 24-hour period. Cancelled submissions and submissions made again after a rejection still count. Deleting a client does not remove its submissions from the window. Reviews opened automatically after you update the identity of an active client do not count toward this limit.
61
-
62
- If you reach the limit, the Developer Portal tells you when you can submit again. Wait until that time before retrying; cancelling, deleting, or replacing a client does not reset the window.
63
-
64
- Lunch Money emails you when the review is complete. You can also check the current status and review history on the client's page in the Developer Portal.
65
-
66
- ## If review is denied
67
-
68
- If the review is denied, the Developer Portal shows feedback explaining what needs to change. Update the client's editable configuration and submit a new review request. Editing the client alone does not resubmit it or clear the `rejected` status.
69
-
70
- There is no review comment thread, so use the new request's additional context to explain how you addressed the feedback. The Portal keeps the earlier request in the client's review history.
71
-
72
- ## After approval
73
-
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.
75
-
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.
79
-
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.
81
-
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.
83
-
84
- Next: [Review the security checklist](/oauth/security).