OAUTH PREVIEW — Create and manage OAuth clients in the Developer PortalStable portal →

Troubleshoot OAuth

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.

For a runnable comparison, try OAuth with the confidential Node.js sample. Reproduce the same stage in its end-to-end flow, then compare the result with the sample repository's troubleshooting guide without copying credentials or full callback URLs.

Two error formats
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.

Authorization endpoint failures

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.

Users cannot authorize an unapproved client

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.

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. 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.

Redirect URI problems

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.

The page displays one of two codes, a UTC timestamp, and the application name and public client ID once the client has been resolved.

Code Likely cause What to do
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 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.
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.

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.

Callback validation failures

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.

Token endpoint failures

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.

API failures

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:

  1. On success, save the replacement tokens and retry the API request once. The access token had merely expired.
  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.
  3. On 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 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:

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.

Insufficient scope

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.

Replace a client to change scopes

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.

Client-management failures

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.

Status and code Meaning What to do
422 quota_exceeded, field clients You already own 25 non-deleted OAuth clients. Delete a client you no longer use before registering another.
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.
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.

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.

Local callback failures

For local development, use a supported loopback host: localhost, 127.0.0.1, or [::1]. Matching depends on the client type:

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.