> ## 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.

# Tokens

> Exchange authorization codes for grant tokens, verify tokens online, and revoke tokens.

## Overview

The `tokens` client provides four operations for managing grant tokens:

* **Exchange** an authorization code for a grant token
* **Refresh** a grant token using a refresh token
* **Verify** a grant token online via the Grantex API
* **Revoke** a grant token by its token ID (JTI)

Access the tokens client via `client.tokens`.

## Exchange

Exchange an authorization code for a grant token after the user approves the consent request.

```python theme={null}
from grantex import Grantex, ExchangeTokenParams

with Grantex(api_key="gx_live_...") as client:
    result = client.tokens.exchange(ExchangeTokenParams(
        code="auth_code_from_callback",
        agent_id="agt_abc123",
    ))

    print(f"Grant token: {result.grant_token}")
    print(f"Grant ID: {result.grant_id}")
    print(f"Scopes: {result.scopes}")
    print(f"Expires at: {result.expires_at}")
    print(f"Refresh token: {result.refresh_token}")
```

### ExchangeTokenParams

| Field           | Type          | Required | Description                                                    |
| --------------- | ------------- | -------- | -------------------------------------------------------------- |
| `code`          | `str`         | Yes      | The authorization code received at the redirect URI.           |
| `agent_id`      | `str`         | Yes      | The agent ID that initiated the authorization.                 |
| `code_verifier` | `str \| None` | No       | The PKCE code verifier, if PKCE was used during authorization. |

### ExchangeTokenResponse

| Field           | Type              | Description                                     |
| --------------- | ----------------- | ----------------------------------------------- |
| `grant_token`   | `str`             | The JWT grant token.                            |
| `grant_id`      | `str`             | The unique grant identifier.                    |
| `scopes`        | `tuple[str, ...]` | The approved scopes.                            |
| `expires_at`    | `str`             | ISO 8601 timestamp when the token expires.      |
| `refresh_token` | `str`             | A refresh token for obtaining new grant tokens. |

### Exchange with PKCE

```python theme={null}
from grantex import Grantex, ExchangeTokenParams

with Grantex(api_key="gx_live_...") as client:
    result = client.tokens.exchange(ExchangeTokenParams(
        code="auth_code_from_callback",
        agent_id="agt_abc123",
        code_verifier="dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
    ))
```

## Refresh

Refresh a grant token using a refresh token. Returns a new grant token and a new refresh token (the old refresh token is invalidated). The `grant_id` stays the same.

Refresh tokens are single-use and rotated on every refresh per SPEC §7.4.

```python theme={null}
from grantex import Grantex, RefreshTokenParams

with Grantex(api_key="gx_live_...") as client:
    new_token = client.tokens.refresh(RefreshTokenParams(
        refresh_token="rt_01HXYZ...",
        agent_id="agt_abc123",
    ))

    print(f"New grant token: {new_token.grant_token}")
    print(f"New refresh token: {new_token.refresh_token}")
    print(f"Same grant ID: {new_token.grant_id}")
```

### RefreshTokenParams

| Field           | Type  | Required | Description                                                             |
| --------------- | ----- | -------- | ----------------------------------------------------------------------- |
| `refresh_token` | `str` | Yes      | The refresh token from a previous `exchange()` or `refresh()` response. |
| `agent_id`      | `str` | Yes      | The agent ID associated with the grant.                                 |

### Response

Returns an `ExchangeTokenResponse` — same shape as `exchange()`. See above for field descriptions.

<Warning>
  Each refresh token can only be used once. If you attempt to reuse a refresh token, the request will be rejected with a `400` error. Always store and use the new `refresh_token` from the response.
</Warning>

## Verify

Verify a grant token online via the Grantex API. This sends the token to the server for validation and returns the token's metadata.

```python theme={null}
from grantex import Grantex

with Grantex(api_key="gx_live_...") as client:
    result = client.tokens.verify("eyJhbGciOiJSUzI1NiIs...")

    if result.valid:
        print(f"Grant ID: {result.grant_id}")
        print(f"Scopes: {result.scopes}")
        print(f"Principal: {result.principal}")
        print(f"Agent: {result.agent}")
        print(f"Expires at: {result.expires_at}")
    else:
        print("Token is invalid or expired")
```

### VerifyTokenResponse

| Field        | Type                      | Description                               |
| ------------ | ------------------------- | ----------------------------------------- |
| `valid`      | `bool`                    | Whether the token is valid.               |
| `grant_id`   | `str \| None`             | The grant identifier (if valid).          |
| `scopes`     | `tuple[str, ...] \| None` | The granted scopes (if valid).            |
| `principal`  | `str \| None`             | The user/principal ID (if valid).         |
| `agent`      | `str \| None`             | The agent DID (if valid).                 |
| `expires_at` | `str \| None`             | ISO 8601 expiration timestamp (if valid). |

<Note>
  For offline verification that does not require a network call to the Grantex API, use the standalone [`verify_grant_token()`](/sdks/python/offline-verification) function.
</Note>

## Revoke

Revoke a grant token by its token ID (JTI claim). The token is immediately invalidated and can no longer be used.

```python theme={null}
from grantex import Grantex

with Grantex(api_key="gx_live_...") as client:
    client.tokens.revoke("tok_abc123")
    # Returns None on success
```

### Parameters

| Parameter  | Type  | Description                                      |
| ---------- | ----- | ------------------------------------------------ |
| `token_id` | `str` | The JTI (token ID) of the grant token to revoke. |

The method returns `None`. A `GrantexApiError` is raised if the token does not exist or has already been revoked.

## Complete Flow Example

```python theme={null}
from grantex import (
    Grantex,
    AuthorizeParams,
    ExchangeTokenParams,
    generate_pkce,
)

pkce = generate_pkce()

with Grantex(api_key="gx_live_...") as client:
    # 1. Start authorization with PKCE
    auth = client.authorize(AuthorizeParams(
        agent_id="agt_abc123",
        user_id="user_xyz",
        scopes=["files:read"],
        redirect_uri="https://myapp.com/callback",
        code_challenge=pkce.code_challenge,
        code_challenge_method=pkce.code_challenge_method,
    ))
    print(f"Redirect user to: {auth.consent_url}")

    # 2. After user approves, exchange the code
    code = "received_from_redirect"  # from your callback handler
    token_response = client.tokens.exchange(ExchangeTokenParams(
        code=code,
        agent_id="agt_abc123",
        code_verifier=pkce.code_verifier,
    ))

    # 3. Use the grant token with your agent
    grant_token = token_response.grant_token

    # 4. Verify the token is still valid
    verification = client.tokens.verify(grant_token)
    print(f"Valid: {verification.valid}")

    # 5. Revoke when no longer needed
    # Extract token ID from the JWT or use the known ID
    client.tokens.revoke("tok_abc123")
```
