Skip to content

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.

EndpointReturns
GET /api/dashboardHTML admin dashboard
GET /api/cost?window=24h|7d&bucketHours=NTime-bucketed cost data
GET /api/sessions?sortBy=cost&limit=NTop sessions list
GET /api/sessions/search?q=...Full-text search across messages
GET /api/sessions/{id}/transcriptFull transcript for one session
POST /api/ops/rotate-keyGenerate + return new admin key
GET /api/teams-app.zipPer-deployment Teams app .zip download
GET /api/teams-setupTeams 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:

{ "oid": "...", "upn": "[email protected]", "email": "...", "tenantId": "..." }

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:

  • statusopen, human_requested, claimed, closed
  • claimedBy — agent oid, or me to filter to own claimed sessions
  • limit — 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 + quotaStatus block 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.