p90
DEVELOPER AND OPERATOR DOCUMENTATION

Run p90 with the product contract in view.

This section covers deployment, environment, authentication, health, CORS, Supabase, and incident recovery. Product usage documentation is kept separately in the main guide.

Services and environments

The production frontend and FastAPI backend run on Cloudflare at https://p90.tech and https://api.p90.tech. Keep the backend URL in NEXT_PUBLIC_API_BASE_URL and do not place credentials in public frontend variables.

Local development uses Next.js on port 3000 and FastAPI on port 8000 in this workspace. SQLite is acceptable for local work. Production uses the Supabase Postgres connection configured on the backend host.

# frontend/.env.local
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_PUBLISHABLE_KEY

Health and incident checks

Start with the backend health endpoint, then check the metrics history endpoint used by the overview chart. A successful health response does not prove that the workspace database, authentication, CORS, or metrics source is healthy.

When Supabase has been paused, resume it and allow the database and authentication services to become available before rotating credentials or changing application code. Check the browser network request after the service resumes.

Invoke-WebRequest https://YOUR_BACKEND_HOST/health
Invoke-RestMethod "https://YOUR_BACKEND_HOST/api/metrics/history?hours=24"
Invoke-WebRequest https://p90.tech/dashboard

CORS and browser access

Production CORS must list exact origins. The backend rejects wildcard origins when credentials are enabled. Add the deployed frontend origin to CORS_ALLOWED_ORIGINS and restart the backend after changing it.

A direct API request can return 200 while browser requests fail. Verify the preflight response includes the requested origin and permits Authorization, Content-Type, X-Workspace-ID, and X-P90-Key.

$headers = @{
  Origin = "https://p90.tech"
  "Access-Control-Request-Method" = "GET"
  "Access-Control-Request-Headers" = "authorization,x-workspace-id"
}
Invoke-WebRequest "https://YOUR_BACKEND_HOST/api/metrics/latest" -Headers $headers

Supabase configuration

The backend verifies Supabase access tokens through the project JWKS endpoint when SUPABASE_URL is configured. SUPABASE_JWT_SECRET remains a legacy HS256 fallback. Production startup rejects missing authentication configuration.

The frontend only receives NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. Never put a service-role key, database password, integration secret, ingestion key, or MCP key in a NEXT_PUBLIC variable.

SUPABASE_URL=https://YOUR_PROJECT.supabase.co
SUPABASE_JWT_AUD=authenticated
DATABASE_URL=postgresql://...
DATABASE_SSL_ROOT_CERT=/opt/render/project/src/backend/certs/supabase-pooler-root-2021.pem

Authentication and workspace selection

Browser dashboard requests use a Supabase session in Authorization: Bearer <access-token> and the active workspace in X-Workspace-ID. The header selects a workspace; it does not grant access. The backend still checks the user's organization membership and role.

SDK ingestion is different. It uses x-p90-key and requires workspace_id in the JSON body. It is intended for trusted server-side product code and does not accept a browser session as a substitute for the ingestion key.

Service API keys are workspace-scoped bearer keys. The backend resolves their workspace from the key, so service-key requests should omit X-Workspace-ID. Scopes are api:read, api:write, mcp:read, mcp:operate, and mcp:configure.

MCP uses the same workspace-scoped service key model. Read access requires mcp:read. Monitoring and recommendation operations require mcp:operate and explicit confirmation. Cost-model updates require the separate mcp:configure scope and confirm=true. The older signed assertion path is retained only for configured internal compatibility.

# Browser session
curl "$P90_API_BASE_URL/api/metrics/latest"   -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"   -H "X-Workspace-ID: $P90_WORKSPACE_ID"

# SDK ingestion key
curl -X POST "$P90_API_BASE_URL/api/sdk/telemetry"   -H "x-p90-key: $P90_INGESTION_KEY"   -H "Content-Type: application/json"   -d '{"workspace_id":"workspace-id","event_type":"model_call","source":"product-gateway","reported_cost_cents":0.42}'

# Service or MCP key, workspace is resolved from the key
curl "$P90_API_BASE_URL/api/metrics/summary"   -H "Authorization: Bearer $P90_SERVICE_KEY"

Deployment recovery

Frontend deployment is verified by building from the frontend project root and checking the production routes. Backend deployment must apply pending versioned migrations before the service enters external migration mode.

If the dashboard reports that the API failed, capture the browser request URL, status code, response body, backend health response, CORS response, and the current workspace id. This evidence distinguishes a frontend configuration error from a paused database, expired session, missing workspace, or backend deployment problem.

cd frontend
npm run typecheck
npm test -- --runInBand
npm run build
vercel deploy --prod --yes