Getting a Session Credential
Before
createSession()can run in the browser, your backend must mint a credential. The credential is a short-lived LiveKit URL and token, scoped to a single session. No secrets reach the client.
By the end of this page you'll have a live voice or video Atanzo AI session running inside your own app. Before you start, you'll need API credentials (an OAuth2 client with the invoke_voice and/or invoke_video scope) and either an agentId or a presetId to run.
How the Credential Flow Works
A session credential is produced in two hops:
- Your backend authenticates with Atanzo, then calls
POST /guide/sessionwith youragentId(orpresetId) and any session options. Atanzo returns aSessionCredentialobject containing a LiveKit URL and a scoped token. - Your frontend receives that credential from your own token endpoint, then calls
createSession({ credential }). The SDK uses the credential to join the LiveKit room — your API key never leaves the server.
All session configuration (agent selection, interaction mode, language, credit cap) is baked into the credential at creation time. The browser cannot override these values after the fact.
Backend Step — Calling the Token Endpoint
Your backend calls:
POST /guide/session
agentId is required unless presetId is supplied — the endpoint accepts either. All other parameters are optional and have sensible defaults.
| Parameter | Type | Default | Notes |
|---|---|---|---|
agentId | string | — | Required unless presetId is supplied |
presetId | string | — | Coaching preset; loads trusted server-side config and overrides client session knobs |
mode | "voice" | "video" | "voice" | Derived from the preset when one is used |
interactionMode | "guided_steps" | "immediate_feedback" | "open_walkthrough" | "guided_steps" | open_walkthrough records an unstructured session — no plan, no steps — and produces a written report at the end |
walkthroughTopic | string | "" | Optional subject for open_walkthrough; titles the generated report. Leave blank and the assistant asks at the start of the session |
lang | string | "en-US" | BCP-47 |
llmModel | string | "gpt-5-mini" | Cascade path only |
realtimeProvider | "gemini-live" | "gemini-live-audio" | "classic" | "cascade" | unset | Omit for the mode-aware default (gemini-live) |
videoFps | number | 1 | Clamped 1–10 |
videoProcessor | string | "none" | yolo-pose, yolo-object, roboflow-rfdetr, mediapipe-face |
enableRag | boolean | true | |
activeAccesstag | string | "" | Non-empty also switches on accesstag RAG |
continueRunId | string | null | Resume |
endUserRef | string | null | ^[A-Za-z0-9_-]{1,64}$ — rejected otherwise |
maxCreditsPerSession | number | null | |
feedbackTask | string | "" | Required for immediate_feedback unless a preset supplies it |
feedbackTipsPerCycle | number | 1 | Clamped 1–5 |
feedbackAutoIntervalSeconds | number | 0 | Clamped 0–600 |
feedbackTiming | "continuous" | "post_session" | "continuous" | |
metrics | object | null | Metric definitions (schema_version 1) |
finalReport | object | null | End-of-session scorer config |
sttOptions | object | null | Cascade path only (e.g. preserveFillers) |
poseGuidance | boolean | false | Per-tip pose graphics |
A few session values are fixed server-side and cannot be overridden by the caller: session_cap_minutes: 60, inactivity_warn_seconds: 300, inactivity_end_seconds: 360.
Mode scope enforcement. video mode requires the invoke_video scope on your credential; voice mode requires invoke_voice. If the requested mode (or the preset's device_mode) isn't covered by your granted scopes, the endpoint returns 403 SCOPE_REQUIRED. See API Authentication for how scopes are granted.
The endpoint returns a SessionCredential object. Pass this object directly to createSession() — do not unpack or modify it.
Frontend Step — Calling createSession()
Once your backend exposes the credential at your own endpoint (e.g. /api/your-token-endpoint), your frontend fetches it and bootstraps the session:
import { createSession } from "@atanzoai/embed-core";
const credential = await fetch("/api/your-token-endpoint").then(r => r.json());
const session = await createSession({ credential });
createSession() validates the credential's protocolVersion, connects to the LiveKit infrastructure, and returns a Session object. From here you attach transports (attachVoice, attachVideo) and subscribe to events.
Session Events
Once a session is created, subscribe to agent events with session.on(). These are the full AgentEvent union — every type your handler may see:
| Event | Payload | Description |
|---|---|---|
session_started | { protocolVersion, mode, interactionMode, runId } | Fired once the session is live. |
status | { status } | status is one of idle, clarifying, planning, plan_review, observing, active, completed, terminated, scoring. |
plan | { plan } | The agent's current ExecutionPlan — steps, safety warnings, tools needed, and metadata. Fires on session start and whenever the plan changes. |
plan_announcement | { text } | Spoken plan relay (the agent narrating the plan). Visual clients that render plan directly can ignore this. |
step_update | { stepIndex, totalSteps, status, stepDescription? } | The active step changed. status is "active" or "completed". |
paused | { paused, hint? } | paused: true = agent paused (holds current step, suppresses coaching tips); false = resumed. hint may be "didnt_catch". |
processor_status | { status, processor } | status is "active" or "inactive"; processor names the running CV processor. |
knowledge | { knowledge_sources } | Array of KnowledgeSource (RAG citations) backing the agent's current response. |
frame_captured | { ts, processor } | A video frame was sampled and processed. |
credits | { creditsUsed, sessionMinutes, ratePerMin, mode? } | Running credit-usage snapshot for the session. |
error | { code, message, recoverable } | A session-level error. code is one of PLAN_GENERATION_TIMEOUT, PLAN_GENERATION_FAILED, CREDITS_EXHAUSTED, RAG_UNAVAILABLE, LLM_OVERLOAD, FEEDBACK_GENERATION_FAILED, VISION_UNAVAILABLE, INTERNAL_ERROR, METRICS_PROVIDER_MISMATCH. |
question_exchange | { questionId, stepIndex, question, answerSummary, answerFull, mode, timestamp } | A user question and the agent's answer, logged for the session transcript/report. |
metrics_update | { timestamp, sessionElapsedSeconds, snapshot } | Periodic MetricsSnapshot of all enabled metrics. Suppressed live when feedbackTiming is "post_session". |
artifact | { runId, artifact } | A durable output the run produced — a document, the end-of-session report, or similar. The end-of-session LLM report arrives here as artifact.kind === "report", with the scored criteria and coaching tips in artifact.data. |
documentation | { state, docRunId, runId, title?, format?, mediaId?, ... } | Lifecycle of the documentation automation agent (if enabled for the session): "running" when a report is being generated, then "ready" or "error". |
session_ended | { reason, canResume, runId } | The session has ended. reason may be "completed", "timeout", "credit_limit", or "error". canResume indicates whether continueRunId can restore this run. |
Sending Commands to the Agent (ClientCommand)
Your client can also send messages back to the agent over the same data channel:
| Command | Shape | Notes |
|---|---|---|
| Text message | { type: "text", message } | Free-form user text (e.g. typed chat fallback). |
| Image | { type: "image", s3Key } | Points the agent at a previously-uploaded image. |
| Control command | { type: "command", cmd, stepIndex? } | cmd is one of confirm_plan, next_step, prev_step, repeat_step, jump_to_step, pause, resume, end_task, generate_documentation. stepIndex is required for jump_to_step. |
Session Resume
The SDK automatically persists the current runId in browser session storage. Use getResumeRunId() to retrieve it — do not read session storage directly, since the key name is an SDK implementation detail and may change. If the user refreshes the page mid-session, you can detect and resume the previous session:
import { createSession, getResumeRunId } from "@atanzoai/embed-core";
// On page load — check for an in-progress session
const existingRunId = getResumeRunId();
// Pass it to your backend, which forwards it to the token endpoint
const credential = await fetch("/api/your-token-endpoint", {
method: "POST",
body: JSON.stringify({
agentId: "your-agent-id",
continueRunId: existingRunId ?? undefined,
}),
}).then(r => r.json());
const session = await createSession({ credential });
When continueRunId is set, Atanzo resumes the session from where it left off — the plan state, step index, and conversation history are all restored.
Next Steps
- API Authentication — How OAuth2 scopes gate
modeand other endpoints - Voice and Video Transports — Attach the voice and video layers to your session
- API & SDK Overview — Package map and architecture overview