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:
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.
/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.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.
When Lunch Money can safely use the registered callback, it redirects the user's browser with an error, an optional error_description, and the original state as query parameters:
https://example.com/oauth/callback?error=access_denied&state=RETURNED_STATE
Match on error when it is returned, verify state, and treat error_description as human-readable text that may change.
| OAuth error | Likely cause | What to do |
|---|---|---|
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. |
invalid_scope |
The client does not have a usable registered scope set | Open the client in the Developer Portal and review its assigned scopes. Registered clients always use that complete set. |
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. |
If the redirect URI does not exactly match one registered for the client, Lunch Money cannot safely return the browser to it. Instead, Lunch Money shows an error page explaining that it could not return the user to the application. The page displays REDIRECT_URI_MISMATCH, a request or reference ID, a timestamp, and the application name and public client ID when they are available.
Ask the user to report those displayed details—not the complete attempted URL, credentials, authorization codes, tokens, cookies, or PKCE values. Open the client in the Developer Portal, find its registered redirect URIs, and compare the complete URL with the value your application sent. The two values must be identical.
After Lunch Money returns the browser to the registered callback, your application must verify that the returned state matches the authorization attempt. A missing, changed, or reused value is a state mismatch 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.
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.
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:
{
"error": "invalid_grant",
"error_description": "grant request is invalid"
}
Match on error. Treat error_description as human-readable text that may change; it does not identify the specific cause.
error value |
Likely cause | What to do |
|---|---|---|
invalid_client |
Client authentication is missing or wrong, or Lunch Money disabled the client | Confirm that a confidential client uses HTTP Basic with an active secret and that a public native client sends its client ID without a secret. If previously working credentials are still configured correctly, stop requests and contact developer support. Reauthorizing users will not restore a disabled client. |
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. |
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. |
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.
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.
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:
invalid_grant, treat the authorization as revoked: mark that user's Lunch Money connection as inactive and discard its tokens. Prompt the user to authorize again the next time they sign in rather than repeatedly interrupting them.invalid_client, stop requests for every user of the client and contact developer support. Lunch Money may have disabled the client; retrying or asking users to authorize again will not help.If no eligible refresh token is available, discard the rejected access token and ask the user to authorize again.
An OAuth access token can fail because it expired, access was revoked, the client was disabled, the user reauthorized the application, a token was revoked or rotated, or the user lost access to the budgeting account. Every OAuth access-token failure returns the same generic 401 response:
HTTP/1.1 401
WWW-Authenticate: Bearer error="invalid_token"
{
"message": "Unauthorized",
"errors": [
{
"errMsg": "Access token does not exist."
}
]
}
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.
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.
HTTP/1.1 403
WWW-Authenticate: Bearer error="insufficient_scope", scope="transactions:read"
{
"message": "Forbidden",
"errors": [
{
"errMsg": "Required OAuth scope: transactions:read"
}
]
}
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 or the scope catalog to confirm its requirements.
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.
For local development, use a supported loopback host: localhost, 127.0.0.1, or [::1]. Matching depends on the client type:
http://localhost/callback, and let the application use the ephemeral port it binds at runtime.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.
If the safe guidance here does not resolve the problem, contact developer support with the client ID, stage, timestamp, and nonsensitive error code. Do not send tokens, secrets, codes, or cookies.