Skip to main content

“Token invalid” or Verification Fails

If verifyGrantToken() or tokens.verify() returns invalid results, work through this checklist:
1

Check token expiry

Decode the JWT and inspect the exp claim. If exp is in the past, the token has expired.
2

Check revocation status

Call POST /v1/tokens/verify with the token. If valid: false, the token or its grant has been revoked.
3

Verify the JWKS URI

Make sure you’re using the correct JWKS endpoint for your environment:
  • Production: https://api.grantex.dev/.well-known/jwks.json
  • Self-hosted: https://your-domain/.well-known/jwks.json
4

Check clock skew

JWT verification compares exp and iat against your server’s clock. If your server clock is more than a few seconds off, tokens may fail verification. Use NTP to keep clocks synchronized.
5

Check audience mismatch

If you set audience in VerifyGrantTokenOptions, the JWT’s aud claim must match. Remove the audience check or ensure the authorization request included the same audience.
If the authorization request stays in pending status:
  1. Check the consent URL — ensure the user was redirected to consentUrl from the authorization response
  2. Check the auth request status — query GET /v1/consent/:id to see the current state
  3. Check expiry — auth requests expire after the expiresIn window (default 24h). If expired, create a new authorization request
  4. Check the redirect URI — if provided, the code is delivered via redirect. Ensure your callback handler is working
  5. Sandbox mode — in sandbox mode, consent is auto-approved and a code is returned directly in the response

Rate Limited (429)

When you receive a 429 Too Many Requests response:
What to do:
  1. Read the Retry-After header for the minimum wait time
  2. Implement exponential backoff with jitter (see Rate Limits guide)
  3. Reduce request frequency:
    • Use local JWKS-based verification instead of POST /v1/tokens/verify when a revocation lookup is not required. The standalone helpers still fetch JWKS per call, but that endpoint is exempt from rate limits.
    • Use webhooks instead of polling for grant status changes
    • Cache tokens — don’t re-exchange or re-verify tokens unnecessarily
Rate limits by endpoint: A route-specific Fastify limit replaces the 5,000 req/min Fastify default on that route. The Redis-backed standard developer plan remains an additional policy wherever standard API-key authentication applies. Standard developer API-key plan exhaustion returns 429 with RATE_LIMIT_EXCEEDED. If the Redis-backed plan counter is unavailable, the request fails closed with 503 and RATE_LIMIT_UNAVAILABLE. Commerce, the SCIM Bearer data-plane routes under /scim/v2/*, admin, and other custom-auth routes do not consume this plan bucket; the standard API-key-authenticated /v1/scim/tokens management routes do.

Plan Limit Exceeded (402)

A 402 Payment Required response means you’ve hit a resource limit for your current plan:
Options:
  • Upgrade your plan — use the billing portal to switch to Pro or Enterprise
  • Clean up unused resources — delete inactive agents, revoke expired grants
  • Check your current usage via the developer dashboard

Delegation Failed

If POST /v1/grants/delegate returns an error:

Refresh Token Rejected

If POST /v1/token/refresh returns 400:

Error Codes Reference

All Grantex API errors include a code field. SDKs expose this as error.code:

Inspecting JWT Claims Locally

You can decode a Grantex grant token without verification to inspect its claims:
Grant token claims:

Getting Help

If none of the above resolves your issue:
  • Check the GitHub Issues for known problems
  • Open a new issue with your error message, requestId, and reproduction steps
  • For Enterprise support, use the contact form
Last modified on July 14, 2026