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. Revoking and the emergency stop do not consume the plan budget and are not refused when the counter is unavailable; the revocation feed and status reads have a budget of their own and still answer 503 then. See Rate Limits. 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

Issuance Frozen (403)

403 with ISSUANCE_FROZEN means an emergency stop placed a lockout over something this request would issue under. That can be your whole developer account, the agent, the principal, or a grant above the one you are refreshing or delegating from.
The OAuth profile’s endpoints answer 403 with access_denied. Nothing is issued until the lockout is lifted. An authorization code or refresh token refused this way is not used up, so it works again afterwards. GET /v1/emergency-stops lists the lockouts in force under freezes, 50 to a page; freezesTotal says how many there are, and ?page=2 fetches the next. POST /v1/emergency-stop/unfreeze lifts one you placed; a lockout placed by the platform operator can only be lifted by the operator. 503 with FREEZE_STATE_UNAVAILABLE (temporarily_unavailable on the OAuth endpoints) means the service could not read whether a lockout applies, and refused rather than risk issuing under one. Retry shortly.

Delegation Failed

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

Refresh Token Rejected

If POST /v1/token/refresh returns 400: If a refresh request times out or the connection drops, retry the same refresh token immediately. Grantex can recover the already-rotated refresh token for five minutes while the grant remains active, without extending the rotation chain or grant lifetime.

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

Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: sanjeev@orchestrum.in or mishra.sanjeev@gmail.com.
Last modified on September 27, 2026