PlanCover Docs
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

SinceChangeWhat to do
#64An anonymous /v1 call no longer gets a confirm_action tool callSend a member session (X-PlanCover-Session or metadata.session_id)
#54Every route except /health/* needs an API keyGet a key; send it as Authorization: Bearer pcai_…
#54The member session moved from Authorization to X-PlanCover-SessionSend the session in the new header
#54Refused chat turns are HTTP statuses, not error events on a 200Check the status before reading the stream
#51/docs, /redoc and /openapi.json are gone outside devUse these docs, or dev

2026-10-29

Anonymous callers get confirmations as text

PR #64.

  • Breaking. /v1/chat/completions offers confirm_action only to a request with a member session, as it already did for sign_in. An anonymous caller is a new member on every request, so it could never own the confirmation it answered: its answer was always a 403. It now gets the text fallback, with finish_reason: "stop", and plancover.confirmation still set.

Hyphenated confirmation ids

PR #61.

  • Fixed. POST /chat/confirm with a confirmation_id in the hyphenated UUID form, as most UUID libraries print it, was refused with a 403, and the member's own pending confirmation was marked superseded. Any spelling of the same UUID now confirms the same request. /v1/chat/completions was not affected.

Usage, cost and the admin API

PR #60.

  • Changed. usage now 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 /v1 and in the native message.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.model names the answer model configured for that turn. /v1 still echoes the model your request sent.
  • New. The admin API (/admin/*) for PlanCover's own admin portal. It takes only admin keys; a client key gets 403 permission_denied.

Human in the loop on /v1

PR #58.

  • New. A /v1 turn that pauses for the member's approval or sign-in arrives as a call to your own confirm_action or sign_in tool, with finish_reason: "tool_calls". You answer with a tool message 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_id or X-Conversation-Id); sign_in also 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_denied with the same message.

OpenAI-compatible Chat Completions

PR #57.

  • New. POST /v1/chat/completions speaks 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 /v1 path, /v1/responses and /v1/models included, is 404 unsupported_endpoint.
  • Fixed. The native message.completed.usage counted 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>_…. A pcai_live_ key works only in prod, a pcai_test_ key only in dev and staging. A missing, unknown, expired, revoked or wrong-environment key is 401 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, 429 or 503 with the error envelope, never an error event on a 200.
  • New. /chat/stream without 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, carries Retry-After and retry-after-ms.

2026-10-28

Production hardening

PR #51.

  • Breaking. The interactive API docs (/docs, /redoc) and /openapi.json exist only in dev.
  • 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 one upstream_timeout error. 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.

On this page