Moving to 2026-10-01
Keys made from 2026-10-01 are on the new contract already: nothing to do. This page is for integrations whose keys were made before, on 2026-07-08. They keep working as they are: move when you are ready, within the support window (see Versions).
The privacy and security changes of the same release apply to every contract, whether you move or not: see the changelog.
How to move#
- Read what changes below, and find the code that relies on it.
- Try it: send
Zentiq-Version: 2026-10-01on your requests (an SDK 1.x does this by default). The answers come in the new contract; your key stays pinned to the old one. - Move your webhooks:
PATCH /v1/webhooks/{id}with{ "apiVersion": "2026-10-01" }. Deliveries switch to the new envelope from the next event. Update your receiver first. - Make a new key in the console (with only the scopes it needs), switch your servers to it, then revoke the old one.
What changes#
| Area | On 2026-07-08 | On 2026-10-01 | What to change |
|---|---|---|---|
| Plan refusals | 403 { "error": "upgrade_required", "feature", "plan", "message", "limit"? } | 403 { "error": { "code": "upgrade_required", "message", "details": { "feature", "plan", "limit"? } } } | Read error.code and error.details. |
| Portal and subject ids | Bare ids in answers and events | portal_… and sub_… (bare ids still accepted on input) | Store the prefixed ids; don't parse them. |
| A refused portal publish | 200 with the draft unchanged and publishRefused | 422 go_live_refused with details.problems | Handle the error. (Creating with publish: true stays 201 with publishRefused.) |
| Who speaks on a portal | Face and voice by first name | By id (avatarId, voiceId); names still accepted, but deprecated | Send ids. |
| Webhook envelope | { id, type, createdAt, data } | Adds apiVersion and organizationId; every id in data prefixed; visitor details on leads, bookings and hand-offs under contact, leadScore renamed leadBand, inboxItemId added | Update your receiver, then move the webhook. |
| Scopes | sessions:read includes transcripts; portals:write includes embed tokens and publishing; personas:* includes duos and widgets; webhooks:manage includes the event log | Each is its own scope (transcripts:read, embed:write, portals:publish, duos:*, widgets:*, content:*, events:read) | Give new keys the scopes they use. Old keys keep what they had. |
| Full-access keys | Never reach candidates, people, the inbox or consent records | A new full-access key reaches them all, except candidates:data and consents:* (added by name) | Nothing, unless you want an old key to reach them: an owner adds them by name. |
| Persona voice tier | settings.engine and settings.globalVoice can be written | Set from the voice, read-only | Choose the voice (voiceId); stop sending engine. |
| Refused transcripts | 403 forbidden | 403 candidate_interview or 403 learner_conversation | Match on the new codes. |
| Too large | 413 invalid_request | 413 too_large | Match on the new code. |
Summaries on getSession and sessionAnalytics | With sessions:read | Need transcripts:read | Add the scope to keys that read summaries. |
The SDKs#
SDK 1.x speaks 2026-10-01, and reads both contracts' error shapes. Moving from @zentiq/node 0.x:
- Every operation is now a method named after its operation id:
personas.create(…)iscreatePersona(…),sessions.createToken(…)iscreateSessionToken(…),knowledge.documents.list(id)islistDocuments(id). The reference shows each one. - An
Idempotency-Keyis sent for you on every operation that takes one (the reference marks them); pass your own as{ idempotencyKey }in the last argument. - Errors are typed by kind (
GoLiveRefusedError,VersionConflictError, …), still allZentiqErrors.
Each package's own changelog lists the rest.