Python FastAPI Quickstart
Build a chat backend that mints per-user JWTs and proxies to Behest. Works for any Python frontend, mobile app, or server-to-server flow.
Prerequisites: Python 3.10+, a Behest project + API key + slug.
1. Configure CORS (only if a browser will call Behest directly)
If your frontend calls Behest from the browser with the minted JWT — the recommended pattern below — add every browser origin to Project → Settings → Allowed Origins in the dashboard:
http://localhost:3000
http://localhost:5173
https://your-app.com
Click Save. Skip this step if FastAPI is the only caller (server-to-server).
2. Install
pip install fastapi uvicorn "behest-ai>=1.5" python-multipartbehest>=1.5 bundles the v1.5 dual-mode SDK (API-key mode + local-sign mode) on top of the OpenAI SDK.
3. Token endpoint
Security: derive
user_idandtierfrom a server-verified session (your session cookie, auth provider, or identity JWT — see integrations/supabase.md, clerk.md, auth0.md). Never accept them from the request body or a client-controlled header — any caller could mint a JWT for any user and spoof the entire tenant.
# app.py
import os
from fastapi import FastAPI, HTTPException, Depends
from behest import Behest
from .auth import require_user # your app's verified-session dependency
app = FastAPI()
# Reads BEHEST_KEY and BEHEST_BASE_URL from env.
# behest_sk_live_* → apiKey mode. behest_pk_* (tenant PEM) → local-sign mode.
behest = Behest()
@app.post("/api/behest/token")
async def mint_token(user = Depends(require_user)):
# user.id and user.plan come from YOUR verified session — never from the request body.
try:
result = await behest.auth.mint(user_id=user.id, tier=user.plan or 1, ttl=900)
except Exception as e:
raise HTTPException(500, str(e))
return {
"token": result.token,
"ttl": result.ttl,
"sessionId": result.session_id,
"expiresAt": result.expires_at,
}Local signing alternative
If BEHEST_KEY starts with behest_pk_ (a tenant RSA PEM), the same code above switches to local-sign mode — no HTTP round-trip per mint. You also need BEHEST_KID, BEHEST_TENANT_ID, BEHEST_PROJECT_ID in env. See auth-modes.
4. Server-to-server chat
# chat.py
from behest import Behest
behest = Behest() # reads BEHEST_KEY + BEHEST_BASE_URL from env
async def chat_for_user(user_id: str, messages: list):
# Pass user_id on the chat call — the SDK auto-mints a per-user JWT for this request.
stream = await behest.chat.completions.create(
messages=messages,
stream=True,
user_id=user_id,
# model is optional — Behest uses your project default when omitted
)
async for chunk in stream:
yield chunk.choices[0].delta.content or ""5. Browser talks to Behest directly (recommended)
Your frontend fetches a JWT from /api/behest/token, then calls Behest directly. Kong enforces the allowed origins you configured in step 1.
// Browser — fetch token
async function fetchBehestToken() {
const r = await fetch("/api/behest/token", {
method: "POST",
credentials: "include",
});
return (await r.json()) as { token: string; ttl: number; sessionId: string };
}
// Browser — stream a chat completion
const { token, sessionId } = await fetchBehestToken();
const resp = await fetch(`${BEHEST_BASE_URL}/v1/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"X-Session-Id": sessionId,
},
body: JSON.stringify({ messages, stream: true }),
});Only add a FastAPI streaming proxy if you need central logging or to inject server-only data into every prompt; most apps prefer browser-direct.
6. Run
BEHEST_KEY=behest_sk_live_... BEHEST_BASE_URL=https://amber-fox-042.behest.app uvicorn app:app --reload# /token requires a verified session — call it from your authenticated client, not curl.Next steps
- Python SDK reference
- Error handling — typed exceptions, retries
- Streaming UI