API stability
Semver and deprecation policy for the stable /api/v1 REST surface.
ChatAI v1.0 freezes the public HTTP API under /api/v1. The OpenAPI document at GET /api/v1/openapi.json is the contract of record.
Stable surface
| Area | Routes | Auth |
|---|---|---|
| Chat | POST /api/v1/chat | Bearer key or keyless widget/playground |
| Voice | POST /api/v1/voice/sessions, POST /api/v1/voice/sessions/{sessionId}/end, POST /api/v1/voice/sessions/{sessionId}/heartbeat, POST /api/v1/voice/sessions/{sessionId}/gate | Bearer key (voice scope) or keyless widget/playground (same SecurityPolicy as chat); heartbeat and gate: session controlToken only |
| Owner REST | /api/v1/assistants, documents, conversations, analytics | Bearer sk_live_… |
| Widget | GET …/config, POST /api/v1/feedback, POST /api/v1/widget/sign | Keyless (domain allowlist + rate limits) |
Voice recording is additive: POST /api/v1/voice/sessions accepts an optional recordingConsent and returns recording. When a session would be recorded and consent is required (always for widget/playground, and for API keys when the assistant requires it), a request without recordingConsent: true is rejected with 400. Recording playback is dashboard-only.
Voice minutes are additive. POST /api/v1/voice/sessions can return 402 (reason: "voice_minutes_exhausted") when the account's Voice minutes for the period are used up, and 429 with reason: "voice_concurrency_limit" when too many Voice sessions are active (a plain 429 without reason is still the request rate limit). POST /api/v1/voice/sessions/{sessionId}/end may return endReason: usage_limit when ChatAI closed the call at the end of the account's allowance, superseded when a newer widget session from the same visitor replaced it, otherwise null. Widget (keyless, public) callers never receive usage reasons: mint refusals are 403 with reason: "voice_unavailable", and a usage-limit end is reported as endReason: "voice_unavailable". POST /api/v1/chat accepts an optional voiceUnavailable: true, which the widget sends after such an end so the assistant can answer "why did the voice end?" neutrally; it carries no reason and is not stored. After ChatAI ends a session, the end endpoint still answers for a short time with the final result. The dashboard usage summary (GET /api/v1/account/usage/summary, dashboard session only, not in the OpenAPI spec) includes a voice object with seconds used, limit, reserved and Playground seconds.
Since 1.2.0 every Voice client must declare the playback_gate capability: ChatAI's backend, not the voice model, decides which assistant speech is heard. The client streams decisions from POST /api/v1/voice/sessions/{sessionId}/gate (NDJSON, { token } body) and keeps assistant audio and captions muted unless the gate is open. A mint without playback_gate is refused with 403 and reason: "client_update_required" (since 1.3.0; 1.2.0 answered reason: "voice_unavailable"). The widget and the dashboard Playground declare capabilities: ["heartbeat", "playback_gate"] (exported by @nightzeros/chatai-widget-core as VOICE_CLIENT_CAPABILITIES).
Voice call supervision is additive. A mint request may also declare heartbeat; the response then includes a session-scoped controlToken and heartbeatIntervalMs, and the client posts { token } to POST /api/v1/voice/sessions/{sessionId}/heartbeat. The heartbeat answers healthy, degraded (ChatAI is reconnecting to the provider; heartbeat every nextHeartbeatMs), ended or lost; 421 means the request reached a different server than the one running the call. Only clients that declare the capability are ended when heartbeats stop (after 45 s). endReason gains idle, max_duration, heartbeat_lost and control_lost; widget callers see heartbeat_lost and control_lost as disconnected. The browser's data channel cannot send control events to the provider session; if the provider does not confirm this restriction, the mint fails with 502 and reason: "voice_unavailable".
Dashboard-only routes under /api/assistants/* (uploads, evals, sources, Voice recording playback) are not part of the v1 contract. Integrations should use /api/v1/* only.
Semver rules (API)
We follow Semantic Versioning for product releases. For the HTTP API:
| Release | Allowed changes on /api/v1 |
|---|---|
| Patch (1.x.y) | Bug fixes, documentation, additive optional response fields |
| Minor (1.x.0) | Additive endpoints, optional request fields, new enum values |
| Major (2.0.0) | Breaking request/response shapes, removed routes, auth changes |
Breaking changes ship only under a new prefix (e.g. /api/v2) while /api/v1 remains available for at least one major cycle with a published sunset date.
Deprecation process
- Mark the field or route deprecated in OpenAPI and docs (minimum 90 days notice).
- Emit a
Deprecationresponse header on affected routes when feasible. - Remove only in the next major product release.
Contract tests
The monorepo stores a frozen fingerprint of all documented operations in @nightzeros/chatai-sdk (openapi.fingerprint.json). CI fails if routes are added, removed, or renamed without updating the fingerprint intentionally.
Regenerate after an approved API change:
pnpm --filter @nightzeros/chatai-sdk openapi:fingerprint