Cannot obtain a token (authentication error)

Troubleshooting Token Authentication OAuth

How to narrow down the cause when the token endpoint POST https://receiptroller.io/api/v1/auth/token returns 400 or 401 and you cannot obtain or refresh an access token. Match the error and error_description you received against the table below. If the error happens earlier, on the authorize URL, see the second table.

Token endpoint errors

HTTP / error error_description Cause and fix
400 invalid_requestnone, code is required, refresh_token is requiredThe body could not be read as JSON, or a required value is missing. Check Content-Type and the body format.
400 unsupported_grant_typenonegrant_type is empty or is not authorization_code / refresh_token. client_credentials is not supported.
401 invalid_clientclient authentication failedThe client ID does not exist (including when the app was deleted), or the client secret is wrong. After regenerating the secret, use the new value.
401 invalid_grantauthorization code is invalid, expired, or already usedThe code's 10 minutes have passed, it was already exchanged, or the value is wrong. Start authorization again.
401 invalid_grantredirect_uri does not match the authorization requestThe redirect_uri in the exchange differs from the one in the authorize request.
401 invalid_grantauthorization code was issued to a different clientYou are exchanging with another app's client ID. Check you have not mixed up your development and production apps.
401 invalid_grantpublic clients require PKCE; the code was issued without a code_challengeA public client authorized without code_challenge. Authorize again with PKCE.
401 invalid_grantcode_verifier is required / PKCE verification failedcode_verifier is missing or does not match code_challenge. With S256, Base64URL-encode the SHA-256 (+ to -, / to _, no trailing =).
401 invalid_grantthe business account's plan does not include the developer API; it needs Starter or aboveThe connected business account is on a plan below Starter. The code is not marked as used, so if the plan changes within the 10 minutes, the same code can still be exchanged.
401 invalid_grantrefresh token is invalid, expired, or revokedThe refresh token is past its 30 days, was already used for a refresh, or was revoked. Use the most recent refresh token you received; if that fails, authorize again.
401 invalid_grantrefresh token was issued to a different clientThe refresh token belongs to another app.

invalid_grant is returned with 401, not 400. Decide on error, not on the status code alone.

When it stops at the authorize URL

These errors on /oauth/authorize do not redirect; the browser shows a 400 JSON body.

error What to do
invalid_redirect_uriRegister a redirect URI on the app and make redirect_uri match it exactly (Setting the redirect URL).
invalid_clientCheck client_id. It is also returned when the app is disabled.
unsupported_response_typeUse response_type=code.
invalid_scopeSend scope, and do not mix user.* and other scopes in one request.
invalid_requestSet code_challenge_method to S256 (recommended) or plain, in exactly that case.
app_not_approveduser.* scopes need the app's review to be approved (Applying for review of User-scopes).
no_organizationThe signed-in user owns no business account. Authorize with the owner's account.

If the callback comes back with error=access_denied, the user denied, or unticked every scope (error_description=no_scopes_approved).

Checklist

  • Have you mixed up the client ID or secret between your development and production apps?
  • Was the secret regenerated recently?
  • Do you exchange the code within 10 minutes, and only once?
  • Is redirect_uri the same string as in the authorize request (compare the original string, not the encoded one)?
  • Are client_id and client_secret in the body? HTTP Basic authentication in the Authorization header is not read.
  • Are the parameters in the body rather than the query string?
  • For a public client, did you authorize with code_challenge and send code_verifier?
  • For a refresh, are you using the most recent refresh token?

Body format

A form-encoded body (application/x-www-form-urlencoded, snake_case keys) is recommended. JSON is accepted as well, with snake_case keys such as grant_type or camelCase keys such as grantType. A body that is not valid JSON returns 400 invalid_request.

Related guides

Published: 2026-04-27 Updated: 2026-10-26