Sessions
Live conversations.
/v1/sessionsList sessions
Newest first by start. Every session of the organisation by default; source=api lists only the ones started through the API. A candidate's interview and a child's session are never listed, so a page can hold fewer than limit items and still have a nextCursor.
Query parameters
limitintegerPage size, 1-200 (default 50).
cursorstringThe
nextCursorof the previous page.status"ACTIVE" | "COMPLETED" | "ABANDONED"Only sessions in this status.
source"api" | "portal" | "visitor" | "playground" | "demo" | "guide" | "all"Which sessions: every one (
all, the default), those your API keys started (api), or one kind. A job interview (a candidate's, or a member's mock interview), a child's session and a builder's preview are never listed.personaIdstringOnly this persona's sessions (
pa_…).subjectIdstringOnly sessions in this subject (
sub_…).portalIdstringOnly sessions in this portal's subjects (
portal_…).personIdstringOnly this member's sessions (
usr_…).fromstringStarted at or after this moment (ISO 8601).
tostringStarted before this moment (ISO 8601).
Returns
200 A page of sessions. A page: data is a list of Session, and nextCursor fetches the next page.
Fields of data
idstringrequiredSession id (
sess_…).pattern `^sess_.*`
personaIdstring | nullrequiredThe persona that spoke (
pa_…).status"ACTIVE" | "COMPLETED" | "ABANDONED"requiredACTIVE while live; COMPLETED or ABANDONED once ended.
sourcestringrequiredWhere it started: api, portal, visitor, playground, demo or guide.
turnModestring | nullrequiredexternalwhen your app owns every avatar turn (autoRespond false); null otherwise.autoRespondbooleanrequiredFalse when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).
roomstring | nullrequiredThe realtime room, while it has one.
minutesBillednumber | nullrequiredMinutes charged once the session ended; null while live.
startedAtstring (date-time)requiredISO 8601 UTC timestamp.
endedAtstring (date-time) | nullrequireddurationinteger | nullrequiredSeconds, once ended.
createdAtstring (date-time)requiredISO 8601 UTC timestamp.
modestringrequiredAVATAR (a talking avatar), VOICE_CALL or CHAT (typed).
subjectIdstring | nullrequiredThe portal subject it ran in (
sub_…; bare for keys on 2026-07-08); null for a persona session.kind"persona" | "subject" | "visitor"requiredA persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
responseTimesResponseTimes | nullrequiredHow fast the avatar started answering, once the session ended; null when it wasn't measured.
Errors
invalid_requestunauthorizedforbiddenupgrade_requiredrate_limitedserver_error
Request
curl "https://app.zentiqai.com/v1/sessions?limit=20" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const page = await zentiq.listSessions({
limit: 20,
});import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
page = zentiq.list_sessions(limit=20)Example answer
{
"data": [
{
"id": "sess_cmfq2b7x10000abcd1234",
"personaId": "pa_cmfq2b7x10000abcd1234",
"status": "ACTIVE",
"source": "api",
"turnMode": "…",
"autoRespond": true
}
],
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d",
"nextCursor": null
}/v1/sessions/tokensStart a live session (mint a client token)
Starts the session exactly as the console does (the same avatar, voice, engine, look, framing, accent and language), with this session's own prompt, greeting and {{variables}} on top. The response's disclosure is the AI disclosure (EU AI Act Article 50), and showing it is required: keep disclosure.label visible on or right next to the avatar's video for the whole session and show disclosure.text when it starts (required is always true; the browser and React SDKs draw the label for you). The avatar also says it's an AI in its opening line. What was sent is recorded against the session and returned by GET /sessions/{id}/analytics. Minutes are checked before the session starts and charged on Zentiq's clock until it ends. When the consent record for the persona's own face or voice can't be checked just now, nothing starts (503 consent_unverified): try again shortly. A persona whose face is new or was just changed answers 409 face_not_ready for about a minute while the live engine prepares it (a session started before then drifts at the opening): retry after Retry-After.
Body
personaIdstringrequiredThe persona to start (
pa_…).identitystring | nullYour end user's id (optional): the participant identity in the room.
languagestring | nullPer-session spoken-language override: one of GET /languages, as a code or the name.
languageMode"follow" | "fixed" | nullHow this session keeps its language:
followswitches to the person's language when they use another;fixedanswers in the session's language anyway. Default: the persona's own setting.overridesobject | nullsystemPromptstring | nullReplaces the persona's prompt for this session (up to 16,000 characters).
greetingstring | nullReplaces the greeting (up to 2,000 characters); an empty string means no greeting.
variablesmap of any | null
autoRespondboolean | nullfalse: your app owns every avatar turn; the avatar only speaks the greeting and the lines you send with say.
turnModeobject | null{ mode: "external" }is the same as autoRespond false.modestring
Returns
200 The session and the token your app joins it with. data is SessionToken.
Fields of data
sessionIdstringrequiredSession id (
sess_…).pattern `^sess_.*`
sessionTokenstringrequiredShort-lived client token (15 minutes) for the realtime room.
urlstringrequiredThe realtime transport URL.
roomstringrequiredpersonaobjectrequiredidstringrequiredPersona id (
pa_…).pattern `^pa_.*`
namestringrequiredThe name the avatar hears. Only a duo's carries " & " ("Mark & Susan"); a single persona named "Smith & Jones" comes back as "Smith and Jones".
disclosureobjectrequiredThe AI disclosure (EU AI Act Article 50): keep
labelvisible on or next to the avatar's video for the whole session and showtextat its start. Recorded against the session.requiredbooleanrequiredAlways true: the AI disclosure can't be switched off.
textstringrequiredThe notice to show when the session starts, in the session's language (the persona's own wording when it has one).
labelstringrequiredThe short label to keep on or right next to the avatar's video for the whole session, in the session's language (the persona's own wording when it has one). The browser and React SDKs draw it for you.
languagestringrequiredThe language of
labelandtext, as a code.display"whole_session"requiredWhen the label shows: for the whole session.
rulestringrequiredWhat your app must do, in plain words: keep the label visible for the whole session and show the notice at the start. You may restyle or move it, never remove, hide or cover it.
ownLabelbooleanrequiredTrue only when an owner of your organisation has confirmed, in the console, that your app shows its own AI label for the whole of every session. Only then may the SDKs' own label be hidden.
ccbooleanrequiredThe persona's caption preference. The transcript always streams on the data topic "chat"; cc is a render hint.
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
Errors
invalid_requestunauthorizedinsufficient_minutesforbiddenupgrade_requiredfeature_not_in_planaccount_pausednot_foundduo_not_liveface_not_readyconsent_withdrawnidempotency_conflictidempotency_in_progressrate_limitedconcurrency_limitserver_errorupstream_errorcapacityconsent_unverifiedupstream_timeout
Events
Request
curl -X POST "https://app.zentiqai.com/v1/sessions/tokens" \
-H "Authorization: Bearer $ZENTIQ_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"personaId": "pa_cmfq2b7x10000abcd1234"
}'import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.createSessionToken({
personaId: "pa_cmfq2b7x10000abcd1234",
});import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.create_session_token({
"personaId": "pa_cmfq2b7x10000abcd1234",
})Example answer
{
"data": {
"sessionId": "sess_cmfq2b7x10000abcd1234",
"sessionToken": "…",
"url": "https://www.example.com",
"room": "…",
"persona": {
"id": "pa_cmfq2b7x10000abcd1234",
"name": "Zoe"
},
"disclosure": {
"required": true,
"text": "You're speaking with an AI avatar.",
"label": "AI avatar",
"language": "en",
"display": "whole_session",
"rule": "…"
}
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/end-idleReclaim this org's dead/idle sessions
Closes this organisation's sessions whose room is gone or that ran past the longest session allowed, freeing their places at once.
Returns
200 How many were closed. data is object.
Fields of data
reapedintegerrequiredHow many sessions were closed.
Errors
invalid_requestunauthorizedforbiddenupgrade_requiredrate_limitedserver_error
Events
Request
curl -X POST "https://app.zentiqai.com/v1/sessions/end-idle" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.endIdleSessions();import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.end_idle_sessions()Example answer
{
"data": {
"reaped": 1
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}Get a session
The session with its short analytics: duration, billed minutes and rate, turns, talk share, and the report's summary and score when it has one. The full breakdown is GET /sessions/{id}/analytics. A session GET /sessions never lists (a job interview, a builder's preview, a child's session) is 404 here too: a candidate's interview is read only through the hiring operations (GET /candidates/{id}/interview, with a key an owner or admin gave interview content), never as a session.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Returns
200 The session. data is SessionDetail.
Fields of data
idstringrequiredSession id (
sess_…).pattern `^sess_.*`
personaIdstring | nullrequiredThe persona that spoke (
pa_…).status"ACTIVE" | "COMPLETED" | "ABANDONED"requiredACTIVE while live; COMPLETED or ABANDONED once ended.
sourcestringrequiredWhere it started: api, portal, visitor, playground, demo or guide.
turnModestring | nullrequiredexternalwhen your app owns every avatar turn (autoRespond false); null otherwise.autoRespondbooleanrequiredFalse when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).
roomstring | nullrequiredThe realtime room, while it has one.
minutesBillednumber | nullrequiredMinutes charged once the session ended; null while live.
startedAtstring (date-time)requiredISO 8601 UTC timestamp.
endedAtstring (date-time) | nullrequireddurationinteger | nullrequiredSeconds, once ended.
createdAtstring (date-time)requiredISO 8601 UTC timestamp.
modestringrequiredAVATAR (a talking avatar), VOICE_CALL or CHAT (typed).
subjectIdstring | nullrequiredThe portal subject it ran in (
sub_…; bare for keys on 2026-07-08); null for a persona session.kind"persona" | "subject" | "visitor"requiredA persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
responseTimesResponseTimes | nullrequiredHow fast the avatar started answering, once the session ended; null when it wasn't measured.
analyticsSessionAnalyticsSummary | nullrequired
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error
Request
curl "https://app.zentiqai.com/v1/sessions/sess_123" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.getSession("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.get_session("sess_123")Example answer
{
"data": {
"id": "sess_cmfq2b7x10000abcd1234",
"personaId": "pa_cmfq2b7x10000abcd1234",
"status": "ACTIVE",
"source": "api",
"turnMode": "…",
"autoRespond": true
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}Steer a live session
Swap the system prompt, language or variables for the turns that follow, without restarting. Only a session started through the API: any other session of the organisation is 404 not_found.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Body (optional)
systemPromptstring | nulllanguagestring | nullOne of GET /languages (a duo's: one of the ten it speaks).
variablesmap of any | null
Returns
200 The session. data is Session.
Fields of data
idstringrequiredSession id (
sess_…).pattern `^sess_.*`
personaIdstring | nullrequiredThe persona that spoke (
pa_…).status"ACTIVE" | "COMPLETED" | "ABANDONED"requiredACTIVE while live; COMPLETED or ABANDONED once ended.
sourcestringrequiredWhere it started: api, portal, visitor, playground, demo or guide.
turnModestring | nullrequiredexternalwhen your app owns every avatar turn (autoRespond false); null otherwise.autoRespondbooleanrequiredFalse when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).
roomstring | nullrequiredThe realtime room, while it has one.
minutesBillednumber | nullrequiredMinutes charged once the session ended; null while live.
startedAtstring (date-time)requiredISO 8601 UTC timestamp.
endedAtstring (date-time) | nullrequireddurationinteger | nullrequiredSeconds, once ended.
createdAtstring (date-time)requiredISO 8601 UTC timestamp.
modestringrequiredAVATAR (a talking avatar), VOICE_CALL or CHAT (typed).
subjectIdstring | nullrequiredThe portal subject it ran in (
sub_…; bare for keys on 2026-07-08); null for a persona session.kind"persona" | "subject" | "visitor"requiredA persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
responseTimesResponseTimes | nullrequiredHow fast the avatar started answering, once the session ended; null when it wasn't measured.
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundsession_endedrate_limitedserver_errorupstream_error
Request
curl -X PATCH "https://app.zentiqai.com/v1/sessions/sess_123" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.steerSession("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.steer_session("sess_123")Example answer
{
"data": {
"id": "sess_cmfq2b7x10000abcd1234",
"personaId": "pa_cmfq2b7x10000abcd1234",
"status": "ACTIVE",
"source": "api",
"turnMode": "…",
"autoRespond": true
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}End a session (alias of /end)
Only a session started through the API: any other session of the organisation is 404 not_found.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Returns
200 The ended session. data is Session.
Fields of data
idstringrequiredSession id (
sess_…).pattern `^sess_.*`
personaIdstring | nullrequiredThe persona that spoke (
pa_…).status"ACTIVE" | "COMPLETED" | "ABANDONED"requiredACTIVE while live; COMPLETED or ABANDONED once ended.
sourcestringrequiredWhere it started: api, portal, visitor, playground, demo or guide.
turnModestring | nullrequiredexternalwhen your app owns every avatar turn (autoRespond false); null otherwise.autoRespondbooleanrequiredFalse when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).
roomstring | nullrequiredThe realtime room, while it has one.
minutesBillednumber | nullrequiredMinutes charged once the session ended; null while live.
startedAtstring (date-time)requiredISO 8601 UTC timestamp.
endedAtstring (date-time) | nullrequireddurationinteger | nullrequiredSeconds, once ended.
createdAtstring (date-time)requiredISO 8601 UTC timestamp.
modestringrequiredAVATAR (a talking avatar), VOICE_CALL or CHAT (typed).
subjectIdstring | nullrequiredThe portal subject it ran in (
sub_…; bare for keys on 2026-07-08); null for a persona session.kind"persona" | "subject" | "visitor"requiredA persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
responseTimesResponseTimes | nullrequiredHow fast the avatar started answering, once the session ended; null when it wasn't measured.
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error
Events
Request
curl -X DELETE "https://app.zentiqai.com/v1/sessions/sess_123" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.deleteSession("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.delete_session("sess_123")Example answer
{
"data": {
"id": "sess_cmfq2b7x10000abcd1234",
"personaId": "pa_cmfq2b7x10000abcd1234",
"status": "ACTIVE",
"source": "api",
"turnMode": "…",
"autoRespond": true
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}/endEnd a session (stops billing)
Ends the session now: billing stops on Zentiq's clock and the avatar's place is freed. Ending twice is harmless. Only a session started through the API: any other session of the organisation is 404 not_found.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Returns
200 The ended session. data is Session.
Fields of data
idstringrequiredSession id (
sess_…).pattern `^sess_.*`
personaIdstring | nullrequiredThe persona that spoke (
pa_…).status"ACTIVE" | "COMPLETED" | "ABANDONED"requiredACTIVE while live; COMPLETED or ABANDONED once ended.
sourcestringrequiredWhere it started: api, portal, visitor, playground, demo or guide.
turnModestring | nullrequiredexternalwhen your app owns every avatar turn (autoRespond false); null otherwise.autoRespondbooleanrequiredFalse when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).
roomstring | nullrequiredThe realtime room, while it has one.
minutesBillednumber | nullrequiredMinutes charged once the session ended; null while live.
startedAtstring (date-time)requiredISO 8601 UTC timestamp.
endedAtstring (date-time) | nullrequireddurationinteger | nullrequiredSeconds, once ended.
createdAtstring (date-time)requiredISO 8601 UTC timestamp.
modestringrequiredAVATAR (a talking avatar), VOICE_CALL or CHAT (typed).
subjectIdstring | nullrequiredThe portal subject it ran in (
sub_…; bare for keys on 2026-07-08); null for a persona session.kind"persona" | "subject" | "visitor"requiredA persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
responseTimesResponseTimes | nullrequiredHow fast the avatar started answering, once the session ended; null when it wasn't measured.
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error
Events
Request
curl -X POST "https://app.zentiqai.com/v1/sessions/sess_123/end" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.endSession("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.end_session("sess_123")Example answer
{
"data": {
"id": "sess_cmfq2b7x10000abcd1234",
"personaId": "pa_cmfq2b7x10000abcd1234",
"status": "ACTIVE",
"source": "api",
"turnMode": "…",
"autoRespond": true
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}/sayMake the avatar speak exact text now
Scripted lines, "repeat after me", announcements, or your system driving every turn (external turn mode). In external turn mode, and for a duo, the line is spoken as sent (interrupt, default true, first cuts off speech in progress). A single avatar answering on its own speaks it as its next turn once it is idle: it may paraphrase slightly, and interrupt, endSession, turnId and eventId don't apply (end such a session with POST /sessions/{id}/end). Only a session started through the API: any other session of the organisation is 404 not_found.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Body
textstringrequiredUp to 2,000 characters.
interruptboolean | nullCut off any speech in progress first (default true).
languagestring | nullThe line's language: one of GET /languages. Default: the session's.
turnIdstring | nullExternal turn mode: the turn this line answers.
eventIdstring | nullExternal turn mode: makes a redelivered line idempotent.
endSessionboolean | nullExternal turn mode: end the session after this line.
Returns
200 The line was handed to the live session. data is object.
Fields of data
okbooleanrequired
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundsession_endedrate_limitedserver_errorupstream_error
Request
curl -X POST "https://app.zentiqai.com/v1/sessions/sess_123/say" \
-H "Authorization: Bearer $ZENTIQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "…"
}'import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.sessionSay("sess_123", {
text: "…",
});import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.session_say("sess_123", {
"text": "…",
})Example answer
{
"data": {
"ok": true
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}/transcriptTurn-by-turn transcript
The conversation as recorded (also live over the realtime data channel). The console's rules apply: a candidate's interview, a young learner's conversation and a website visitor who said they are under 18 are never given out here (403; a hiring system reads a candidate's interview through GET /candidates/{id}/interview), and a session whose subject keeps nothing, a builder's preview, or any session of an organisation with zero data retention on (while it runs too), has no turns (kept: false).
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Returns
200 The transcript. data is Transcript.
Fields of data
sessionIdstringrequiredSession id (
sess_…).pattern `^sess_.*`
keptbooleanrequiredTrue when the turns are the whole transcript; false when the session's subject keeps nothing (no turns).
aiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
Errors
invalid_requestunauthorizedforbiddenupgrade_requiredcandidate_interviewlearner_conversationnot_foundrate_limitedserver_error
Request
curl "https://app.zentiqai.com/v1/sessions/sess_123/transcript" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.sessionTranscript("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.session_transcript("sess_123")Example answer
{
"data": {
"sessionId": "sess_cmfq2b7x10000abcd1234",
"turns": [
{
"role": "user",
"text": "…",
"aiGenerated": true
}
],
"kept": true,
"aiGenerated": true
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}/analyticsSession analytics
Duration, billed minutes and rate (from the minutes ledger), turn counts, talk share (user vs avatar), per-turn timing, the report's summary and score when the session has one, and the AI disclosure sent. A session GET /sessions never lists (a job interview, a builder's preview, a child's session) is 404.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Returns
200 The session's analytics. data is SessionAnalytics.
Fields of data
sessionIdstringrequiredSession id (
sess_…).pattern `^sess_.*`
statusstringrequiredsourcestringrequireddurationobjectrequiredsecondsintegerrequiredlivebooleanrequiredstartedAtstring (date-time)requiredISO 8601 UTC timestamp.
endedAtstring (date-time) | nullrequired
billingobjectrequiredratenumberrequiredbilledSecondsintegerrequiredbilledMinutesnumberrequiredledgerobjectrequiredThe minutes ledger entries for this session.
chargedSecondsintegerrequiredabsorbedSecondsintegerrequiredunmeteredSecondsintegerrequiredwallSecondsintegerrequiredentriesintegerrequired
turnsobjectrequiredtotalintegerrequireduserintegerrequiredavatarintegerrequired
talkShareobjectrequiredmethod"word_timings" | "word_count" | nullrequiredusernumber | nullrequiredavatarnumber | nullrequireduserWordsintegerrequiredavatarWordsintegerrequireduserSpeechMsinteger | nullrequiredavatarSpeechMsinteger | nullrequired
timingobjectrequiredturnTimestampsbooleanrequiredwordTimingsbooleanrequireduserToAvatarMsobject | nullrequiredTime between a person's turn being recorded and the avatar's reply being recorded. Not a pure response latency: turns are timestamped when they are stored.
medianintegerrequiredp90integerrequiredsamplesintegerrequired
turnsobject[]requiredindexintegerrequiredrole"user" | "avatar"requiredspeakerstringA duo's speaker.
atMsinteger | nullrequiredwordsintegerrequiredspeechMsinteger | nullrequiredsincePreviousMsinteger | nullrequired
interruptionsanyrequiredNot recorded yet.
clientMetricsobject | nullrequiredpaceWpmnumber | nullrequiredtalkListenRationumber | nullrequiredfillerWordsPerMinutenumber | nullrequired
candidatebooleanrequiredA candidate's screening interview: report and summary are always null.
reportobject | nullrequiredkindstring | nullrequiredsummarystring | nullrequiredscorenumber | nullrequiredoverallnumber | nullrequiredpercentnumber | nullrequiredpassedboolean | nullrequiredproducedAtstring (date-time)requiredISO 8601 UTC timestamp.
summarystring | nullrequireddisclosureobject | nullrequiredrequiredboolean | nullrequiredtextstring | nullrequiredmethodstring | nullrequiredrecordedAtstring (date-time)requiredISO 8601 UTC timestamp.
notRecordedstring[]requiredaiGeneratedtruerequiredAlways true: the avatar's video, voice and words are AI-generated (EU AI Act Article 50(2)). Keep this marker with the content when you store or pass it on.
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error
Request
curl "https://app.zentiqai.com/v1/sessions/sess_123/analytics" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.sessionAnalytics("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.session_analytics("sess_123")Example answer
{
"data": {
"sessionId": "sess_cmfq2b7x10000abcd1234",
"status": "…",
"source": "…",
"duration": {
"seconds": 1,
"live": true,
"startedAt": "2026-09-25T12:00:00.000Z",
"endedAt": "2026-09-25T12:00:00.000Z"
},
"billing": {
"rate": 1,
"billedSeconds": 1,
"billedMinutes": 1,
"ledger": {
"chargedSeconds": 1,
"absorbedSeconds": 1,
"unmeteredSeconds": 1,
"wallSeconds": 1,
"entries": 1
}
},
"turns": {
"total": 1,
"user": 1,
"avatar": 1
}
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}/v1/sessions/{id}/staff-tokenJoin a visitor's live conversation as a person from your team
A member of your team joins a website visitor's live avatar conversation as themselves (their own voice, and their camera when they turn it on), for example from your own helpdesk. The avatar says who joined and what the visitor needs, then goes quiet; when the last person leaves it carries on, or the conversation ends if the visitor has gone. A member's own key (made by them in the console) joins as them; a full-access key may name any member in userId. The member must be able to open the portal's inbox, the switch serve.staff_join must be on, and at most two people from the team can be in one call. Records handoff.joined.
Path parameters
idstringrequiredSession id (
sess_…); the id without its prefix is accepted too.
Body (optional)
userIdstring | nullThe member who joins. A member's own key (made by them in the console) joins as them and may leave this out; only a full-access key may name another member.
Returns
200 The member's token for the visitor's room. data is StaffToken.
Fields of data
sessionIdstringrequiredSession id (
sess_…).pattern `^sess_.*`
staffTokenstringrequiredShort-lived client token (15 minutes) for the realtime room, as the person: microphone and camera, no data channel.
urlstringrequiredThe realtime transport URL.
roomstringrequiredidentitystringrequiredThe person's identity in the room:
staff-<userId>-<random>, with the attribute zq_role=staff.userIdstringrequiredPerson id (
usr_…).pattern `^usr_.*`
handedOverbooleanrequiredTrue when this person is the first from the team in the call: the avatar says who joined and what the visitor needs, then goes quiet until the team has left.
Errors
invalid_requestunauthorizedforbiddenupgrade_requirednot_foundconflictsession_endedrate_limitedserver_errorupstream_errorcapacity
Events
Request
curl -X POST "https://app.zentiqai.com/v1/sessions/sess_123/staff-token" \
-H "Authorization: Bearer $ZENTIQ_API_KEY"import { Zentiq } from "@zentiq/node";
const zentiq = new Zentiq({ apiKey: process.env.ZENTIQ_API_KEY });
const result = await zentiq.createStaffToken("sess_123");import os
from zentiq import Zentiq
zentiq = Zentiq(api_key=os.environ["ZENTIQ_API_KEY"])
result = zentiq.create_staff_token("sess_123")Example answer
{
"data": {
"sessionId": "sess_cmfq2b7x10000abcd1234",
"staffToken": "…",
"url": "https://www.example.com",
"room": "…",
"identity": "staff-cmfq2b7x10000abcd1234-k3m9qa",
"userId": "usr_cmfq2b7x10000abcd1234"
},
"error": null,
"requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}