Skip to the content
Menu: Sessions

Sessions

Live conversations.

GET/v1/sessions

List sessions

listSessionssessions:readread: 600/min per key

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

  • limitinteger

    Page size, 1-200 (default 50).

  • cursorstring

    The nextCursor of 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.

  • personaIdstring

    Only this persona's sessions (pa_…).

  • subjectIdstring

    Only sessions in this subject (sub_…).

  • portalIdstring

    Only sessions in this portal's subjects (portal_…).

  • personIdstring

    Only this member's sessions (usr_…).

  • fromstring

    Started at or after this moment (ISO 8601).

  • tostring

    Started 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
  • idstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • personaIdstring | nullrequired

    The persona that spoke (pa_…).

  • status"ACTIVE" | "COMPLETED" | "ABANDONED"required

    ACTIVE while live; COMPLETED or ABANDONED once ended.

  • sourcestringrequired

    Where it started: api, portal, visitor, playground, demo or guide.

  • turnModestring | nullrequired

    external when your app owns every avatar turn (autoRespond false); null otherwise.

  • autoRespondbooleanrequired

    False when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).

  • roomstring | nullrequired

    The realtime room, while it has one.

  • minutesBillednumber | nullrequired

    Minutes charged once the session ended; null while live.

  • startedAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • endedAtstring (date-time) | nullrequired
  • durationinteger | nullrequired

    Seconds, once ended.

  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • modestringrequired

    AVATAR (a talking avatar), VOICE_CALL or CHAT (typed).

  • subjectIdstring | nullrequired

    The portal subject it ran in (sub_…; bare for keys on 2026-07-08); null for a persona session.

  • kind"persona" | "subject" | "visitor"required

    A persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.

  • aiGeneratedtruerequired

    Always 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 | nullrequired

    How 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"
Example answer
Response
{
  "data": [
    {
      "id": "sess_cmfq2b7x10000abcd1234",
      "personaId": "pa_cmfq2b7x10000abcd1234",
      "status": "ACTIVE",
      "source": "api",
      "turnMode": "…",
      "autoRespond": true
    }
  ],
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d",
  "nextCursor": null
}
POST/v1/sessions/tokens

Start a live session (mint a client token)

createSessionTokensessions:writelive: 30/min per keyIdempotency-Key

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

  • personaIdstringrequired

    The persona to start (pa_…).

  • identitystring | null

    Your end user's id (optional): the participant identity in the room.

  • languagestring | null

    Per-session spoken-language override: one of GET /languages, as a code or the name.

  • languageMode"follow" | "fixed" | null

    How this session keeps its language: follow switches to the person's language when they use another; fixed answers in the session's language anyway. Default: the persona's own setting.

  • overridesobject | null
    • systemPromptstring | null

      Replaces the persona's prompt for this session (up to 16,000 characters).

    • greetingstring | null

      Replaces the greeting (up to 2,000 characters); an empty string means no greeting.

    • variablesmap of any | null
  • autoRespondboolean | null

    false: 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
  • sessionIdstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • sessionTokenstringrequired

    Short-lived client token (15 minutes) for the realtime room.

  • urlstringrequired

    The realtime transport URL.

  • roomstringrequired
  • personaobjectrequired
    • idstringrequired

      Persona id (pa_…).

      pattern `^pa_.*`

    • namestringrequired

      The name the avatar hears. Only a duo's carries " & " ("Mark & Susan"); a single persona named "Smith & Jones" comes back as "Smith and Jones".

  • disclosureobjectrequired

    The AI disclosure (EU AI Act Article 50): keep label visible on or next to the avatar's video for the whole session and show text at its start. Recorded against the session.

    • requiredbooleanrequired

      Always true: the AI disclosure can't be switched off.

    • textstringrequired

      The notice to show when the session starts, in the session's language (the persona's own wording when it has one).

    • labelstringrequired

      The 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.

    • languagestringrequired

      The language of label and text, as a code.

    • display"whole_session"required

      When the label shows: for the whole session.

    • rulestringrequired

      What 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.

    • ownLabelbooleanrequired

      True 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.

  • ccbooleanrequired

    The persona's caption preference. The transcript always streams on the data topic "chat"; cc is a render hint.

  • aiGeneratedtruerequired

    Always 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

session.started

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"
}'
Example answer
Response
{
  "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"
}
POST/v1/sessions/end-idle

Reclaim this org's dead/idle sessions

endIdleSessionssessions:writewrite: 120/min per key

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
  • reapedintegerrequired

    How many sessions were closed.

Errors

invalid_requestunauthorizedforbiddenupgrade_requiredrate_limitedserver_error

Events

session.ended

Request

curl -X POST "https://app.zentiqai.com/v1/sessions/end-idle" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "reaped": 1
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
GET/v1/sessions/{id}

Get a session

getSessionsessions:readread: 600/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Returns

200 The session. data is SessionDetail.

Fields of data
  • idstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • personaIdstring | nullrequired

    The persona that spoke (pa_…).

  • status"ACTIVE" | "COMPLETED" | "ABANDONED"required

    ACTIVE while live; COMPLETED or ABANDONED once ended.

  • sourcestringrequired

    Where it started: api, portal, visitor, playground, demo or guide.

  • turnModestring | nullrequired

    external when your app owns every avatar turn (autoRespond false); null otherwise.

  • autoRespondbooleanrequired

    False when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).

  • roomstring | nullrequired

    The realtime room, while it has one.

  • minutesBillednumber | nullrequired

    Minutes charged once the session ended; null while live.

  • startedAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • endedAtstring (date-time) | nullrequired
  • durationinteger | nullrequired

    Seconds, once ended.

  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • modestringrequired

    AVATAR (a talking avatar), VOICE_CALL or CHAT (typed).

  • subjectIdstring | nullrequired

    The portal subject it ran in (sub_…; bare for keys on 2026-07-08); null for a persona session.

  • kind"persona" | "subject" | "visitor"required

    A persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.

  • aiGeneratedtruerequired

    Always 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 | nullrequired

    How 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"
Example answer
Response
{
  "data": {
    "id": "sess_cmfq2b7x10000abcd1234",
    "personaId": "pa_cmfq2b7x10000abcd1234",
    "status": "ACTIVE",
    "source": "api",
    "turnMode": "…",
    "autoRespond": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
PATCH/v1/sessions/{id}

Steer a live session

steerSessionsessions:writewrite: 120/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Body (optional)

  • systemPromptstring | null
  • languagestring | null

    One 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
  • idstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • personaIdstring | nullrequired

    The persona that spoke (pa_…).

  • status"ACTIVE" | "COMPLETED" | "ABANDONED"required

    ACTIVE while live; COMPLETED or ABANDONED once ended.

  • sourcestringrequired

    Where it started: api, portal, visitor, playground, demo or guide.

  • turnModestring | nullrequired

    external when your app owns every avatar turn (autoRespond false); null otherwise.

  • autoRespondbooleanrequired

    False when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).

  • roomstring | nullrequired

    The realtime room, while it has one.

  • minutesBillednumber | nullrequired

    Minutes charged once the session ended; null while live.

  • startedAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • endedAtstring (date-time) | nullrequired
  • durationinteger | nullrequired

    Seconds, once ended.

  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • modestringrequired

    AVATAR (a talking avatar), VOICE_CALL or CHAT (typed).

  • subjectIdstring | nullrequired

    The portal subject it ran in (sub_…; bare for keys on 2026-07-08); null for a persona session.

  • kind"persona" | "subject" | "visitor"required

    A persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.

  • aiGeneratedtruerequired

    Always 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 | nullrequired

    How 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"
Example answer
Response
{
  "data": {
    "id": "sess_cmfq2b7x10000abcd1234",
    "personaId": "pa_cmfq2b7x10000abcd1234",
    "status": "ACTIVE",
    "source": "api",
    "turnMode": "…",
    "autoRespond": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
DELETE/v1/sessions/{id}

End a session (alias of /end)

deleteSessionsessions:writewrite: 120/min per key

Only a session started through the API: any other session of the organisation is 404 not_found.

Path parameters

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Returns

200 The ended session. data is Session.

Fields of data
  • idstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • personaIdstring | nullrequired

    The persona that spoke (pa_…).

  • status"ACTIVE" | "COMPLETED" | "ABANDONED"required

    ACTIVE while live; COMPLETED or ABANDONED once ended.

  • sourcestringrequired

    Where it started: api, portal, visitor, playground, demo or guide.

  • turnModestring | nullrequired

    external when your app owns every avatar turn (autoRespond false); null otherwise.

  • autoRespondbooleanrequired

    False when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).

  • roomstring | nullrequired

    The realtime room, while it has one.

  • minutesBillednumber | nullrequired

    Minutes charged once the session ended; null while live.

  • startedAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • endedAtstring (date-time) | nullrequired
  • durationinteger | nullrequired

    Seconds, once ended.

  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • modestringrequired

    AVATAR (a talking avatar), VOICE_CALL or CHAT (typed).

  • subjectIdstring | nullrequired

    The portal subject it ran in (sub_…; bare for keys on 2026-07-08); null for a persona session.

  • kind"persona" | "subject" | "visitor"required

    A persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.

  • aiGeneratedtruerequired

    Always 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 | nullrequired

    How fast the avatar started answering, once the session ended; null when it wasn't measured.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error

Events

session.ended

Request

curl -X DELETE "https://app.zentiqai.com/v1/sessions/sess_123" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "id": "sess_cmfq2b7x10000abcd1234",
    "personaId": "pa_cmfq2b7x10000abcd1234",
    "status": "ACTIVE",
    "source": "api",
    "turnMode": "…",
    "autoRespond": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
POST/v1/sessions/{id}/end

End a session (stops billing)

endSessionsessions:writewrite: 120/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Returns

200 The ended session. data is Session.

Fields of data
  • idstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • personaIdstring | nullrequired

    The persona that spoke (pa_…).

  • status"ACTIVE" | "COMPLETED" | "ABANDONED"required

    ACTIVE while live; COMPLETED or ABANDONED once ended.

  • sourcestringrequired

    Where it started: api, portal, visitor, playground, demo or guide.

  • turnModestring | nullrequired

    external when your app owns every avatar turn (autoRespond false); null otherwise.

  • autoRespondbooleanrequired

    False when the avatar only speaks the lines your app sends (POST /sessions/{id}/say).

  • roomstring | nullrequired

    The realtime room, while it has one.

  • minutesBillednumber | nullrequired

    Minutes charged once the session ended; null while live.

  • startedAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • endedAtstring (date-time) | nullrequired
  • durationinteger | nullrequired

    Seconds, once ended.

  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • modestringrequired

    AVATAR (a talking avatar), VOICE_CALL or CHAT (typed).

  • subjectIdstring | nullrequired

    The portal subject it ran in (sub_…; bare for keys on 2026-07-08); null for a persona session.

  • kind"persona" | "subject" | "visitor"required

    A persona session (your app's, or the console's), a lesson or role-play in a subject, or a website visitor's conversation.

  • aiGeneratedtruerequired

    Always 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 | nullrequired

    How fast the avatar started answering, once the session ended; null when it wasn't measured.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error

Events

session.ended

Request

curl -X POST "https://app.zentiqai.com/v1/sessions/sess_123/end" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "id": "sess_cmfq2b7x10000abcd1234",
    "personaId": "pa_cmfq2b7x10000abcd1234",
    "status": "ACTIVE",
    "source": "api",
    "turnMode": "…",
    "autoRespond": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
POST/v1/sessions/{id}/say

Make the avatar speak exact text now

sessionSaysessions:writewrite: 120/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Body

  • textstringrequired

    Up to 2,000 characters.

  • interruptboolean | null

    Cut off any speech in progress first (default true).

  • languagestring | null

    The line's language: one of GET /languages. Default: the session's.

  • turnIdstring | null

    External turn mode: the turn this line answers.

  • eventIdstring | null

    External turn mode: makes a redelivered line idempotent.

  • endSessionboolean | null

    External 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": "…"
}'
Example answer
Response
{
  "data": {
    "ok": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
GET/v1/sessions/{id}/transcript

Turn-by-turn transcript

sessionTranscripttranscripts:readread: 600/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Returns

200 The transcript. data is Transcript.

Fields of data
  • sessionIdstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • turnsTranscriptTurn[]required
  • keptbooleanrequired

    True when the turns are the whole transcript; false when the session's subject keeps nothing (no turns).

  • aiGeneratedtruerequired

    Always 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"
Example answer
Response
{
  "data": {
    "sessionId": "sess_cmfq2b7x10000abcd1234",
    "turns": [
      {
        "role": "user",
        "text": "…",
        "aiGenerated": true
      }
    ],
    "kept": true,
    "aiGenerated": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
GET/v1/sessions/{id}/analytics

Session analytics

sessionAnalyticssessions:readread: 600/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Returns

200 The session's analytics. data is SessionAnalytics.

Fields of data
  • sessionIdstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • statusstringrequired
  • sourcestringrequired
  • durationobjectrequired
    • secondsintegerrequired
    • livebooleanrequired
    • startedAtstring (date-time)required

      ISO 8601 UTC timestamp.

    • endedAtstring (date-time) | nullrequired
  • billingobjectrequired
    • ratenumberrequired
    • billedSecondsintegerrequired
    • billedMinutesnumberrequired
    • ledgerobjectrequired

      The minutes ledger entries for this session.

      • chargedSecondsintegerrequired
      • absorbedSecondsintegerrequired
      • unmeteredSecondsintegerrequired
      • wallSecondsintegerrequired
      • entriesintegerrequired
  • turnsobjectrequired
    • totalintegerrequired
    • userintegerrequired
    • avatarintegerrequired
  • talkShareobjectrequired
    • method"word_timings" | "word_count" | nullrequired
    • usernumber | nullrequired
    • avatarnumber | nullrequired
    • userWordsintegerrequired
    • avatarWordsintegerrequired
    • userSpeechMsinteger | nullrequired
    • avatarSpeechMsinteger | nullrequired
  • timingobjectrequired
    • turnTimestampsbooleanrequired
    • wordTimingsbooleanrequired
    • userToAvatarMsobject | nullrequired

      Time 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.

      • medianintegerrequired
      • p90integerrequired
      • samplesintegerrequired
    • turnsobject[]required
      • indexintegerrequired
      • role"user" | "avatar"required
      • speakerstring

        A duo's speaker.

      • atMsinteger | nullrequired
      • wordsintegerrequired
      • speechMsinteger | nullrequired
      • sincePreviousMsinteger | nullrequired
  • interruptionsanyrequired

    Not recorded yet.

  • clientMetricsobject | nullrequired
    • paceWpmnumber | nullrequired
    • talkListenRationumber | nullrequired
    • fillerWordsPerMinutenumber | nullrequired
  • candidatebooleanrequired

    A candidate's screening interview: report and summary are always null.

  • reportobject | nullrequired
    • kindstring | nullrequired
    • summarystring | nullrequired
    • scorenumber | nullrequired
    • overallnumber | nullrequired
    • percentnumber | nullrequired
    • passedboolean | nullrequired
    • producedAtstring (date-time)required

      ISO 8601 UTC timestamp.

  • summarystring | nullrequired
  • disclosureobject | nullrequired
    • requiredboolean | nullrequired
    • textstring | nullrequired
    • methodstring | nullrequired
    • recordedAtstring (date-time)required

      ISO 8601 UTC timestamp.

  • notRecordedstring[]required
  • aiGeneratedtruerequired

    Always 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"
Example answer
Response
{
  "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"
}
POST/v1/sessions/{id}/staff-token

Join a visitor's live conversation as a person from your team

createStaffTokensessions:writelive: 30/min per key

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

  • idstringrequired

    Session id (sess_…); the id without its prefix is accepted too.

Body (optional)

  • userIdstring | null

    The 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
  • sessionIdstringrequired

    Session id (sess_…).

    pattern `^sess_.*`

  • staffTokenstringrequired

    Short-lived client token (15 minutes) for the realtime room, as the person: microphone and camera, no data channel.

  • urlstringrequired

    The realtime transport URL.

  • roomstringrequired
  • identitystringrequired

    The person's identity in the room: staff-<userId>-<random>, with the attribute zq_role=staff.

  • userIdstringrequired

    Person id (usr_…).

    pattern `^usr_.*`

  • handedOverbooleanrequired

    True 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

handoff.joined

Request

curl -X POST "https://app.zentiqai.com/v1/sessions/sess_123/staff-token" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "sessionId": "sess_cmfq2b7x10000abcd1234",
    "staffToken": "…",
    "url": "https://www.example.com",
    "room": "…",
    "identity": "staff-cmfq2b7x10000abcd1234-k3m9qa",
    "userId": "usr_cmfq2b7x10000abcd1234"
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}