API reference
import { Tabs, TabItem } from “@astrojs/starlight/components”;
All endpoints live on your customer Function App at https://pocai-fa-{stack-hash}.azurewebsites.net/api/. Authentication varies by endpoint — see each entry.
Anonymous endpoints
GET /api/health
Liveness probe + minimal deployment metadata.
{ "ok": true, "deploymentId": "pocai-...", "marketplaceSubscriptionId": "...", "version": "phase24-synctrigger", "startedAt": "2026-...", "bootstrap": { "attempted": true, "result": "succeeded" }, "seat": { "activeSessionCount": 0, "lastReportedQuantity": 0 }, "tokens": { ... }}GET /api/plan
Current plan + seat quota + grace state. Used by the dashboard JS.
{ "planId": "growth", "planDisplayName": "Growth", "seatQuota": 7, "seatsUsed": 4, "exceeded": false, "daysOverQuota": 0, "gracePeriodDays": 7, "blocked": false, "upgradeUrl": "https://portal.azure.com/..."}POST /api/chat
Visitor sends a chat message (non-streaming variant; the widget itself uses /api/chat/stream below). Anonymous — no bearer token is required or validated. Browser access is limited by the allowedOrigins CORS allowlist (App Service platform CORS + per-response Access-Control-Allow-Origin header) when configured — the wizard default is * (open embedding) — and CORS does not block non-browser clients. Requests are additionally rate-limited per client IP and per session (429 + Retry-After when exceeded).
Body:
{ "message": "Hi, I need help with...", "sessionId": "<uuid optional>" }When the session is bot-handled: returns 200 with the bot’s reply. When the session has been claimed by an agent: returns 202 queued-for-agent and broadcasts the visitor message to the agent via SignalR.
POST /api/chat/stream
Streaming variant of /api/chat — this is the endpoint the embedded widget actually calls. Same auth model (anonymous + CORS + rate limits) and the same request body. The response is text/event-stream (SSE): a session event first (carries the sessionId for fresh sessions), then many delta events as the model emits content, one complete event with fullContent + token usage, and a data: [DONE] terminator. Mid-stream errors surface as an error event.
GET /api/messages?sessionId=<uuid>
Returns the stored message list for one session. Anonymous + CORS, same model as the chat endpoints. Anyone holding a sessionId can read that session’s messages — session ids are unguessable UUIDs, so don’t log or share them.
POST /api/negotiate
SignalR negotiate for visitors. Returns connection info bound to userId = sessionId. Anonymous; the widget posts here.
Admin-key gated endpoints
All require ?code=<admin-key> query param, Authorization: Bearer <key> header, OR a valid session cookie.
| Endpoint | Returns |
|---|---|
GET /api/dashboard | HTML admin dashboard |
GET /api/cost?window=24h|7d&bucketHours=N | Time-bucketed cost data |
GET /api/sessions?sortBy=cost&limit=N | Top sessions list |
GET /api/sessions/search?q=... | Full-text search across messages |
GET /api/sessions/{id}/transcript | Full transcript for one session |
POST /api/ops/rotate-key | Generate + return new admin key |
GET /api/teams-app.zip | Per-deployment Teams app .zip download |
GET /api/teams-setup | Teams setup wizard HTML |
Agent endpoints (Teams SSO gated)
All require Authorization: Bearer <Teams-SSO-token>. The customer-runtime validates the JWT against your AAD app (AAD_APP_CLIENT_ID + AAD_APP_TENANT_ID).
GET /api/agent/me
Returns the authenticated agent’s normalized claims:
POST /api/agent/negotiate
SignalR negotiate for agents. Returns connection info bound to userId = agent.oid.
GET /api/agent/sessions[?status=&claimedBy=&limit=]
Lists chat sessions visible to agents. Filters:
status—open,human_requested,claimed,closedclaimedBy— agent oid, ormeto filter to own claimed sessionslimit— 1-200, default 50
POST /api/agent/sessions/{id}/claim
Claim a session. Seat-gated:
- Under quota → 200 with the updated session
- Grace period → 200 +
quotaStatusblock in response body - Post-grace + new agent → 403
{ error: "seat-quota-exceeded", upgradeUrl, ... } - Already claimed by another agent → 409
- Already claimed by caller → 200
{ alreadyClaimed: true }(idempotent)
POST /api/agent/sessions/{id}/message
Send a message from the owning agent. Body: { "content": "string (1-4000 chars)" }. Broadcasts to the visitor via SignalR.
- Not the owning agent → 403
- Session closed → 409
POST /api/agent/sessions/{id}/close
Close a session. Owning agent only.
ISV control plane endpoints
These live on the ISV side at https://pocai-isv-cp.azurewebsites.net/api/ and are mostly invoked by Microsoft Marketplace, Microsoft Graph, or our own orchestrator. The public ones:
GET /api/plans— list all plans (Start / Growth / Professional)GET /api/plans/{planId}— single plan details
Both are anonymous with 5-minute cache.