Skip to content
Developers

Errors

Every error code, and what actually causes it in practice.

Every endpoint reports errors the same way — flat, not nested, per RFC 6749 §5.2, because that is the shape every client library parses.

json
{
  "error": "invalid_grant",
  "error_description": "code_verifier does not match the code_challenge."
}

⚠️ error_description is written for a human debugging an integration. The wording may change — branch on error and the HTTP status, never on the description.

⚠️ This shape differs from the Visit Korat Partner API at /v1/*, which nests under { error: { code, message } }. Two APIs, two contracts. If you integrate both, do not share an error handler.

OAuth error codes

CodeWhat it means, and what usually causes it
invalid_requestMalformed or incomplete. Usually: sending JSON instead of form-encoded, a missing code_verifier, or a missing state.
invalid_clientWe cannot tell who you are. Wrong secret, an application still awaiting review, a suspended one, or a confidential client calling from a browser.
invalid_grantThe code or refresh token is no good. Expired (a code lives 60 seconds), already used, a redirect_uri that differs from the authorize step, a code_verifier that does not match, or consent that has been withdrawn.
unsupported_grant_typeOnly authorization_code and refresh_token are accepted.
unsupported_response_typeOnly response_type=code. No implicit or hybrid flow.
invalid_scopeA scope your application is not registered for, a missing openid, or offline_access before Visit Korat has enabled it.
access_deniedThe person declined. Not a bug — say something gracious and stop.
invalid_tokenThe access token is expired, revoked, or its consent was withdrawn. Refresh if you can; otherwise send the person through sign-in again.
temporarily_unavailableRate limited. See Retry-After.
server_errorOur fault. Retry, and tell us if it persists.

Note that invalid_grantcovers “unknown”, “expired” and “already used” together. That is deliberate: telling them apart hands somebody holding a stolen code a way to test whether it still works.

HTTP status codes

StatusWhen
200Success — including /oauth/revoke, which always answers 200 whether or not the token existed.
302Back to your redirect URI, on success and on a reportable error alike.
400The request or the grant is no good.
401We cannot tell who you are (invalid_client) or the token is no good (invalid_token). Bearer endpoints also send WWW-Authenticate.
429Rate limited. Token: 60/min per application per address. UserInfo: 120/min per token. Authorize: 30/min per address. See Retry-After.
500Our fault.

Troubleshooting

The person sees an error page and is never redirected back

Your redirect_uri does not match what is registered, byte for byte. Check the trailing slash, http versus https, the port, and the case.

Not redirecting is deliberate: while we cannot verify the destination, sending the browser there with ?error= would hand anybody a free open redirect.

invalid_grant: code_verifier does not match the code_challenge

  • The challenge must be base64url, not base64 — no +, / or =.
  • It must be the SHA-256 of the verifier as bytes, not the hash of its hex representation.
  • It must be the verifier from this same request. A mismatched session will not match.

Check against the RFC 7636 vector: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM.

invalid_grant on a code you only just received

A code lives 60 seconds and can be used once. If your callback runs twice — a browser prefetch, somebody pressing reload — the second run always gets this.

invalid_request: terms_version and privacy_version are required

These two are specific to Visit Korat, so no standard client library sends them. Pass them as extra authorization parameters — see using a client library.

The browser blocks your call to /oauth/token (CORS)

  • Your application must be public. Confidential applications are refused when an Origin is present, by design.
  • The origin must be in your allowed origins, matched exactly — scheme and port included.
  • https://app.example.com and https://app.example.com/ are not the same string. Register it without the trailing slash.

invalid_client although the secret is right

The application may still be awaiting review, or suspended — check its status under your applications. Or you are using development credentials against production: the two are entirely separate registries.

Sign-in works, but /oauth/userinfo starts returning 401 soon after

The person disconnected your application at /account/connections. We cut the tokens immediately rather than waiting for them to expire. Send them through sign-in again, and delete the data you stored.

The ID token fails signature verification

  • Use the JWKS for the right environment — development and production sign with different keys.
  • On an unknown kid, refetch the JWKS and retry. That is a key rotation, and most libraries handle it for you.
  • iss must match byte for byte, including https:// and with no trailing slash.

Still stuck

Every response carries an x-request-id header. Send that, your client_id, and roughly when it happened, and we can find it much faster.

⚠️ Never send us a client secret, access token, refresh token or authorization code — not by email, not in a chat. We will never ask, and we do not need one to diagnose anything.