## TL;DR

The authorization code is single-use and short-lived, so invalid_grant usually means the code was already redeemed, expired, or the token request does not match the original (redirect URI, client id). Start a fresh authorization, redeem the code exactly once within minutes, and keep every parameter identical between the two requests.

## Error

```text
{
  "error": "invalid_grant",
  "error_description": "expired authorization code"
}
```

## Steps

1. Start a new authorization request and get a fresh code. Expected: a new single-use code.
2. Redeem it within a few minutes, exactly once. Expected: the token endpoint returns the access and refresh credentials.
3. Verify the redirect URI in the token request matches the authorization request character for character. Expected: mismatches are the most common avoidable cause.
4. Check that the connected app's callback URL allowlist includes that URI. Expected: the app accepts the redirect.
5. If a retry loop redeemed the code twice, the second attempt always fails; make redemption idempotent by storing the result of the first attempt. Expected: no double-redemption.

## When to use

- The OAuth token exchange returns invalid_grant.
- An agent's auth flow worked once then fails on retry.
- After changing the connected app's callback URLs.

## When not to use

- INVALID_LOGIN on password auth (different flow).
- Expired access tokens during a run (use the refresh flow, not a new code).

## Tool compatibility

- Salesforce OAuth 2.0 web server flow; connected apps.
- Any OAuth client library.

## Variant phrasings

### expired authorization code

The code lived past its short lifetime; get a new one.

### invalid_grant on token refresh

The refresh credential itself expired or was revoked; re-authorize.

## Why it happens

Authorization codes are designed to be brief and single-use to limit theft. Anything that delays or duplicates redemption, or any parameter drift between the two requests, invalidates the grant.

## Edge cases

- Load-balanced agents where two workers redeem the same code: the loser gets invalid_grant; coordinate or use separate flows.
- Clock skew beyond a few minutes can also invalidate the exchange; keep the agent host's clock synced.
- Revoking the refresh credential invalidates outstanding codes too.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_sBctYtZECl3S0ZL47hJjIA
