Quickstart
By the end of this page a person on your website talks to a Zentiq avatar, face to face. Your server starts the session with your API key; the browser joins with a short-lived token and never sees the key.
1. Make an API key#
In the Zentiq console, open More → API Keys and choose Create a key. Give it a name ("Website", "Production server") and choose what it may do: for this page, the Full access preset, or Choose scopes with personas:read and sessions:write. The key starts with zq_live_ and is shown once. Keep it on your server, in an environment variable:
export ZENTIQ_API_KEY=zq_live_…
curl https://app.zentiqai.com/v1/me -H "Authorization: Bearer $ZENTIQ_API_KEY"GET /v1/me answers with your organisation, its plan and what the key may do (apiKey.effectiveScopes).
2. Choose a persona#
A persona is the avatar people talk to: a face, a voice, what it knows and how it behaves. Make one in the console (Avatars), or with the API:
curl -X POST https://app.zentiqai.com/v1/personas \
-H "Authorization: Bearer $ZENTIQ_API_KEY" -H "Content-Type: application/json" \
-d '{ "name": "Ava", "systemPrompt": "You are Ava, a friendly product specialist for Acme. Keep answers short.", "greeting": "Hi, I am Ava. Ask me anything about Acme." }'The answer's data.id (pa_…) is the persona's id. GET /v1/personas lists the ones you have.
3. Start a session on your server#
npm install @zentiq/node@^1Coming to npm
@zentiq/node 1.0.0 is being released with the 2026-10-01 contract. npm has 0.2.0, an earlier version for the 2026-07-08 contract. The command above installs 1.0.0 or a later 1.x only, so it works as soon as 1.0.0 is out.import { Zentiq, ZentiqError } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });async function startSession(userId: string) {
const token = await zentiq.createSessionToken(
{ personaId: process.env.ZENTIQ_PERSONA_ID ?? "", identity: userId },
{ idempotencyKey: `start-${userId}-${Date.now()}` },
);
// { sessionId, sessionToken, url, room, persona, disclosure, cc }: hand all of it to the browser.
return token;
}createSessionToken (POST /v1/sessions/tokens) starts the session and answers with:
| Field | What it is |
|---|---|
sessionId | The session (sess_…): end it, steer it or read it later. |
sessionToken, url | What the browser joins with. The token is short-lived and works for this session only. |
persona | The persona's id and name, as the avatar says it. |
disclosure | The AI disclosure: while required is true, show text for the whole session. |
cc | Whether the persona shows captions by default. |
Starting a session can take a while (up to a minute) while an avatar is made ready: give the request a long timeout (the SDK does). Minutes count while the session is live.
4. Join in the browser#
npm install @zentiq/client@^1Coming to npm
@zentiq/client 1.0.0 is being released with the 2026-10-01 contract. npm has 0.2.0, an earlier version. The command above installs 1.0.0 or a later 1.x only, so it works as soon as 1.0.0 is out.import { joinSession, type SessionToken } from "@zentiq/client";
const token = (await fetch("/zentiq/session", { method: "POST" }).then((r) => r.json())) as SessionToken;
// People must know they are talking to an AI: show the persona's words for the whole session.
const disclosure = document.querySelector<HTMLElement>("#disclosure");
if (disclosure && token.disclosure?.required) disclosure.textContent = token.disclosure.text;
const session = await joinSession(token, {
video: document.querySelector<HTMLVideoElement>("#avatar")!,
onTranscript: (t) => {
if (t.final) console.log(t.role === "user" ? "You" : t.speaker ?? token.persona.name, t.text);
},
onSpeaking: (who) => document.body.setAttribute("data-speaking", who),
});People must know they are talking to an AI: whenever disclosure.required is true, keep disclosure.text on the screen for the whole session.
5. End the session#
document.querySelector("#end")?.addEventListener("click", async () => {
session.end(); // leaves the room
// …and your server ends the session, so its minutes stop now.
await fetch("/zentiq/end", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId: token.sessionId }) });
});async function endSession(sessionId: string) {
// Minutes stop as soon as the session ends. Leaving the room in the browser is not enough on its own.
await zentiq.endSession(sessionId);
}session.end() in the browser only leaves the room. End the session from your server (POST /v1/sessions/{id}/end) and its minutes stop at once. If a browser simply goes away, Zentiq's clean-up ends the session once it finds the room empty, a few minutes later.
Next#
- Keep the key's scopes to what the integration needs: Keys and scopes.
- Change what the avatar is told mid-session, make it say exact words, or answer every turn from your own model: Live sessions.
- Get told when sessions end: Webhooks.