> ## 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 underlying grant expires. |
| `refresh_token` | `str` | A refresh token for rotating credentials while the grant remains active. |

### 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 while the underlying grant remains active. Returns a new grant token and a new refresh token. The `grant_id` and `expires_at` stay the same.

Refresh tokens are single-use and rotated on every refresh per SPEC §7.4. Refresh does not extend the grant lifetime; after `expires_at`, the caller must re-authorize. If the HTTP response is lost after commit, retry the same previous refresh token with the same idempotency key. During a five-minute window, Grantex returns the already-rotated refresh token instead of rotating again while the grant remains active. After that window, or once the rotated child token has been used, the previous token is rejected.

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

with Grantex(api_key="gx_live_...") as client:
    # Persist this with the old token until the response is durable.
    refresh_attempt_id = str(uuid4())
    new_token = client.tokens.refresh(RefreshTokenParams(
        refresh_token="rt_01HXYZ...",
        agent_id="agt_abc123",
        idempotency_key=refresh_attempt_id,
    ))

    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}")
    print(f"Same grant expiration: {new_token.expires_at}")
```

### 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. |
| `idempotency_key` | `str` | No | Stable 16-256 character identifier for this logical refresh. The SDK retains an omitted key for five minutes in-process; persist an explicit key for restart/failover recovery. |

### Response

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

<Warning>
  Each refresh token can only be used once. Always store the new `refresh_token`. Retry a lost response with the same old token and `idempotency_key`; a new SDK process cannot reconstruct an omitted key. After five minutes, after the rotated child token is used, or after grant expiration, reuse is rejected.
</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 local signature-and-claim verification without the online revocation
  endpoint, use [`verify_grant_token()`](/sdks/python/offline-verification). The
  standalone helper still fetches the remote JWKS on every invocation.
</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")
```

## Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.