Skip to the content
Menu: Quickstart

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:

Shell
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:

Shell
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#

Install
npm install @zentiq/node@^1

Coming 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.
server.ts
import { Zentiq, ZentiqError } from "@zentiq/node";

const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
server.ts
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:

FieldWhat it is
sessionIdThe session (sess_…): end it, steer it or read it later.
sessionToken, urlWhat the browser joins with. The token is short-lived and works for this session only.
personaThe persona's id and name, as the avatar says it.
disclosureThe AI disclosure: while required is true, show text for the whole session.
ccWhether 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#

Install
npm install @zentiq/client@^1

Coming 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.
browser.ts
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#

browser.ts
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 }) });
});
server.ts
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.