> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Offline Verification

> Verify grant tokens locally with published JWKS keys, without a per-token Grantex verification API call.

## Overview

`verifyGrantToken()` is a standalone function that verifies signatures and claims
locally after retrieving the published JWKS (JSON Web Key Set). It validates the
RS256 signature, issuer, expiry, and optional scopes and audience. The current
standalone helper resolves the remote JWKS on each invocation, so it is not a
network-free hot path.

Use it when signature-and-claim validation without a revocation lookup is the
right trade-off. It avoids `POST /v1/tokens/verify`, but the JWKS endpoint must be
reachable for each standalone helper call.

```typescript theme={null}
import { verifyGrantToken } from '@grantex/sdk';

const grant = await verifyGrantToken(token, {
  jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
  requiredScopes: ['calendar:read'],
});

console.log(grant.principalId); // The user who authorized this agent
console.log(grant.agentDid);    // The agent's DID
console.log(grant.scopes);      // All granted scopes
```

<Note>
  The standalone helper may contact the JWKS endpoint when it resolves keys.
  Keep that endpoint reachable from the verifying service; token validation
  itself is performed locally and does not call `POST /v1/tokens/verify`.
</Note>

<Note>
  Hosted tokens use the canonical issuer `https://grantex.dev`, even though the
  stable JWKS URL is served from `https://api.grantex.dev`. The SDK handles this
  alias automatically. For self-hosted deployments, it derives the expected
  issuer from the JWKS URL unless you set `issuer` or `issuerDid` explicitly.
</Note>

## Import

```typescript theme={null}
import { verifyGrantToken } from '@grantex/sdk';
```

This function does not require a `Grantex` client instance.

## Parameters

<ParamField body="token" type="string" required>
  The grant token JWT string to verify.
</ParamField>

<ParamField body="options" type="VerifyGrantTokenOptions" required>
  Verification options.
</ParamField>

### VerifyGrantTokenOptions

<ParamField body="jwksUri" type="string" required>
  The JWKS endpoint URL. For the hosted service, use `https://api.grantex.dev/.well-known/jwks.json`.
</ParamField>

<ParamField body="requiredScopes" type="string[]">
  If provided, the function throws `GrantexTokenError` when the token is missing any of these scopes.
</ParamField>

<ParamField body="audience" type="string">
  Expected `aud` claim. If provided, verification fails when the token's audience does not match.
</ParamField>

<ParamField body="issuer" type="string">
  Expected `iss` claim. The hosted JWKS alias automatically expects
  `https://grantex.dev`; custom JWKS URLs derive the issuer from their URL when
  this option is omitted.
</ParamField>

<ParamField body="issuerDid" type="string">
  A `did:web` issuer identifier. When provided, the SDK derives both the JWKS URL
  and expected issuer from the DID. An explicit `issuer` still takes precedence.
</ParamField>

<ParamField body="clockTolerance" type="number">
  Optional clock-skew tolerance, in seconds, for time-based JWT claims.
</ParamField>

## Response: `VerifiedGrant`

<ResponseField name="tokenId" type="string">
  Unique token ID (the `jti` JWT claim).
</ResponseField>

<ResponseField name="grantId" type="string">
  The grant record ID (from the `grnt` claim, falls back to `jti`).
</ResponseField>

<ResponseField name="principalId" type="string">
  The end-user who authorized the agent (the `sub` claim).
</ResponseField>

<ResponseField name="agentDid" type="string">
  The agent's decentralized identifier (the `agt` claim).
</ResponseField>

<ResponseField name="developerId" type="string">
  The developer organization that owns the agent (the `dev` claim).
</ResponseField>

<ResponseField name="scopes" type="string[]">
  The scopes granted to the agent (the `scp` claim).
</ResponseField>

<ResponseField name="issuedAt" type="number">
  Token issued-at timestamp in seconds since the Unix epoch.
</ResponseField>

<ResponseField name="expiresAt" type="number">
  Token expiry timestamp in seconds since the Unix epoch.
</ResponseField>

<ResponseField name="parentAgentDid" type="string">
  The parent agent's DID, present only for delegated grants.
</ResponseField>

<ResponseField name="parentGrantId" type="string">
  The parent grant ID, present only for delegated grants.
</ResponseField>

<ResponseField name="delegationDepth" type="number">
  The delegation depth (`0` = root grant, `1` = first-level delegation, etc.). Present only for delegated grants.
</ResponseField>

## Error handling

`verifyGrantToken()` throws `GrantexTokenError` in the following cases:

* The JWT signature is invalid
* The token has expired
* The token issuer does not match the expected issuer
* Required claims (`jti`, `sub`, `agt`, `dev`, `scp`, `iat`, `exp`) are missing
* The token is missing one or more `requiredScopes`
* The `audience` does not match

```typescript theme={null}
import { verifyGrantToken, GrantexTokenError } from '@grantex/sdk';

try {
  const grant = await verifyGrantToken(token, {
    jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
    requiredScopes: ['payments:initiate'],
  });
  // Token is valid -- proceed
} catch (err) {
  if (err instanceof GrantexTokenError) {
    console.error('Token verification failed:', err.message);
    // e.g. "Grant token is missing required scopes: payments:initiate"
  }
}
```

## JWT claims mapping

The following table shows how JWT claims map to `VerifiedGrant` fields:

| JWT Claim         | VerifiedGrant Field | Description                           |
| ----------------- | ------------------- | ------------------------------------- |
| `jti`             | `tokenId`           | Unique token identifier               |
| `grnt`            | `grantId`           | Grant record ID (falls back to `jti`) |
| `sub`             | `principalId`       | End-user identifier                   |
| `agt`             | `agentDid`          | Agent decentralized identifier        |
| `dev`             | `developerId`       | Developer organization ID             |
| `scp`             | `scopes`            | Granted scopes array                  |
| `iat`             | `issuedAt`          | Issued-at timestamp (epoch seconds)   |
| `exp`             | `expiresAt`         | Expiry timestamp (epoch seconds)      |
| `parentAgt`       | `parentAgentDid`    | Parent agent DID (delegation)         |
| `parentGrnt`      | `parentGrantId`     | Parent grant ID (delegation)          |
| `delegationDepth` | `delegationDepth`   | Delegation chain depth                |

## Comparison with online verification

|                      | `verifyGrantToken()`                               | `tokens.verify()`                     |
| -------------------- | -------------------------------------------------- | ------------------------------------- |
| **Network call**     | May fetch keys from the JWKS endpoint              | Calls Grantex API                     |
| **Latency**          | Local cryptography plus JWKS retrieval when needed | Network round-trip                    |
| **Revocation check** | No (checks signature + expiry only)                | Yes (checks revocation status)        |
| **Requires API key** | No                                                 | Yes                                   |
| **Use case**         | Services verifying tokens at high throughput       | Admin dashboards, token status checks |
