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

# FastAPI

> Grant token verification and scope-based authorization for FastAPI using dependency injection.

## Install

```bash theme={null}
pip install grantex-fastapi
```

## Quick Start

One dependency protects your entire API:

```python theme={null}
from fastapi import Depends, FastAPI
from grantex import VerifiedGrant
from grantex_fastapi import GrantexAuth, GrantexFastAPIError, grantex_exception_handler

app = FastAPI()
app.add_exception_handler(GrantexFastAPIError, grantex_exception_handler)

JWKS_URI = "https://api.grantex.dev/.well-known/jwks.json"
grantex = GrantexAuth(jwks_uri=JWKS_URI)

@app.get("/api/calendar")
async def calendar(grant: VerifiedGrant = Depends(grantex.scopes("calendar:read"))):
    return {"principalId": grant.principal_id, "scopes": list(grant.scopes)}
```

After verification succeeds, `grant` is a fully typed `VerifiedGrant` dataclass with the principal, agent, scopes, and timestamps.

***

## How It Works

```
Client → Authorization: Bearer <grantToken> → FastAPI app
                                                 │
                                         Depends(grantex)
                                            │         │
                                       Valid JWT?   Invalid/Missing?
                                            │         │
                                   Return VerifiedGrant   Raise GrantexFastAPIError
                                            │                    │
                                      .scopes() check      grantex_exception_handler
                                       │           │              │
                                  Has scopes?   Missing?     JSON error response
                                       │           │
                                  Return grant   Raise 403
```

1. `GrantexAuth` extracts the Bearer token from the `Authorization` header
2. It verifies the RS256 signature against the Grantex JWKS endpoint
3. On success, a `VerifiedGrant` is returned and injected into your handler
4. `.scopes()` optionally checks that the grant contains all required scopes
5. Errors are converted to JSON responses by `grantex_exception_handler`

***

## Token Verification Only

Use `Depends(grantex)` to verify the token without checking scopes:

```python theme={null}
@app.get("/api/me")
async def me(grant: VerifiedGrant = Depends(grantex)):
    return {"principalId": grant.principal_id, "agentDid": grant.agent_did}
```

***

## Scope Enforcement

### Via dependency (recommended)

Use `Depends(grantex.scopes(...))` to verify the token AND check scopes in one step:

```python theme={null}
# Single scope
@app.get("/api/calendar")
async def calendar(grant: VerifiedGrant = Depends(grantex.scopes("calendar:read"))):
    return {"events": get_calendar_events(grant.principal_id)}

# Multiple scopes — all required
@app.post("/api/email/send")
async def send_email(grant: VerifiedGrant = Depends(grantex.scopes("email:read", "email:send"))):
    return {"sent": True}
```

### Via function call

Check scopes inside the route handler with `require_scopes()`:

```python theme={null}
from grantex_fastapi import require_scopes

@app.get("/api/data")
async def data(grant: VerifiedGrant = Depends(grantex)):
    require_scopes(grant, "data:read")
    return {"data": get_data(grant.principal_id)}
```

***

## Custom Token Extraction

By default, the middleware reads the `Authorization: Bearer <token>` header. Override this with `token_extractor`:

<CodeGroup>
  ```python Cookie theme={null}
  from fastapi import Request

  def extract_from_cookie(request: Request) -> str | None:
      return request.cookies.get("grant_token")

  grantex = GrantexAuth(jwks_uri=JWKS_URI, token_extractor=extract_from_cookie)
  ```

  ```python Custom Header theme={null}
  def extract_from_header(request: Request) -> str | None:
      return request.headers.get("x-grant-token")

  grantex = GrantexAuth(jwks_uri=JWKS_URI, token_extractor=extract_from_header)
  ```

  ```python Query Parameter theme={null}
  def extract_from_query(request: Request) -> str | None:
      return request.query_params.get("token")

  grantex = GrantexAuth(jwks_uri=JWKS_URI, token_extractor=extract_from_query)
  ```
</CodeGroup>

***

## Error Handling

Register the built-in exception handler to return JSON error responses:

```python theme={null}
app.add_exception_handler(GrantexFastAPIError, grantex_exception_handler)
```

Or write a custom handler:

```python theme={null}
from fastapi import Request
from fastapi.responses import JSONResponse
from grantex_fastapi import GrantexFastAPIError

@app.exception_handler(GrantexFastAPIError)
async def custom_handler(request: Request, exc: GrantexFastAPIError) -> JSONResponse:
    if exc.code == "TOKEN_EXPIRED":
        return JSONResponse(
            status_code=401,
            content={"error": "session_expired", "message": "Please re-authorize."},
        )
    return JSONResponse(
        status_code=exc.status_code,
        content={"error": exc.code, "message": str(exc)},
    )
```

### Error Codes

| Code | HTTP Status | When |
| - | - | - |
| `TOKEN_MISSING` | 401 | No token found in the request |
| `TOKEN_INVALID` | 401 | JWT signature or format is invalid |
| `TOKEN_EXPIRED` | 401 | JWT `exp` claim is in the past |
| `SCOPE_INSUFFICIENT` | 403 | Token is missing one or more required scopes |

***

## `grant` Reference

The `VerifiedGrant` dataclass contains:

| Field | Type | Description |
| - | - | - |
| `token_id` | `str` | JWT `jti` claim — unique token identifier |
| `grant_id` | `str` | Grantex grant record ID |
| `principal_id` | `str` | End-user who authorized the agent |
| `agent_did` | `str` | Agent's decentralized identifier |
| `developer_id` | `str` | Developer organization ID |
| `scopes` | `tuple[str, ...]` | Scopes the agent was granted |
| `issued_at` | `int` | Token issued-at (seconds since epoch) |
| `expires_at` | `int` | Token expiry (seconds since epoch) |
| `parent_agent_did` | `str \| None` | Parent agent DID (delegated grants only) |
| `parent_grant_id` | `str \| None` | Parent grant ID (delegated grants only) |
| `delegation_depth` | `int \| None` | Delegation depth (0 = root grant) |

***

## Full Example

A complete FastAPI application protected by Grantex:

```python theme={null}
from fastapi import Depends, FastAPI
from grantex import VerifiedGrant
from grantex_fastapi import GrantexAuth, GrantexFastAPIError, grantex_exception_handler

app = FastAPI(title="My Agent API")
app.add_exception_handler(GrantexFastAPIError, grantex_exception_handler)

grantex = GrantexAuth(
    jwks_uri="https://api.grantex.dev/.well-known/jwks.json",
    clock_tolerance=5,
)


# Public health check
@app.get("/health")
async def health():
    return {"status": "ok"}


# Token verification only
@app.get("/api/me")
async def me(grant: VerifiedGrant = Depends(grantex)):
    return {
        "principalId": grant.principal_id,
        "agentDid": grant.agent_did,
        "scopes": list(grant.scopes),
    }


# Scope enforcement
@app.get("/api/calendar")
async def calendar(grant: VerifiedGrant = Depends(grantex.scopes("calendar:read"))):
    return {"events": get_calendar_events(grant.principal_id)}


@app.post("/api/calendar/events")
async def create_event(grant: VerifiedGrant = Depends(grantex.scopes("calendar:write"))):
    return {"created": True}


@app.post("/api/email/send")
async def send_email(
    grant: VerifiedGrant = Depends(grantex.scopes("email:read", "email:send")),
):
    return {"sent": True}


def get_calendar_events(principal_id: str) -> list[dict]:
    return [{"id": "1", "title": "Team standup", "principalId": principal_id}]
```

***

## API Reference

### `GrantexAuth(jwks_uri, *, clock_tolerance=0, audience=None, token_extractor=None)`

Creates a Grantex authentication dependency.

| Parameter | Type | Default | Description |
| - | - | - | - |
| `jwks_uri` | `str` | *required* | JWKS endpoint URL for token verification |
| `clock_tolerance` | `int` | `0` | Seconds of clock skew tolerance |
| `audience` | `str \| None` | `None` | Expected JWT `aud` claim |
| `token_extractor` | `Callable[[Request], str \| None] \| None` | `None` | Custom function to extract the token |

### `grantex.scopes(*required_scopes)`

Returns a dependency that verifies the token AND checks all required scopes.

### `require_scopes(grant, *scopes)`

Standalone function that checks scopes on an already-verified grant. Raises `GrantexFastAPIError` with `SCOPE_INSUFFICIENT` if any scope is missing.

### `grantex_exception_handler(request, exc)`

Starlette exception handler that converts `GrantexFastAPIError` to a JSON response.

***

## Requirements

* Python 3.9+
* FastAPI >= 0.100.0
* `grantex` >= 0.1.0

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

## Current Authority and Human Identity

Use the exact integration version and minimum primary SDK in [Release Status](/release-status).
Per-invocation issuer authority and trusted principal/agent binding are opt-in.
Configure the callback with the trusted issuer and derive identity from the host's
authenticated session; signature and scope checks alone do not prove current
revocation or human consent. See [SDK execution authority](/guides/sdk-execution-authority)
for configuration, denial behavior and manifest/decision/caps boundaries.


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