SPA OAuth PKCE: 400 invalid_grant on token exchange despite correct code_verifier

building a custom SPA dashboard to pull WEM adherence metrics via the Genesys Cloud API. trying to implement the Authorization Code flow with PKCE. the authorization request hits fine and returns the code. swapping that code for a token is where it breaks.

here’s the POST to /oauth/token:

curl -X POST "https://myorg.mygenesyscloud.com/oauth/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "client_id=abc123"
 -d "code=AUTH_CODE_FROM_REDIRECT"
 -d "code_verifier=GENERATED_PKCE_VERIFIER"
 -d "grant_type=authorization_code"
 -d "redirect_uri=http://localhost:3000/callback"

getting a 400 back:

{
 "status": 400,
 "code": "invalid_grant",
 "message": "The authorization code has expired or been used"
}

the code is only used once. timing is tight. generated the code_verifier in JS using crypto.randomUUID(). encoded it for the challenge. everything looks right in the logs. am i missing a parameter in the token exchange? or is the SPA client setup in Genesys Cloud wrong? checked the redirect URIs. they match exactly.

anyone else hit this wall?