API
Changelog
What changed for integrators in each release of the chat API, breaking changes first.
Newest first. Each entry is one merged change, named by its pull request. Breaking changes come first in each entry.
Breaking changes at a glance
| Since | Change | What to do |
|---|---|---|
| #64 | An anonymous /v1 call no longer gets a confirm_action tool call | Send a member session (X-PlanCover-Session or metadata.session_id) |
| #54 | Every route except /health/* needs an API key | Get a key; send it as Authorization: Bearer pcai_… |
| #54 | The member session moved from Authorization to X-PlanCover-Session | Send the session in the new header |
| #54 | Refused chat turns are HTTP statuses, not error events on a 200 | Check the status before reading the stream |
| #51 | /docs, /redoc and /openapi.json are gone outside dev | Use these docs, or dev |
2026-10-29
Anonymous callers get confirmations as text
PR #64.
- Breaking.
/v1/chat/completionsoffersconfirm_actiononly to a request with a member session, as it already did forsign_in. An anonymous caller is a new member on every request, so it could never own the confirmation it answered: its answer was always a403. It now gets the text fallback, withfinish_reason: "stop", andplancover.confirmationstill set.
Hyphenated confirmation ids
PR #61.
- Fixed.
POST /chat/confirmwith aconfirmation_idin the hyphenated UUID form, as most UUID libraries print it, was refused with a403, and the member's own pending confirmation was marked superseded. Any spelling of the same UUID now confirms the same request./v1/chat/completionswas not affected.
Usage, cost and the admin API
PR #60.
- Changed.
usagenow adds up every model call in a turn: the router, the tool-calling model, the answer and any fallback (not the reranker). Figures are higher than before for the same turn. It is the same figure on/v1and in the nativemessage.completed.usage. - Changed. PlanCover can now change the models from the
admin portal without a deploy, from the next
turn. On the native stream,
message.started.modelnames the answer model configured for that turn./v1still echoes themodelyour request sent. - New. The admin API (
/admin/*) for PlanCover's own admin portal. It takes onlyadminkeys; aclientkey gets403 permission_denied.
Human in the loop on /v1
PR #58.
- New. A
/v1turn that pauses for the member's approval or sign-in arrives as a call to your ownconfirm_actionorsign_intool, withfinish_reason: "tool_calls". You answer with atoolmessage on the same conversation. See Confirmations and sign-in. - New. A call arrives only when your request declares the tool and names
the conversation (
metadata.conversation_idorX-Conversation-Id);sign_inalso needs a member session.tool_choice: "none"suppresses it. Otherwise you get the text fallback. - New. Every refused answer, whether not yours, expired, already answered
or unknown, is the same
403 permission_deniedwith the same message.
OpenAI-compatible Chat Completions
PR #57.
- New.
POST /v1/chat/completionsspeaks OpenAI's Chat Completions protocol, streaming and not, so OpenAI SDKs, the Vercel AI SDK and LangChain can call the assistant. Start with the Quickstart. - New. Errors on
/v1/*use OpenAI's envelope,{"error": {"message", "type", "param", "code"}}. Every other/v1path,/v1/responsesand/v1/modelsincluded, is404 unsupported_endpoint. - Fixed. The native
message.completed.usagecounted the answer model twice. It now counts each call once, so its figures drop.
API keys
PR #54.
- Breaking. Every route except
/health/*needs an API key:Authorization: Bearer pcai_<live|test>_…. Apcai_live_key works only inprod, apcai_test_key only indevandstaging. A missing, unknown, expired, revoked or wrong-environment key is401 invalid_api_key. - Breaking. The member session moved to
X-PlanCover-Session. A session id sent as the bearer is refused as an invalid key. - Breaking. Every check runs before the stream opens, so a refused turn is
a real
401,403,429or503with the error envelope, never anerrorevent on a200. - New.
/chat/streamwithout a session runs anonymously: knowledge questions only, with no continuity. - New. Each key has a per-minute limit, 120 by default. Every
429, the key's, the member's and sign-in's, carriesRetry-Afterandretry-after-ms.
2026-10-28
Production hardening
PR #51.
- Breaking. The interactive API docs (
/docs,/redoc) and/openapi.jsonexist only indev. - Changed. CORS no longer allows credentials. Callers authenticate with headers, never cookies.
- New. A whole turn is capped at
TURN_TIMEOUT_SECONDS(120 by default). A turn past it ends in oneupstream_timeouterror. It is retryable, except after a confirmation, where the approved action may already have run: check before retrying. - Changed. When PlanCover rejects a member's stored sign-in, the chat API forgets it at once. The member reads as signed out on the next request and is asked to sign in again.