Skip to content
Latchkey

OAuth2 "invalid_grant" from the token endpoint in CI

The OAuth2 token endpoint rejected the credential you exchanged: an authorization code that expired or was already used, an invalid refresh token, or wrong resource-owner credentials. The provider returns HTTP 400 with "error":"invalid_grant".

What this error means

A step that exchanges a grant for an access token gets HTTP 400 with a JSON body containing "error":"invalid_grant". It often appears when a CI-stored refresh token has been revoked or an authorization code is replayed.

token endpoint
HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error":"invalid_grant","error_description":"The provided authorization grant is invalid, expired, revoked, or does not match the redirection URI."}

Common causes

An expired or already-used authorization code

Authorization codes are single-use and short-lived. A retried or replayed code exchange returns invalid_grant.

A revoked or rotated refresh token

The refresh token stored as a CI secret was revoked, expired, or rotated by the provider, so the exchange fails.

How to fix it

Use a fresh grant per run

  1. Do not cache an authorization code across retries; each is single-use.
  2. For machine-to-machine flows, use client_credentials instead of a stored refresh token.
  3. If a refresh token is required, rotate the CI secret when the provider rotates it.
Terminal
curl -s -X POST "$TOKEN_URL" \
  -d grant_type=client_credentials \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET"

Read error_description for the exact grant that failed

The error_description field names whether the code, refresh token, or redirect URI mismatched, so you fix the right input.

How to prevent it

  • Prefer client_credentials for CI over long-lived refresh tokens.
  • Never retry a single-use authorization code exchange.
  • Rotate stored refresh tokens promptly when the provider rotates them.

Frequently asked questions

What causes ""error":"invalid_grant""?
Authorization codes are single-use and short-lived. A retried or replayed code exchange returns invalid_grant.
How do I fix "error":"invalid_grant"?
Use a fresh grant per run

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card