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.
Install
pip install grantex-fastapi
Quick Start
One dependency protects your entire API:
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://grantex-auth-dd4mtrt2gq-uc.a.run.app/.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
GrantexAuth extracts the Bearer token from the Authorization header
- It verifies the RS256 signature against the Grantex JWKS endpoint
- On success, a
VerifiedGrant is returned and injected into your handler
.scopes() optionally checks that the grant contains all required scopes
- Errors are converted to JSON responses by
grantex_exception_handler
Token Verification Only
Use Depends(grantex) to verify the token without checking scopes:
@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:
# 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():
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)}
By default, the middleware reads the Authorization: Bearer <token> header. Override this with token_extractor:
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)
Error Handling
Register the built-in exception handler to return JSON error responses:
app.add_exception_handler(GrantexFastAPIError, grantex_exception_handler)
Or write a custom handler:
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:
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://grantex-auth-dd4mtrt2gq-uc.a.run.app/.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
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