openapi.json and the file-based TanStack Start routes under apps/web/src/routes/api/. Regenerate the OpenAPI document with pnpm generate:docs.
Base URL: http://localhost:3000
All request bodies are validated with zod schemas in apps/web/src/lib/pi/chat-protocol.zod.ts. Responses are JSON unless otherwise noted; /api/chat returns an NDJSON event stream.
Chat
POST /api/chat
Send a chat message and receive a streaming response. Request body- 200 — NDJSON stream of
ChatStreamEventlines. See stream events. - 400 — Validation error.
POST /api/chat/abort
Abort the active chat session. Request body- 200 —
{ "aborted": true | false } - 500 —
{ "message": "<error>" }
GET /api/chat/models
List available chat models. Responses- 200 —
{ models, selectedModelKey, defaultProvider, defaultModel, defaultThinkingLevel, diagnostics } - 500 —
{ message }
GET /api/chat/providers · POST /api/chat/providers
Read or update provider credential state.GET reports which providers have credentials configured (best-effort, based on env-var presence — actual auth is verified at runtime). POST updates the matching env var entry through the in-app config panel.
GET response
amazon-bedrock, openai, anthropic, google-vertex, google-genai (Gemini), mistral, groq, ollama.
GET /api/chat/commands
List slash commands available in the chat input. Populates the/-triggered command menu with built-ins plus any active skills and prompts. See slash commands for the UX and keyboard shortcuts.
Response
source is one of builtin, skill, prompt, or extension. Skill and prompt entries are omitted when enableSkillCommands is disabled in Pi settings, and diagnostics will explain why.
Responses
- 200 —
{ commands, diagnostics } - 500 —
{ message }
POST /api/chat/new
Create a new chat session. Responses- 200 —
{ sessionFile, sessionId } - 500 —
{ message }
GET /api/chat/provenance
Fetch per-run provenance records. Responses- 200 — provenance payload
- 500 —
{ message }
POST /api/chat/question
Answer aquestionnaire follow-up question raised by the assistant.
Request body
- 200 —
{ ok, message, mode, planAction } - 400 — Bad request.
- 404 —
{ ok: false, message }
GET /api/chat/resources
List available chat resources (skills, prompts, extensions, themes, packages, AGENTS.md files). Responses- 200 —
{ packages, skills, prompts, extensions, themes, agentsFiles, diagnostics } - 500 —
{ message }
POST /api/chat/resume
Resume an existing chat session. Request body- 200 —
{ session, messages, sessionReset } - 500 —
{ message }
POST /api/chat/run
Execute a single recorded run.GET /api/chat/runs
List recorded runs for a session.GET /api/chat/session · DELETE /api/chat/session
Hydrate or delete a chat session by query parameters.DELETE removes the caller’s owned mirror row in Neon (cascading run, tool, and file mutation rows) and best-effort cleans up the ephemeral JSONL file. Requires an authenticated Better Auth session and passes ownership verification against the Neon mirror.
GET responses
- 200 —
{ session, messages, sessionReset } - 500 —
{ message }
- 200 —
{ ok: true, sessionId, sessionFile } - 401 — Unauthorized.
- 403 — Session is not owned by the caller.
- 404 — Session not owned or missing (
{ ok: false, reason: "session-not-owned-or-missing" }). - 501 — Mirror disabled (
FLEET_PI_CHAT_DATABASE_URLnot set). - 503 — Mirror unavailable.
DELETE /api/chat/account
Erase all mirrored Pi data for the signed-in user: every row inpi_sessions (cascading transcripts, runs, tool output, and file mutations) and every BYOK provider credential in pi_user_providers. Ownership is enforced under Neon RLS and only the caller’s data is touched.
Better Auth identity rows are not deleted — coordinate account deletion in Better Auth separately if required. Workspace files on disk are not reverted.
Responses
- 200 —
{ ok: true, scope: "pi-mirror", erasedSessions, erasedProviders, message } - 401 — Unauthorized.
- 500 —
{ ok: false, reason, message }
GET /api/chat/sessions
List all chat sessions. Responses- 200 —
{ sessions } - 500 —
{ message }
GET /api/chat/settings · POST /api/chat/settings
Read or patch effectiveChatPiSettings: compaction, retry, default model and thinking level, enabled models, extensions, packages, prompts, skills, themes, transport, and steering / follow-up delivery modes.
POST request body
Workspace
Workspace endpoints read and write the durable layer described by the adaptive workspace contract.Every
/api/workspace/* route resolves the caller’s user-scoped workspace and rejects unauthenticated callers with 401 Unauthorized on protected deployments (Vercel, Neon Managed Auth, Neon Function surface, or FLEET_PI_CHAT_RUNTIME_REQUIRE_AUTH=1). Send either a Neon Auth Authorization: Bearer <token> header or an authenticated session cookie. Local development without auth configured continues to serve the repo-scoped workspace.GET /api/workspace/health
Workspace contract health (manifest version, missing canonical paths, projection state).GET /api/workspace/file
Read a canonical workspace file.GET /api/workspace/item · GET /api/workspace/items
Fetch a single workspace item or list items.GET /api/workspace/tree
Browse the workspace tree.GET /api/workspace/search
Search workspace items via the projection index inagent-workspace/indexes/.
GET /api/workspace/reindex · POST /api/workspace/reindex
Rebuild the projection index. The canonical files are not modified — the index is recomputed. On protected deployments,POST /api/workspace/reindex is protected by a double-submit CSRF token and a per-user rate limit:
- Rate limit — up to 5 requests per minute per user. Excess calls return
429 Too Many RequestswithRetry-After: 60. - CSRF token — cookie-authenticated callers must first
GET /api/workspace/reindexto receive afleet_pi_csrfcookie (HttpOnly,SameSite=Strict) plus acsrfTokenin the response body, then echo the token back on thePOSTas thex-fleet-csrf-tokenheader. Same-originOrigin(orReferer) is also required. Bearer-token callers skip CSRF because they are not vulnerable to cross-site cookie replay.
- 200 — reindex completed.
- 403 —
{ "message": "Invalid CSRF protection" }when the CSRF token, cookie, or same-origin check fails. - 429 —
{ "message": "Workspace reindex rate limit exceeded" }withRetry-After: 60.
Outbound network safety
The chat runtime’sweb_fetch tool only accepts public HTTPS URLs. Requests to localhost, loopback, RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local (169.254.0.0/16), or IPv6 unique-local (fc00::/7) addresses are rejected before any connection is opened, and DNS lookups are pinned so a public hostname cannot rebind to a private address between resolution and connect. GitHub blob/ URLs are automatically rewritten to raw content URLs. Only text/* and application/json responses are returned; binary payloads are refused.
Sandbox
Daytona-backed user sandboxes. Each authenticated user gets one isolated sandbox in their Daytona account (BYOK). Tool calls run inside the container, and a persistent volume mounted at/home/daytona/agent-workspace keeps the user’s workspace across restarts. All routes require Better Auth.
GET /api/sandbox/preview
Return a signed preview URL into the calling user’s active Daytona sandbox.
Responses
- 200 —
{ url }— short-lived preview URL into the sandbox. - 400 —
{ error: "Invalid port" } - 401 —
{ error: "Authentication required" } - 404 —
{ error: "No active sandbox for user" } - 503 —
{ error: "Sandbox not available" }— Daytona is not enabled for the calling user. On Vercel this typically means the user has not saved adaytonaprovider secret; locally it usually meansDAYTONA_API_KEYis unset.
POST /api/webhooks/daytona
Receive lifecycle events from Daytona (sandbox start, stop, archive, delete). Side effects are applied only when thex-daytona-signature header verifies against DAYTONA_WEBHOOK_SECRET; otherwise the call is accepted and logged but ignored.
Headers
x-daytona-signature— HMAC signature of the request body usingDAYTONA_WEBHOOK_SECRET.
- 200 —
{ received: true } - 500 —
{ error: "Webhook processing failed" }
Authentication
Fleet Pi routes follow a deployment-based auth policy:- Local development (no
NEON_AUTH_BASE_URL/NEON_AUTH_URLand not running on Vercel): chat and workspace endpoints allow anonymous access so the app works without login. - Vercel, Neon Managed Auth, or dual-host chat runtime: session, catalog, and workspace routes require authentication. This covers
/api/chat/*streaming and session routes, the catalog endpoints (/api/chat/models,/api/chat/resources,/api/chat/commands,/api/chat/models/discover), and every route under/api/workspace/*. Unauthenticated requests receive401. /api/sandbox/previewalways requires authentication.
authClient.token() and send Authorization: Bearer <jwt> on every request. Better Auth clients use signed session cookies.
/api/auth/*
Proxies Neon Managed Auth when NEON_AUTH_BASE_URL (or the Vercel↔Neon NEON_AUTH_URL) is set. Otherwise it serves Better Auth when BETTER_AUTH_SECRET is set. See configuration and the Neon Managed Auth reference for required environment variables.
Health
GET /api/health
Smoke endpoint used by the quickstart. Responses- 200 —
{ "status": "ok" }
Stream events
/api/chat emits one JSON object per newline. Every event has a type discriminator. The full union is ChatStreamEvent in chat-protocol.ts.
Plan events carry a
ChatPlanState shape:
Related
Chat modes
Which tools each mode unlocks.
Architecture
How requests flow from the browser through the configured model provider and back.