OAuth2 error reference

Every error returned by the Xyte OAuth2 endpoints, and by the Organization Core API for bearer tokens, with the recommended client action.

This page lists every error the Xyte OAuth2 endpoints return, grouped by endpoint. For the flow itself, see Connect your app with Xyte OAuth2.

The token and revocation endpoints use the RFC 6749 section 5.2 error object:

{
  "error": "invalid_grant",
  "error_description": "..."
}

error is stable and meant for your code; error_description is human-readable and may change. Branch on error and the HTTP status only.

Authorization endpoint

GET /oauth/authorize reports errors in two ways, depending on how far validation got.

Error page, no redirect

Xyte validates client_id, redirect_uri and the length of state before anything else. When one of these checks fails, or the consent screen cannot be hosted, the user sees an error page hosted by Xyte and the browser is not redirected. Your application receives nothing.

StatuserrorConditionAction
400invalid_clientUnknown client_id: the value is not a registered application.Check the id your deployment uses.
400invalid_clientDisabled application: Xyte has disabled your application. Its grants are revoked.Contact Xyte. Once re-enabled, every End Customer has to connect again.
400invalid_requestMissing redirect_uri.Always send redirect_uri.
400invalid_requestUnregistered redirect_uri: the value does not match a registered URI byte for byte (scheme, host, port, path, trailing slash, query).Fix the URI in your code, or ask Xyte to register the new one.
400invalid_requeststate longer than 512 characters.Shorten the value. A random 32-byte value encoded as hex or base64url is plenty.
500server_errorThe Partner that owns your application has no Customer Portal domain to host the consent screen.Contact Xyte.

Errors returned on the redirect URI

Once client_id and redirect_uri are verified, errors come back to your redirect URI as query parameters, together with your state. Validation errors also carry an error_description parameter with a human-readable explanation; only access_denied comes without one:

HTTP/1.1 302 Found
Location: https://app.example.com/oauth/callback?error=access_denied&state=1f7c2d8e9a4b
errorCauseAction
access_deniedThe user declined on the consent screen.Show a neutral message and offer to try again. Do not retry automatically.
invalid_scopescope contains a value outside openid profile email.Fix the scope list.
unsupported_response_typeresponse_type is present but is not code.Fix the request.
invalid_requestresponse_type, state or code_challenge is missing, code_challenge is malformed, code_challenge_method is not S256, or nonce is longer than 512 characters. Unlike an over-long state, an over-long nonce is reported on the redirect URI.Fix the request. These are integration bugs, not runtime conditions.

Always verify state before reading error or code. A callback with an unknown state is not yours; discard it.

📘

Users with no End Customer they can pick

The consent screen lists every End Customer the user can reach, but a row is selectable only when the user administers that End Customer, or when your application is already approved there and the connection's sign-in policy admits the user. An approval can be narrowed to one group or to administrators only, so a user who signed in before may find nothing selectable after an administrator changed the policy. Either way the screen cannot be submitted and no code is issued; there is no redirect and no error on your side.

Token endpoint

POST /oauth/token. Error responses carry Cache-Control: no-store and Pragma: no-cache.

StatuserrorCauseAction
400invalid_requestA parameter is missing; the body is not application/x-www-form-urlencoded (for example JSON); client credentials were sent both in the Authorization header and in the body with different values.Fix the request. Do not retry it unchanged.
401invalid_clientUnknown client_id, wrong client_secret, or the application is disabled. When HTTP Basic was used, the response also carries WWW-Authenticate: Basic realm="oauth".Check the deployed credentials. If the secret may have leaked, ask Xyte to rotate it. If the application was disabled, contact Xyte; disabling revoked its grants, so every End Customer has to connect again once it is re-enabled.
400invalid_grantWith authorization_code: the code is unknown, expired (60 seconds), already used, code_verifier does not match code_challenge, or redirect_uri differs from the authorization request. With refresh_token: the token is expired (90 days), already rotated, or its grant was revoked (by the End Customer, by you, by a replay, or because Xyte disabled the application; those grants stay revoked after it is re-enabled).For a code: start a new authorization request. For a refresh token: delete the stored tokens and send the user through authorization again.
400unsupported_grant_typegrant_type is not authorization_code or refresh_token.Fix the request.
500server_errorXyte could not sign the id_token (signing key not configured).Retry with backoff. Contact Xyte if it persists.
429More than 60 requests per minute from one IP address, or 300 per minute for one client_id. The body is the JSON {"error":"Too many requests"}; there is no Retry-After header.Back off with your own schedule and retry later. Refresh only when the access token is about to expire.

Refresh token replay

Reusing a refresh token that has already been rotated returns 400 invalid_grant in both cases below, but the consequences differ:

WhenWhat Xyte doesWhat to do
Within 60 seconds of the rotationTreats it as a retry of a request whose response was lost. Only invalid_grant is returned; the grant stays valid.Use the refresh token you received from the first, successful call.
Later than 60 secondsTreats it as a stolen token. Revokes the whole grant — every access token, refresh token and pending code your application holds for that End Customer, including those obtained by its other users — and records an audit event.Delete every stored token for the grant and ask the user to connect again. Find out how the old token was reused.

Revocation endpoint

POST /oauth/revoke.

StatuserrorCauseAction
200Success: every token issued to the same authorization is revoked. The End Customer's approval of your application is untouched, so re-authorizing issues fresh tokens without another administrator approval; only a Customer Portal revoke (Settings → Connected apps) withdraws the approval. Also returned for unknown, expired and already-revoked tokens and for tokens issued to another client (RFC 7009). The body is empty.Treat the token as gone.
400invalid_requesttoken is missing; the body is not application/x-www-form-urlencoded; or client credentials were sent both in the Authorization header and in the body with different values.Fix the request.
401invalid_clientBad client credentials.Same as for the token endpoint.
429More than 60 requests per minute from one IP address. The body is the JSON {"error":"Too many requests"}; there is no Retry-After header.Back off with your own schedule and retry.

Userinfo endpoint

GET /oauth/userinfo (also POST). Errors follow RFC 6750: the status and the WWW-Authenticate header carry the error, and the body repeats it.

StatusBodyWWW-AuthenticateCauseAction
401{"error":"invalid_token"}Bearer error="invalid_token"Missing, expired, revoked or malformed access token.Refresh the token and retry once. If the refresh fails, re-authorize.
403{"error":"insufficient_scope"}Bearer error="insufficient_scope"The token's authorization did not request the openid scope. Who signed in makes no difference: userinfo answers for an administrator's token as readily as for anyone else's.Do not retry with this token. Include openid in the next authorization request.
429{"error":"Too many requests"}More than 120 requests per minute from one IP address. There is no Retry-After header.Back off with your own schedule. The id_token already carries the same claims, so most applications never need to poll userinfo.

Discovery and JWKS endpoints

GET /.well-known/openid-configuration and GET /oauth/.well-known/jwks.json. Neither takes authentication.

StatusBodyCauseAction
429{"error":"Too many requests"}More than 120 requests per minute from one IP address, for either endpoint. There is no Retry-After header.Cache the discovery document for 1 hour and the JWKS for 5 minutes instead of fetching them on every sign-in.

Organization Core API with a bearer token

Calls to https://hub.xyte.io/core/v1/organization/... with Authorization: Bearer xoat_... return the same responses as with an API key, plus this authentication error:

StatusBodyWWW-AuthenticateCauseAction
401{"error":"Not authorized"}Bearer error="invalid_token"The access token is expired, revoked or not recognized.Refresh once and retry. If the refresh returns invalid_grant, delete the stored tokens and ask the user to connect again.

Authorization is otherwise identical to an API key: an access token carries the End Customer's full administrative reach, so nothing is refused because of the role of the user who signed in. /core/v1/organization/users and /core/v1/organization/groups answer 200 for every access token.

Quick decision table

You gotDo
error=access_denied on the callbackTell the user and offer to retry.
state mismatchDiscard the callback.
invalid_grant on the code exchangeStart a new authorization request.
invalid_grant on refreshDelete the stored tokens; the user connects again.
invalid_clientFix the credentials; rotate them if leaked.
401 from the API or userinfoRefresh once, then re-authorize.
403 insufficient_scope from userinfoThe authorization did not request openid; add the scope and authorize again.
429 anywhereBack off with jitter; do not refresh ahead of need.