@lunch-money/developer-docs 2.11.2 → 2.11.4-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/docs/branding-your-app.md +1 -1
- package/docs/getting-started.md +21 -17
- package/docs/introduction.md +3 -2
- package/docs/oauth/authorization-code.md +110 -0
- package/docs/oauth/concepts.md +116 -0
- package/docs/oauth/development.md +73 -0
- package/docs/oauth/index.md +33 -0
- package/docs/oauth/native-apps.md +89 -0
- package/docs/oauth/oauth-scope-catalog-design.md +370 -0
- package/docs/oauth/register-client.md +64 -0
- package/docs/oauth/review-and-approval.md +84 -0
- package/docs/oauth/sample-applications.md +263 -0
- package/docs/oauth/scopes.md +104 -0
- package/docs/oauth/security.md +78 -0
- package/docs/oauth/tokens.md +83 -0
- package/docs/oauth/troubleshooting.md +159 -0
- package/docs/rate-limiting.md +6 -0
- package/docs/using-with-ai.md +2 -2
- package/manifest.json +110 -0
- package/package.json +3 -3
- package/v2/docs/intro-to-v2.md +1 -1
- package/v2/docs/version-history.md +7 -0
- package/v2/images/oauth-authorization-flow.svg +35 -0
- package/v2/images/oauth-token-lifecycle.svg +14 -0
- package/v2/spec/AGENTS.md +6 -0
- package/v2/spec/lunch-money-api-v2.yaml +1081 -1733
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Troubleshoot OAuth
|
|
2
|
+
|
|
3
|
+
OAuth problems are usually easiest to resolve once you identify where the flow stopped. An OAuth-related failure can occur in one of three places:
|
|
4
|
+
|
|
5
|
+
- **Authorization and callback:** your application sends the user to Lunch Money to authorize access, then Lunch Money returns the browser to your registered callback.
|
|
6
|
+
- **Token endpoint:** your application exchanges an authorization code for tokens or uses a refresh token to request replacements.
|
|
7
|
+
- **v2 API:** your application presents an access token while making a request for Lunch Money data.
|
|
8
|
+
|
|
9
|
+
When collecting diagnostic details, preserve nonsensitive information such as timestamps and error names, but leave credentials and full callback URLs out of logs and support messages.
|
|
10
|
+
|
|
11
|
+
For a runnable comparison, [try OAuth with the confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account). Reproduce the same stage in its end-to-end flow, then compare the result with the sample repository's [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/TROUBLESHOOTING.md) without copying credentials or full callback URLs.
|
|
12
|
+
|
|
13
|
+
> [!NOTE] Two error formats
|
|
14
|
+
> OAuth protocol endpoints under `/oauth/` use lower-case OAuth error names such as `invalid_grant`. The v2 API uses the Lunch Money response format, with a `message` and an `errors` array whose entries contain `errMsg`.
|
|
15
|
+
|
|
16
|
+
## Authorization endpoint failures
|
|
17
|
+
|
|
18
|
+
Authorization can fail before your application reaches the token endpoint. Lunch Money may return a lower-case OAuth error such as `invalid_request`, `invalid_scope`, or `access_denied`.
|
|
19
|
+
|
|
20
|
+
When Lunch Money can safely use the registered callback, it redirects the user's browser with an `error`, an optional `error_description`, the original `state`, and the authorization-server issuer `iss` as query parameters:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
https://example.com/oauth/callback?error=access_denied&state=RETURNED_STATE&iss=https%3A%2F%2Fapi.lunchmoney.dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Match on `error` when it is returned, verify `state`, and treat `error_description` as human-readable text that may change. Decode `iss` and compare it with the `issuer` value from authorization-server metadata using exact string comparison. Reject the callback if `iss` is missing or does not match; do not exchange a returned code.
|
|
27
|
+
|
|
28
|
+
| OAuth error | Likely cause | What to do |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `invalid_request` | A required parameter is missing, duplicated, or malformed | Compare the request with discovery metadata; send `response_type=code`, exact redirect URI, `state`, and PKCE `S256` values. |
|
|
31
|
+
| `invalid_scope` | The client does not have a usable registered scope set | Open the client in the [Developer Portal](/oauth/applications) and review its assigned scopes. Registered clients always use that complete set. |
|
|
32
|
+
| `access_denied` | The user declined or the application is unavailable to that user | Treat denial normally. During development, only the owner may authorize; other users require an active approved application. |
|
|
33
|
+
|
|
34
|
+
### Users cannot authorize an unapproved client
|
|
35
|
+
|
|
36
|
+
Until a client is approved, only its owner may authorize the application. Anyone else is stopped at the consent screen and is never returned to your application, so you will see no callback and no `error` parameter for these attempts.
|
|
37
|
+
|
|
38
|
+
Those users are shown the support email registered with your client so they can reach you. 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
|
+
|
|
40
|
+
### Redirect URI problems
|
|
41
|
+
|
|
42
|
+
If the callback address cannot be matched to one registered for the client, Lunch Money cannot safely return the browser to your application. It stops locally and shows the user a page explaining what happened, rather than redirecting with an `error`. Your application receives no callback for these attempts.
|
|
43
|
+
|
|
44
|
+
The page displays one of two codes, a UTC timestamp, and the application name and public client ID once the client has been resolved.
|
|
45
|
+
|
|
46
|
+
| Code | Likely cause | What to do |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `redirect_uri_mismatch` | The address your application sent is not registered for the client, or differs from a registered one | Open the client in the [Developer Portal](/oauth/applications) and compare the address with its registered redirect URIs. Web-client redirects and native HTTPS or private-use scheme redirects must match every component exactly. For a native HTTP loopback redirect, the scheme, host, path, and query string must match, but the ephemeral port may differ. See [local callback failures](#local-callback-failures). |
|
|
49
|
+
| `redirect_uri_missing` | No `redirect_uri` was sent, and the client has more than one registered, so Lunch Money cannot choose | Send the parameter explicitly. A client with exactly one registered address may omit it; Lunch Money then uses the registered value. |
|
|
50
|
+
|
|
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
|
+
|
|
53
|
+
## Callback validation failures
|
|
54
|
+
|
|
55
|
+
After Lunch Money returns the browser to the registered callback, your application must verify that the returned `state` matches the authorization attempt and that `iss` exactly matches the discovered issuer. A missing, changed, or reused `state`, or a missing or mismatched `iss`, is a callback validation failure detected by your application, not an error reported by Lunch Money. Stop the flow, discard the authorization code, clear the one-time state, and begin again. Never exchange the code.
|
|
56
|
+
|
|
57
|
+
Your application should show the user a safe error message and enough internal reference information for your team to find the failed attempt without exposing the callback URL or its parameters.
|
|
58
|
+
|
|
59
|
+
## Token endpoint failures
|
|
60
|
+
|
|
61
|
+
The token endpoint (`POST /oauth/token`) returns OAuth 2.0 errors rather than the Lunch Money v2 API error format. The error name appears in an `error` field and is always lower case:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"error": "invalid_grant",
|
|
66
|
+
"error_description": "grant request is invalid"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Match on `error`. Treat `error_description` as human-readable text that may change; it does not identify the specific cause.
|
|
71
|
+
|
|
72
|
+
| `error` value | Likely cause | What to do |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `invalid_request` | The request is incomplete or malformed, a parameter is duplicated, client authentication is missing or supplied more than once, or the requested grant is not enabled for this client | Do not retry the unchanged request. Confirm that the body is form encoded, each required parameter appears exactly once, and the client uses its registered authentication method. Confidential clients use HTTP Basic; public clients send `client_id` without a secret. |
|
|
75
|
+
| `invalid_client` | The client ID is unknown, the secret is wrong or expired, the authentication method does not match the registered client type, or Lunch Money disabled or deleted the client | Confirm the client ID, client type, and authentication method. For a confidential client, confirm that HTTP Basic uses an active secret. If previously working credentials are still configured correctly, stop requests and contact [developer support](mailto:developer-support@lunchmoney.app). Reauthorizing users will not restore a disabled client. |
|
|
76
|
+
| `invalid_grant` during code exchange | The code expired, was already used, has the wrong PKCE verifier, or does not match the redirect/client | Start a new authorization. Do not retry the same code. |
|
|
77
|
+
| `invalid_grant` during refresh | The refresh token expired, was revoked, was replayed, or the grant is no longer usable | Stop retrying, discard the token set, and ask the user to authorize again. If the error recurs for the same user shortly after a successful refresh, investigate concurrent refreshes before reauthorizing. |
|
|
78
|
+
| `invalid_scope` | `scope` is malformed or a refresh request asks for a scope that the authorization did not grant | Omit `scope` to retain the existing scopes, or send only scopes already granted to the refresh token. Refresh cannot expand an authorization's scopes. |
|
|
79
|
+
| `unsupported_grant_type` | `grant_type` names a grant that the token endpoint does not support | Use `authorization_code` when exchanging a code or `refresh_token` when refreshing. Send the exact value in a form-encoded request. |
|
|
80
|
+
| `invalid_target` | A `resource` value is missing where required, malformed, repeated, unknown, or was not granted during authorization | Use the exact resource from authorization-server metadata or the resource previously authorized. After correcting a refresh request, you may retry with the same refresh token because Lunch Money validates the resource before consuming it. |
|
|
81
|
+
|
|
82
|
+
A network failure or `5xx` response is not an OAuth protocol result. Use bounded retries for transient failures only when repeating the request is safe. A refresh may have succeeded even when its response was lost, so never retry a consumed refresh token after receiving `invalid_grant`.
|
|
83
|
+
|
|
84
|
+
An `invalid_grant` response does not distinguish a replayed refresh token from one that expired or is otherwise unknown. Recurrence shortly after a successful refresh is the useful signal: Lunch Money rotates refresh tokens on every use, and replaying a consumed token revokes the entire grant. Serialize refreshes for each grant and persist every replacement before discarding the previous token, as described in [Refresh access](/oauth/tokens#refresh-access).
|
|
85
|
+
|
|
86
|
+
If a successful authorization-code exchange does not include a refresh token, confirm that the client was registered with `offline_access`. If it was not, create a replacement client that includes `offline_access`; test and review it, then reauthorize users. If it was, contact [developer support](mailto:developer-support@lunchmoney.app).
|
|
87
|
+
|
|
88
|
+
## API failures
|
|
89
|
+
|
|
90
|
+
Treat an OAuth `401` as an unknown access-token failure unless a refresh request establishes what happened. When an eligible refresh token is available, attempt one refresh and handle the token endpoint's result, which uses the OAuth format shown above rather than the v2 API format:
|
|
91
|
+
|
|
92
|
+
1. On success, save the replacement tokens and retry the API request once. The access token had merely expired.
|
|
93
|
+
2. On `invalid_grant`, treat that connection as revoked: mark the connection for the selected user/account as inactive and discard its tokens. Prompt the user to authorize that connection again the next time they need it rather than repeatedly interrupting them. Leave the user's other budget-account connections unchanged.
|
|
94
|
+
3. On `invalid_client`, stop requests for every user of the client and contact [developer support](mailto:developer-support@lunchmoney.app). Lunch Money may have disabled the client; retrying or asking users to authorize again will not help.
|
|
95
|
+
|
|
96
|
+
If no eligible refresh token is available, discard the rejected access token and ask the user to authorize again.
|
|
97
|
+
|
|
98
|
+
An OAuth access token can fail because it expired, access was revoked, the client was disabled, the token was revoked or rotated, or the user lost access to the budget account. Every OAuth access-token failure returns the same generic `401` response:
|
|
99
|
+
|
|
100
|
+
```http
|
|
101
|
+
HTTP/1.1 401
|
|
102
|
+
WWW-Authenticate: Bearer error="invalid_token"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"message": "Unauthorized",
|
|
108
|
+
"errors": [
|
|
109
|
+
{
|
|
110
|
+
"errMsg": "Access token does not exist."
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The response does not identify the cause. Follow the refresh-first recovery path above rather than inferring whether the token expired or another event ended access.
|
|
117
|
+
|
|
118
|
+
### Insufficient scope
|
|
119
|
+
|
|
120
|
+
An `insufficient_scope` response cannot be fixed by retrying, refreshing, or asking the user to authorize the existing client again. It means the client was created without all the scopes required for the operation. This applies to development clients as well as approved clients. The only recovery is to [replace the client](#replace-a-client-to-change-scopes).
|
|
121
|
+
|
|
122
|
+
```http
|
|
123
|
+
HTTP/1.1 403
|
|
124
|
+
WWW-Authenticate: Bearer error="insufficient_scope", scope="transactions:read"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"message": "Forbidden",
|
|
130
|
+
"errors": [
|
|
131
|
+
{
|
|
132
|
+
"errMsg": "Required OAuth scope: transactions:read"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
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.
|
|
139
|
+
|
|
140
|
+
### Replace a client to change scopes
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
## Support email is not confirmed
|
|
145
|
+
|
|
146
|
+
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`.
|
|
147
|
+
|
|
148
|
+
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
|
+
|
|
150
|
+
## Local callback failures
|
|
151
|
+
|
|
152
|
+
For local development, use a supported loopback host: `localhost`, `127.0.0.1`, or `[::1]`. Matching depends on the client type:
|
|
153
|
+
|
|
154
|
+
- **Native or public client:** The scheme, host, path, and query string must match exactly. The port is ignored for a loopback redirect, so register one URI without a port, such as `http://localhost/callback`, and let the application use the ephemeral port it binds at runtime.
|
|
155
|
+
- **Confidential web client:** Every component, including the port, must match the registered redirect URI exactly.
|
|
156
|
+
|
|
157
|
+
Host matching is exact for both client types: a redirect registered with `127.0.0.1` does not match one sent with `localhost`. The browser must also be able to reach the application on the selected host and port. See [development and loopback guidance](/oauth/development#local-loopback-callbacks).
|
|
158
|
+
|
|
159
|
+
If the safe guidance here does not resolve the problem, contact [developer support](mailto:developer-support@lunchmoney.app) with the client ID, stage, timestamp, and nonsensitive error code. Do not send tokens, secrets, codes, or cookies.
|
package/docs/rate-limiting.md
CHANGED
|
@@ -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/docs/using-with-ai.md
CHANGED
|
@@ -75,7 +75,7 @@ const token = process.env.LUNCH_MONEY_ACCESS_TOKEN;
|
|
|
75
75
|
|
|
76
76
|
## Best practices
|
|
77
77
|
|
|
78
|
-
1. **Start with a test budget** — API changes are permanent. Use a test budget while you're learning so your real data stays safe. See the [Getting Started guide](/getting-started) for instructions.
|
|
78
|
+
1. **Start with a test budget account** — API changes are permanent. Use a test budget account while you're learning so your real data stays safe. See the [Getting Started guide](/getting-started) for instructions.
|
|
79
79
|
|
|
80
80
|
2. **Prefer read-only workflows** — A workflow that only reads data is lower risk than one that can create, update, or delete records.
|
|
81
81
|
|
|
@@ -95,7 +95,7 @@ If you want to experiment, here are a few low-risk starting points:
|
|
|
95
95
|
4. Ask for patterns in merchant names that might deserve new rules or categories.
|
|
96
96
|
5. Build a reusable "money questions" notebook with prompts you revisit each month.
|
|
97
97
|
|
|
98
|
-
Start small, and use a test budget while you're learning.
|
|
98
|
+
Start small, and use a test budget account while you're learning.
|
|
99
99
|
|
|
100
100
|
## Need help?
|
|
101
101
|
|
package/manifest.json
CHANGED
|
@@ -73,6 +73,102 @@
|
|
|
73
73
|
"type": "markdown",
|
|
74
74
|
"aliases": ["/v2/branding-your-app"]
|
|
75
75
|
},
|
|
76
|
+
{
|
|
77
|
+
"path": "/oauth",
|
|
78
|
+
"file": "docs/oauth/index.md",
|
|
79
|
+
"title": "OAuth Overview",
|
|
80
|
+
"section": "OAUTH",
|
|
81
|
+
"type": "markdown",
|
|
82
|
+
"aliases": ["/v2/oauth", "/v2/oauth/"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"path": "/oauth/concepts",
|
|
86
|
+
"file": "docs/oauth/concepts.md",
|
|
87
|
+
"title": "OAuth Concepts",
|
|
88
|
+
"section": "OAUTH",
|
|
89
|
+
"type": "markdown",
|
|
90
|
+
"aliases": ["/v2/oauth/concepts"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"path": "/oauth/register-client",
|
|
94
|
+
"file": "docs/oauth/register-client.md",
|
|
95
|
+
"title": "Register an OAuth Client",
|
|
96
|
+
"section": "OAUTH",
|
|
97
|
+
"type": "markdown",
|
|
98
|
+
"aliases": ["/v2/oauth/register-client"]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"path": "/oauth/authorization-code",
|
|
102
|
+
"file": "docs/oauth/authorization-code.md",
|
|
103
|
+
"title": "Implement Authorization",
|
|
104
|
+
"section": "OAUTH",
|
|
105
|
+
"type": "markdown",
|
|
106
|
+
"aliases": ["/v2/oauth/authorization-code"]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"path": "/oauth/sample-applications",
|
|
110
|
+
"file": "docs/oauth/sample-applications.md",
|
|
111
|
+
"title": "OAuth Sample Applications",
|
|
112
|
+
"section": "OAUTH",
|
|
113
|
+
"type": "markdown",
|
|
114
|
+
"aliases": ["/v2/oauth/sample-applications"]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"path": "/oauth/development",
|
|
118
|
+
"file": "docs/oauth/development.md",
|
|
119
|
+
"title": "Develop and Test",
|
|
120
|
+
"section": "OAUTH",
|
|
121
|
+
"type": "markdown",
|
|
122
|
+
"aliases": ["/v2/oauth/development"]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"path": "/oauth/tokens",
|
|
126
|
+
"file": "docs/oauth/tokens.md",
|
|
127
|
+
"title": "Token Lifecycle",
|
|
128
|
+
"section": "OAUTH",
|
|
129
|
+
"type": "markdown",
|
|
130
|
+
"aliases": ["/v2/oauth/tokens"]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"path": "/oauth/security",
|
|
134
|
+
"file": "docs/oauth/security.md",
|
|
135
|
+
"title": "OAuth Security",
|
|
136
|
+
"section": "OAUTH",
|
|
137
|
+
"type": "markdown",
|
|
138
|
+
"aliases": ["/v2/oauth/security"]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"path": "/oauth/troubleshooting",
|
|
142
|
+
"file": "docs/oauth/troubleshooting.md",
|
|
143
|
+
"title": "Troubleshoot OAuth",
|
|
144
|
+
"section": "OAUTH",
|
|
145
|
+
"type": "markdown",
|
|
146
|
+
"aliases": ["/v2/oauth/troubleshooting"]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"path": "/oauth/review-and-approval",
|
|
150
|
+
"file": "docs/oauth/review-and-approval.md",
|
|
151
|
+
"title": "OAuth Client Review and Approval",
|
|
152
|
+
"section": "OAUTH",
|
|
153
|
+
"type": "markdown",
|
|
154
|
+
"aliases": ["/v2/oauth/review-and-approval"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"path": "/oauth/native-apps",
|
|
158
|
+
"file": "docs/oauth/native-apps.md",
|
|
159
|
+
"title": "Native Apps",
|
|
160
|
+
"section": "OAUTH",
|
|
161
|
+
"type": "markdown",
|
|
162
|
+
"aliases": ["/v2/oauth/native-apps"]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"path": "/oauth/scopes",
|
|
166
|
+
"file": "docs/oauth/scopes.md",
|
|
167
|
+
"title": "OAuth Scopes",
|
|
168
|
+
"section": "OAUTH",
|
|
169
|
+
"type": "markdown",
|
|
170
|
+
"aliases": ["/v2/oauth/scopes"]
|
|
171
|
+
},
|
|
76
172
|
{
|
|
77
173
|
"path": "/v2/overview",
|
|
78
174
|
"file": "v2/docs/intro-to-v2.md",
|
|
@@ -211,6 +307,20 @@
|
|
|
211
307
|
{ "label": "Using the API with AI", "path": "/using-with-ai" },
|
|
212
308
|
{ "label": "Branding your App", "path": "/branding-your-app" }
|
|
213
309
|
]},
|
|
310
|
+
{ "section": "OAUTH", "collapsed": true, "items": [
|
|
311
|
+
{ "label": "OAuth Overview", "path": "/oauth" },
|
|
312
|
+
{ "label": "Concepts", "path": "/oauth/concepts" },
|
|
313
|
+
{ "label": "Register a Client", "path": "/oauth/register-client" },
|
|
314
|
+
{ "label": "Implement Authorization", "path": "/oauth/authorization-code" },
|
|
315
|
+
{ "label": "Sample Applications", "path": "/oauth/sample-applications" },
|
|
316
|
+
{ "label": "Develop and Test", "path": "/oauth/development" },
|
|
317
|
+
{ "label": "Token Lifecycle", "path": "/oauth/tokens" },
|
|
318
|
+
{ "label": "Security", "path": "/oauth/security" },
|
|
319
|
+
{ "label": "Troubleshooting", "path": "/oauth/troubleshooting" },
|
|
320
|
+
{ "label": "Review and Approval", "path": "/oauth/review-and-approval" },
|
|
321
|
+
{ "label": "Native Apps", "path": "/oauth/native-apps" },
|
|
322
|
+
{ "label": "Scopes", "path": "/oauth/scopes" }
|
|
323
|
+
]},
|
|
214
324
|
{ "section": "REFERENCE", "items": [
|
|
215
325
|
{ "label": "v2 API Overview", "path": "/v2/overview" },
|
|
216
326
|
{ "label": "v2 API Reference", "path": "/v2/docs", "external": true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunch-money/developer-docs",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.4-preview.1",
|
|
4
4
|
"description": "Developer documentation content for Lunch Money APIs",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./package.json",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"validate:manifest": "node scripts/validate-manifest.js",
|
|
22
|
-
"prepack": "node scripts/validate-manifest.js && node scripts/prepack.js",
|
|
21
|
+
"validate:manifest": "node ../../scripts/generate-oauth-scope-catalog.js --check && node scripts/validate-manifest.js",
|
|
22
|
+
"prepack": "node ../../scripts/generate-oauth-scope-catalog.js --check && node scripts/validate-manifest.js && node scripts/prepack.js",
|
|
23
23
|
"postpack": "node scripts/postpack.js"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
package/v2/docs/intro-to-v2.md
CHANGED
|
@@ -10,7 +10,7 @@ All API requests require a Bearer token in the `Authorization` header:
|
|
|
10
10
|
Authorization: Bearer YOUR_ACCESS_TOKEN
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Use a personal access token for API exploration or work with your own budget account. The [Getting started guide](/getting-started) walks through creating one on the <a href="https://my.lunchmoney.app/developers" target="_blank" rel="noopener noreferrer">Lunch Money Developers page</a> and making your first request. Applications built for other Lunch Money users should use an [OAuth access token issued after the user authorizes the application](/oauth).
|
|
14
14
|
|
|
15
15
|
## Base URL
|
|
16
16
|
|
|
@@ -5,6 +5,13 @@ The Lunch Money API spec uses a modified version of SEMVER for its versioning me
|
|
|
5
5
|
- The minor version represents the number of main endpoints the current version of the spec supports. For example, a version of the API that supports the /me, /categories, and /transactions endpoints would have a minor version of 3.
|
|
6
6
|
- The revision number represents the number of updates since the last endpoint was added. For example, each time changes are made to one of the existing three APIs as described above, the revision number will be bumped.
|
|
7
7
|
|
|
8
|
+
## v2.11.4 - TBD
|
|
9
|
+
- Add OAuth 2.0 bearer-token authentication to the v2 API, allowing applications to access Lunch Money on behalf of users who authorize them
|
|
10
|
+
- Document and enforce required OAuth scopes for every v2 operation and publish the scope and scope-group catalog mapped to V2 `operationId` values.
|
|
11
|
+
- Added a new `403` `insufficient_scope` response
|
|
12
|
+
- Document the OAuth 2.0 authorization-code flow used to obtain access tokens
|
|
13
|
+
- Add `include_files` query parameter to `GET /transactions/{id}`
|
|
14
|
+
|
|
8
15
|
## v2.11.2 - Nov 7, 2026
|
|
9
16
|
- Allow `balance_as_of` to be `null` in manual account responses
|
|
10
17
|
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
<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 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
|
+
<defs>
|
|
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
|
+
<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>
|
|
7
|
+
<marker id="lm-arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto"><path d="M0,0 L0,6 L9,3 z" fill="#0f766e"/></marker>
|
|
8
|
+
</defs>
|
|
9
|
+
<style>
|
|
10
|
+
.actor{stroke-width:2}.app{fill:#ffedd5;stroke:#b45309}.user{fill:#fef3c7;stroke:#9a6700}.lm{fill:#ccfbf1;stroke:#0f766e}
|
|
11
|
+
.actor-label{font:700 19px system-ui,sans-serif;fill:#172b2a}.actor-detail{font:14px system-ui,sans-serif;fill:#334e4b}
|
|
12
|
+
.lifeline{stroke:#94a3b8;stroke-width:2;stroke-dasharray:7 7}.flow{stroke-width:2.5;fill:none}.app-flow{stroke:#b45309;marker-end:url(#app-arrow)}.user-flow{stroke:#9a6700;marker-end:url(#user-arrow)}.lm-flow{stroke:#0f766e;marker-end:url(#lm-arrow)}
|
|
13
|
+
.step-bg{fill:#fff;stroke:#cbd5e1;stroke-width:1}.step{font:600 14px system-ui,sans-serif;fill:#172b2a}
|
|
14
|
+
</style>
|
|
15
|
+
<rect class="actor app" x="40" y="25" width="260" height="72" rx="12"/>
|
|
16
|
+
<text class="actor-label" x="170" y="56" text-anchor="middle">Your application</text><text class="actor-detail" x="170" y="79" text-anchor="middle">browser route and server</text>
|
|
17
|
+
<rect class="actor user" x="430" y="25" width="260" height="72" rx="12"/>
|
|
18
|
+
<text class="actor-label" x="560" y="56" text-anchor="middle">Lunch Money user</text><text class="actor-detail" x="560" y="79" text-anchor="middle">browser, sign-in, and approval</text>
|
|
19
|
+
<rect class="actor lm" x="820" y="25" width="260" height="72" rx="12"/>
|
|
20
|
+
<text class="actor-label" x="950" y="56" text-anchor="middle">Lunch Money</text><text class="actor-detail" x="950" y="79" text-anchor="middle">authorization server and API</text>
|
|
21
|
+
<path class="lifeline" d="M170 105 V930"/><path class="lifeline" d="M560 105 V930"/><path class="lifeline" d="M950 105 V930"/>
|
|
22
|
+
<path class="flow app-flow" d="M170 140 H550"/><rect class="step-bg" x="278" y="118" width="180" height="28" rx="6"/><text class="step" x="368" y="137" text-anchor="middle">1. Redirect the user</text>
|
|
23
|
+
<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
|
+
<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
|
+
<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 scopes</text>
|
|
27
|
+
<path class="flow user-flow" d="M560 440 H940"/><rect class="step-bg" x="585" y="418" width="330" height="28" rx="6"/><text class="step" x="750" y="437" text-anchor="middle">6. Select budget account and approve</text>
|
|
28
|
+
<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
|
+
<path class="flow app-flow" d="M170 580 H940"/><rect class="step-bg" x="335" y="558" width="460" height="28" rx="6"/><text class="step" x="565" y="577" text-anchor="middle">8. Exchange code with PKCE (+ client secret for confidential client)</text>
|
|
30
|
+
<path class="flow lm-flow" d="M950 640 H180"/><rect class="step-bg" x="385" y="618" width="360" height="28" rx="6"/><text class="step" x="565" y="637" text-anchor="middle">9. Return access token + optional refresh token</text>
|
|
31
|
+
<path class="flow app-flow" d="M170 700 H940"/><rect class="step-bg" x="430" y="678" width="270" height="28" rx="6"/><text class="step" x="565" y="697" text-anchor="middle">10. Call GET /v2/me</text>
|
|
32
|
+
<path class="flow lm-flow" d="M950 760 H180"/><rect class="step-bg" x="350" y="738" width="430" height="28" rx="6"/><text class="step" x="565" y="757" text-anchor="middle">11. Return user id, account_id, and budget_name</text>
|
|
33
|
+
<path class="flow app-flow" d="M170 800 H330 V840 H180"/><rect class="step-bg" x="285" y="798" width="330" height="28" rx="6"/><text class="step" x="450" y="817" text-anchor="middle">12. Save connection and make it active</text>
|
|
34
|
+
<path class="flow app-flow" d="M170 890 H940"/><rect class="step-bg" x="385" y="868" width="360" height="28" rx="6"/><text class="step" x="565" y="887" text-anchor="middle">13. Call the V2 API for the active account</text>
|
|
35
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="990" height="300" viewBox="0 0 990 300" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">OAuth token lifecycle and recovery</title>
|
|
3
|
+
<desc id="desc">Authorization yields a short-lived access token and, when offline access applies, a finite refresh token. Refresh replaces tokens. Expiration, revocation, or a terminal refresh failure leads to fresh authorization.</desc>
|
|
4
|
+
<defs><marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto"><path d="M0,0 L0,6 L9,3 z" fill="#0f766e"/></marker></defs>
|
|
5
|
+
<style>.box{fill:#f0f9f8;stroke:#2a6b64;stroke-width:2}.warn{fill:#fff4dc;stroke:#9a6a16;stroke-width:2}.label{font:600 17px system-ui,sans-serif;fill:#173f3b}.small{font:14px system-ui,sans-serif;fill:#294744}.line{stroke:#0f766e;stroke-width:2.5;fill:none;marker-end:url(#arrow)}.annotation-bg{fill:#fff;stroke:#cbd5e1;stroke-width:1}.annotation{font:600 14px system-ui,sans-serif;fill:#172b2a}</style>
|
|
6
|
+
<rect class="box" x="35" y="65" width="180" height="70" rx="12"/><text class="label" x="125" y="94" text-anchor="middle">Authorization</text><text class="small" x="125" y="117" text-anchor="middle">user present</text>
|
|
7
|
+
<rect class="box" x="300" y="35" width="200" height="70" rx="12"/><text class="label" x="400" y="64" text-anchor="middle">Access token</text><text class="small" x="400" y="87" text-anchor="middle">short-lived</text>
|
|
8
|
+
<rect class="box" x="300" y="165" width="200" height="70" rx="12"/><text class="label" x="400" y="194" text-anchor="middle">Refresh token</text><text class="small" x="400" y="217" text-anchor="middle">finite; offline_access</text>
|
|
9
|
+
<rect class="box" x="590" y="100" width="170" height="70" rx="12"/><text class="label" x="675" y="129" text-anchor="middle">Refresh</text><text class="small" x="675" y="152" text-anchor="middle">store replacements</text>
|
|
10
|
+
<rect class="warn" x="790" y="100" width="140" height="70" rx="12"/><text class="label" x="860" y="129" text-anchor="middle">Terminal failure</text><text class="small" x="860" y="152" text-anchor="middle">reauthorize</text>
|
|
11
|
+
<path class="line" d="M215 88 L290 72"/><path class="line" d="M215 112 L290 185"/><path class="line" d="M500 200 L580 150"/><path class="line" d="M760 135 H780"/><path class="line" d="M675 95 C675 25 500 15 470 35"/>
|
|
12
|
+
<rect class="annotation-bg" x="530" y="10" width="170" height="28" rx="6"/><text class="annotation" x="615" y="29" text-anchor="middle">replacement token set</text>
|
|
13
|
+
<rect class="annotation-bg" x="740" y="198" width="240" height="36" rx="6"/><text class="annotation" x="860" y="221" text-anchor="middle">expired, revoked, or rejected</text>
|
|
14
|
+
</svg>
|
package/v2/spec/AGENTS.md
CHANGED
|
@@ -41,6 +41,12 @@ Follow these rules whenever editing `v2/spec/lunch-money-api-v2.yaml`:
|
|
|
41
41
|
- Add links when they materially help consumers discover a related endpoint or relevant guide.
|
|
42
42
|
- Do not add links that merely restate obvious navigation.
|
|
43
43
|
|
|
44
|
+
### YAML formatting
|
|
45
|
+
|
|
46
|
+
- Write each description paragraph as a single line. Never hard-wrap prose to a column: inside a folded scalar a line break becomes a space, so a wrap landing beside punctuation or markup silently corrupts the rendered text.
|
|
47
|
+
- Use a folded block (`>-`) only when a description has more than one paragraph, separated by a blank line. A single-paragraph description stays on the key line.
|
|
48
|
+
- Never use a literal block (`|`, `|-`) for prose — newlines become significant, so reflowing later changes the output.
|
|
49
|
+
|
|
44
50
|
### Examples
|
|
45
51
|
|
|
46
52
|
Examples are selective documentation aids, not a required artifact for every spec change.
|