AgentOnboard Docs

Python (FastAPI)

How to authenticate AI agents in Python and FastAPI using agentonboard-sdk and public RS256 JWKS keys.

AgentOnboard identity tokens are standard RS256 JWTs signed by our identity engine.

You can verify them using the official agentonboard-sdk Python library, or directly with native pyjwt.


1. Install the Python SDK

pip install "agentonboard-sdk[fastapi]"

2. Guard your FastAPI routes

The SDK provides AgentBearerAuth, a FastAPI security dependency that handles token extraction, RS256 JWKS key caching, and 401/403 status mappings automatically.

Basic Token Authentication

main.py
from fastapi import Depends, FastAPI
from agentonboard import AgentIdentity
from agentonboard.frameworks.fastapi import AgentBearerAuth

app = FastAPI(title="Notes API")

# Configure verifier with your service domain
agent_auth = AgentBearerAuth(audience="notes.com")

@app.get("/api/notes")
async def list_notes(identity: AgentIdentity = Depends(agent_auth)):
    # identity.email contains the verified human's email address
    return {"message": f"Hello, {identity.email}"}

With Database Account Resolution & Auto-Provisioning

Use resolver to look up your existing database user, and optional creator to automatically provision accounts for new agents:

main.py
from fastapi import Depends, FastAPI
from agentonboard import AgentIdentity
from agentonboard.frameworks.fastapi import AgentBearerAuth

app = FastAPI(title="Notes API")

# Database lookups
async def get_user_by_email(identity: AgentIdentity):
    return await db.users.find_one({"email": identity.email.lower()})

async def create_user_for_agent(identity: AgentIdentity):
    return await db.users.insert_one({"email": identity.email.lower(), "role": "agent_user"})

agent_auth = AgentBearerAuth(
    audience="notes.com",
    resolver=get_user_by_email,
    creator=create_user_for_agent,
    auto_create=True,  # Set False to require existing account (returns 403 ACCOUNT_REQUIRED)
)

@app.post("/api/notes")
async def create_note(user = Depends(agent_auth)):
    return {"success": True, "user_id": str(user["_id"])}

3. Standalone Verification (Flask / Django / Scripts)

You can also call the verifier directly in synchronous or asynchronous code:

from agentonboard import verify_agent_token, verify_agent_account

# Sync verification
result = verify_agent_token(
    token=bearer_token,
    audience="notes.com",
)

if result.ok:
    print(f"Verified human: {result.email}")
else:
    print(f"Auth failed [{result.code}]: {result.error}")

For Flask apps:

from flask import Flask, jsonify, g
from agentonboard.frameworks.flask import require_agent_token

app = Flask(__name__)

@app.route("/api/notes")
@require_agent_token(audience="notes.com")
def get_notes():
    return jsonify({"email": g.agent_identity.email})

4. Alternative: Native PyJWT (Without SDK)

If you prefer not to add a third-party SDK dependency, you can verify tokens using standard pyjwt:

main.py
import jwt
from jwt import PyJWKClient
from fastapi import FastAPI, Header, HTTPException, Depends, status

app = FastAPI(title="Notes API")

JWKS_URL = "https://agentonboard.xyz/.well-known/jwks.json"
AUDIENCE = "notes.com"

jwks_client = PyJWKClient(JWKS_URL, cache_keys=True, max_cached_keys=16)

async def require_agent_user(authorization: str = Header(...)):
    if not authorization.startswith("Bearer "):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail={"code": "MALFORMED_TOKEN", "error": "Bearer token required"}
        )

    token = authorization.removeprefix("Bearer ").strip()
    try:
        signing_key = jwks_client.get_signing_key_from_jwt(token)
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            audience=AUDIENCE,
            leeway=5
        )
        email = payload.get("email")
        if not email:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail={"code": "MISSING_EMAIL", "error": "Token missing email claim"}
            )
        return email
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail={"code": "EXPIRED", "error": "Token expired"})
    except Exception as err:
        raise HTTPException(status_code=401, detail={"code": "INVALID_SIGNATURE", "error": str(err)})

5. The 401 vs 403 contract

Follow the AgentOnboard error specification:

  • Return 401 Unauthorized for invalid, expired, or malformed tokens. The agent will re-mint a fresh token once via get_token and retry.
  • Return 403 Forbidden with {"code": "ACCOUNT_REQUIRED"} when the token is cryptographically valid but the email has no account. The agent stops and directs the human user to connect their account.

6. Test with curl

# Mint a 5-minute test token
TOKEN=$(curl -s -X POST https://agentonboard.xyz/api/v1/token/exchange \
  -H "Authorization: Bearer aon_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"audience": "notes.com"}' | jq -r .token)

# Call your FastAPI endpoint
curl -i -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/notes

On this page