For developers

The same people, inside your own product.

A REST API for the people you build in the console. Start a live, face-to-face conversation from your server, show it in your web app, and hear by webhook when sessions end, leads arrive and results are ready.

Base URL https://app.zentiqai.com/v1

Emma, one of Zentiq's avatars
In your app · Emma

Shown by your app

You're speaking with an AI avatar.

Quick start

A live conversation in three steps.

Step 1

Create a key in the console

Open API Keys in the console and create one. It starts with zq_live_ and is shown once, so keep it on your server. Choose what it may do: a preset such as Website assistant or CRM, full access, or scopes ticked one by one, like sessions:write.

Open API Keys
terminal
# Check the key works: returns your organization and the key's scopescurl https://app.zentiqai.com/v1/me \  -H "Authorization: Bearer $ZENTIQ_API_KEY"

Step 2

Start a live session from your server

POST /v1/sessions/tokens with a personaId. You get back a short-lived sessionToken, the url and room to join, and the persona's name.

It also returns disclosure. Your app shows its text for the whole session: required is always true.

Optional: identity for your own user id, language, and overrides for a different prompt, greeting or {{variables}} in this one session. Minutes are prepaid: when they run out, new sessions don't start until more are added.

server.ts
// On your server. The API key never goes to the browser.const res = await fetch("https://app.zentiqai.com/v1/sessions/tokens", {  method: "POST",  headers: {    Authorization: `Bearer ${process.env.ZENTIQ_API_KEY}`,    "Content-Type": "application/json",  },  body: JSON.stringify({ personaId: "pa_…", identity: "user_42" }),});const { data } = await res.json();// Hand `data` to the browser for step 3
response.json
{  "data": {    "sessionId": "sess_…",    "sessionToken": "…",   // short-lived, for this one session    "url": "…",            // the WebRTC address to join    "room": "…",    "persona": { "id": "pa_…", "name": "Emma" },    "cc": true,    "disclosure": {      "required": true,      "text": "You're speaking with an AI avatar."    }  },  "error": null,  "requestId": "…"}

Step 3

Join from the browser

Our browser SDK, @zentiq/client, joins with the whole response from step 2. It plays the avatar in a video element and gives you the live transcript and who is speaking. With a duo, each line comes with the speaker's name.

Version 1.0 is coming to npm with the 2026-10-01 contract. The session is standard WebRTC, so you can also join the url with the token using your own client.

browser.ts
import { joinSession } from "@zentiq/client";// The `data` your server got in step 2const token = await fetch("/api/zentiq-session", { method: "POST" }).then((r) => r.json());// Show the AI disclosure for the whole sessionif (token.disclosure?.required) banner.textContent = token.disclosure.text;const session = await joinSession(token, {  video: document.querySelector("#avatar"),  onTranscript: (t) => console.log(t.role, t.text, t.final),  onSpeaking: (who) => console.log(who), // "avatar" | "user" | "none"});// Leave. Your server can also call POST /v1/sessions/{id}/endsession.end();

The API

What you can build with the API.

JSON in, JSON out, with prefixed ids and one error format. The full list, with an OpenAPI file, is in the reference.

Personas

The person your users talk to: a face, a voice, a personality and a greeting. Create, change and delete them.

  • /personas
  • /personas/{id}

Avatars

List the built-in people, or bring your own face from an image, with the consent of the person in it.

  • /avatars
  • /avatars/{id}/preview

Voices

Studio voices, or clone a real voice with permission. Any voice speaks every supported language.

  • /voices
  • /languages

Knowledge

Knowledge bases from files, web pages or plain text, with search. Your avatar answers from them.

  • /knowledge/bases
  • /knowledge/bases/{id}/search

Live sessions

Start a session, steer it while it runs, make the avatar say exact words, then end it.

  • /sessions/tokens
  • /sessions/{id}/say

Transcripts and analytics

The turn-by-turn transcript, duration, minutes billed, talk share, and the report's summary and score when there is one.

  • /sessions/{id}/transcript
  • /sessions/{id}/analytics

Webhooks

Signed events sent to your server when sessions start and end, documents finish, and more. Plus a log of every delivery.

  • /webhooks
  • /webhooks/deliveries

Usage and keys

Minutes used and minutes left, and API keys, including keys limited to just the scopes they need.

  • /usage
  • /keys

Portals

Build a whole portal from our templates, choose who speaks on it, publish it, and sign your own users straight in.

  • /portals
  • /portals/{id}/embed-tokens

Setup from one sentence

Describe what the avatar should do and get back the right template, a name and who it's for. Then build it with one call.

  • /setup/plan
  • /portals/templates

Embed a portal

Your backend mints a five-minute, single-use embed token after your own sign-in, and the portal opens in an iframe as that user.

See how in the reference

New in the API

Case links, chat handoffs and more.

Each feature is off until an owner or admin switches it on for the organization, so a key with the right scope isn't enough on its own. Every change is in the changelog.

Case links

Your CRM or helpdesk makes a personal link to one customer's order, claim, appointment or ticket. The avatar greets them about it, and your actions get the customer's id and the case's reference.

  • POST /portals/{id}/case-links
  • GET /portals/{id}/case-links
  • GET /case-links/{id}
  • DELETE /case-links/{id}

cases:write, on a key an owner or admin makes; never through a connected app or MCP.

Chat escalation

Your chat's server sends the chat so far and gets a link to one face-to-face conversation with the website avatar, which opens with a recap. The outcome comes back as escalation.completed.

  • POST /portals/{id}/escalations

escalations:write. Intercom and Zendesk connect in the console instead.

Ask a website avatar

An AI assistant asks a business's public website avatar one question and gets a written answer, marked aiGenerated, with the same rules as a typed conversation on its page.

  • POST /agents/{org}/{door}/ask

No key: only for public pages their business opted in, with limits per caller, page and organization.

Connected systems

Connect Cal.com, HubSpot, Shopify or any MCP server once, and the avatar uses it through its actions. A key goes in once and never comes back out.

  • GET /connections
  • POST /connections
  • POST /connections/{id}/test
  • DELETE /connections/{id}

connections:write; never offered over MCP.

Change a subject

Rename a subject, move it to another unit, or choose the language a website subject speaks with its visitors.

  • PATCH /subjects/{id}

content:write.

US hiring records

A role's candidate requests with their due dates and outcomes, and a candidate's record: the notices they read, their consent for Illinois and their requests.

  • GET /roles/{id}/requests
  • GET /candidates/{id}/compliance

candidates:read. Read-only, and never sent by webhook.

A case link, from your CRM

The address is in this answer only: send it by email or text message straight away. Zentiq keeps a hash of the link, never the link, and the case's details are never in the address. When the customer starts a conversation from it, case_link.opened tells your system.

case-link.ts
// Your CRM, on your server: a key with cases:writeconst res = await fetch(`https://app.zentiqai.com/v1/portals/${portalId}/case-links`, {  method: "POST",  headers: {    Authorization: `Bearer ${process.env.ZENTIQ_CASES_KEY}`,    "Content-Type": "application/json",    "Idempotency-Key": `case-link-${ticket.id}`,  },  body: JSON.stringify({    customerId: "cus_4821", reference: "10423", kind: "order",    firstName: "Sam", summary: "Out for delivery, due Tuesday.",  }),});const { data } = await res.json(); // data.url: send it by email or text

Webhooks

Results come to you.

Register a URL with POST /v1/webhooks and pick events, or "*" for all of them. Every delivery is signed with your secret and retried if your server doesn't answer.

Sessions

  • session.started
  • session.ended

Website visitors

  • lead.captured
  • booking.requested
  • handoff.requested

Voices and knowledge

  • voice.clone.completed
  • knowledge.document.processed
  • knowledge.document.failed

Results

  • report.produced
  • assessment.completed

Hiring (ids and statuses only)

  • candidate.invited
  • candidate.screened
  • candidate.interview_booked
  • candidate.reviewed
  • candidate.decided
  • candidate.erased

Chat handoffs and case links

  • escalation.completed
  • case_link.opened

Connected systems

  • connection.failed

A candidate's events never carry names, contact details, answers, scores or transcripts. The reference lists every event.

session.ended.json
// POST to your URL, with X-Zentiq-Event: session.ended{  "id": "evt_…",  "type": "session.ended",  "apiVersion": "2026-10-01",  "organizationId": "org_…",  "createdAt": "2026-10-01T14:05:12.000Z",  "data": {    "sessionId": "sess_…",    "personaId": "pa_…",    "durationSeconds": 412,    "billedMinutes": 7  }}
verify.ts
// Check it came from us (Node)const expected = "sha256=" + crypto  .createHmac("sha256", WEBHOOK_SECRET)  .update(`${req.headers["x-zentiq-timestamp"]}.${rawBody}`)  .digest("hex");// compare with X-Zentiq-Signature, and reject old timestamps

Always honest

Every avatar is shown as an AI.

Europe's AI Act (Article 50) says people must be told when they are talking to an AI. On Zentiq's own pages and in the website widget, an AI label stays on screen and a notice opens every conversation. In your app, the screen is yours, so every session you start returns the words to show. Asked, the avatar always says it's an AI.

  1. Read it from the session

    Each POST /v1/sessions/tokens response carries disclosure, with the persona's own wording in text.

  2. Show it for the whole session

    Keep the text on screen while the conversation runs. required is always true: no persona can switch the disclosure off.

  3. It's on the record

    What we sent is recorded against the session and comes back in GET /v1/sessions/{id}/analytics.

Build it into your product.

Get early access, create a key, and start your first session today. API sessions use the same prepaid minutes as the console.