> ## 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 signatures and claims locally with a remote JWKS request per standalone call

## Overview

`VerifyGrantToken` verifies a grant token JWT locally using keys from the Grantex
JWKS endpoint. It avoids the online token-verification endpoint, including its
revocation lookup.

The standalone helper fetches keys from the configured JWKS endpoint on every
verification call, so that endpoint must be reachable. It does not call the
online token-verification/revocation API, but it is not network-free.

```go theme={null}
grant, err := grantex.VerifyGrantToken(ctx, tokenString, grantex.VerifyOptions{
    JwksURI: "https://api.grantex.dev/.well-known/jwks.json",
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Principal: %s, Scopes: %v\n", grant.PrincipalID, grant.Scopes)
```

## Options

| Field            | Type            | Required | Description                                                                                                                |
| ---------------- | --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `JwksURI`        | `string`        | Yes      | URL to fetch the JWKS from                                                                                                 |
| `RequiredScopes` | `[]string`      | No       | Scopes the token must contain                                                                                              |
| `Audience`       | `string`        | No       | Expected `aud` claim                                                                                                       |
| `Issuer`         | `string`        | No       | Expected `iss` claim. The hosted JWKS alias expects `https://grantex.dev`; custom JWKS URLs derive an issuer when omitted. |
| `IssuerDID`      | `string`        | No       | A `did:web` issuer used to derive the JWKS URL and expected issuer. An explicit `Issuer` takes precedence.                 |
| `ClockTolerance` | `time.Duration` | No       | Tolerance for clock skew                                                                                                   |

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.

## Response (`VerifiedGrant`)

| Field             | Type       | Description                                  |
| ----------------- | ---------- | -------------------------------------------- |
| `TokenID`         | `string`   | JWT `jti` claim                              |
| `GrantID`         | `string`   | Grant ID (`grnt` claim, falls back to `jti`) |
| `PrincipalID`     | `string`   | End-user (`sub` claim)                       |
| `AgentDID`        | `string`   | Agent DID (`agt` claim)                      |
| `DeveloperID`     | `string`   | Developer (`dev` claim)                      |
| `Scopes`          | `[]string` | Granted scopes (`scp` claim)                 |
| `IssuedAt`        | `int64`    | Unix timestamp                               |
| `ExpiresAt`       | `int64`    | Unix timestamp                               |
| `ParentAgentDID`  | `*string`  | Parent agent for delegated grants            |
| `ParentGrantID`   | `*string`  | Parent grant for delegated grants            |
| `DelegationDepth` | `*int`     | Delegation depth (0 = root)                  |

## Scope Checking

```go theme={null}
grant, err := grantex.VerifyGrantToken(ctx, token, grantex.VerifyOptions{
    JwksURI:        "https://api.grantex.dev/.well-known/jwks.json",
    RequiredScopes: []string{"read:email", "send:email"},
})
// Returns *TokenError if any required scope is missing
```

## Error Handling

Returns `*grantex.TokenError` for:

* Missing or invalid JWKS URI
* Expired tokens
* Invalid signatures
* Issuer or audience mismatches
* Missing or incorrectly typed required claims (`jti`, `sub`, `agt`, `dev`,
  `scp`, `iat`, `exp`)
* Missing required scopes
* Malformed JWTs
