Get a session transcript

GET /api/billing/sessions/{session_id}/
Returns the full AgentSessionSummary for a single voice session, including the entire conversation transcript.
AuthJWT bearer or X-API-Key (see API Keys)
ScopingStaff see any session. All other callers see only sessions for AI characters they created.
Path paramsession_id (UUID) returned at session start
# Using an API key (recommended for backends)
curl https://api.oshara.ai/api/billing/sessions/sess_a1b2c3/ \
  -H "X-API-Key: sk_..."

# Or using a JWT
curl https://api.oshara.ai/api/billing/sessions/sess_a1b2c3/ \
  -H "Authorization: Bearer <token>"

Response

Wrapped in the standard envelope ({ success, message, data, errors }). The data field contains:
{
  "id": 142,
  "session_id": "sess_a1b2c3",
  "tenant_id": "acme",
  "character_slug": "support-bot",
  "user": 7,
  "room_name": "oshara-voice-support-bot-user123-abc12",
  "origin_url": "https://yoursite.com/contact",
  "metadata": { "user_id": "u_42", "plan": "pro" },

  "session_duration_seconds": 184.7,
  "turn_count": 14,
  "interruption_count": 2,

  "llm_input_tokens": 1840,
  "llm_output_tokens": 612,
  "llm_model": "gpt-4o-mini",
  "llm_provider": "openai",
  "stt_audio_seconds": 92.3,
  "stt_provider": "openai",
  "tts_characters": 1284,
  "tts_provider": "chatterbox",
  "estimated_cost_usd": "0.014200",

  "finalized_at": "2026-05-28T14:23:48Z",
  "recording_status": "READY",
  "recording_url": "s3://oshara-recordings/sess_a1b2c3.mp4",
  "recording_presigned_url": "https://s3.amazonaws.com/...?X-Amz-Signature=...",

  "transcript": [
    { "role": "assistant", "text": "Hi! How can I help you today?", "turn_index": 0, "ts": 0.42 },
    { "role": "user",      "text": "I want to book a demo.",         "turn_index": 1, "ts": 3.18 },
    { "role": "assistant", "text": "Great — what day works for you?","turn_index": 2, "ts": 5.04 }
  ],
  "transcript_text": "Assistant: Hi! How can I help you today?\nUser: I want to book a demo.\nAssistant: Great — what day works for you?",

  "created_at": "2026-05-28T14:20:43Z"
}

Transcript shape

transcript is an ordered array — index 0 is the first utterance, increasing over time.
FieldTypeDescription
rolestring"user" or "assistant". Tool calls / system messages are excluded.
textstringThe exact text — STT output for user turns, LLM output for assistant turns.
turn_indexnumber0-based turn counter within the session.
tsnumberSeconds since the session started. Useful for syncing to the recording.
transcript_text is the same data flattened into a single string — convenient for previews, search indexing, or feeding back into an LLM as context.

Field reference

FieldDescription
session_idThe same UUID returned from POST /api/agents/agent-session/.
tenant_id / character_slugWhich tenant and character this session belonged to.
userUser ID that owned the session (if authenticated).
room_nameLiveKit room name — useful for correlating with LiveKit dashboard logs.
origin_urlThe page URL that initiated the call.
metadataWhatever you passed at session start (user_id, plan, etc.).
session_duration_secondsWall-clock length of the call.
turn_countTotal turns (user + assistant).
interruption_countTimes the user spoke over the agent.
llm_*, stt_*, tts_*Token / audio / character usage for cost accounting.
estimated_cost_usdSum of LLM + STT + TTS costs based on provider unit prices.
recording_urlS3 URI of the call recording (private).
recording_presigned_urlTime-limited HTTPS URL that anyone can play. Refresh by re-fetching this endpoint.
finalized_atWhen the worker finalized the session (post-call). null for in-progress sessions.

Errors

StatusCause
400 Bad Requestsession_id is not a valid UUID
401 UnauthorizedMissing / expired JWT, or invalid / inactive X-API-Key
404 Not FoundSession doesn’t exist or the caller doesn’t own the character that ran the session. Treated the same to avoid leaking session IDs across tenants.

Common patterns

Get transcript only

const r = await fetch(`https://api.oshara.ai/api/billing/sessions/${id}/`, {
  headers: { "X-API-Key": process.env.OSHARA_API_KEY }
}).then(r => r.json());

const turns = r.data.transcript;          // array of { role, text, turn_index, ts }

Stream the recording while showing the transcript

const { recording_presigned_url, transcript } = (await fetchSession(id)).data;

audio.src = recording_presigned_url;
audio.addEventListener("timeupdate", () => {
  const active = transcript.findLast(t => t.ts <= audio.currentTime);
  highlight(active?.turn_index);
});

Index transcripts into your own search system

const sessions = (await listSessions({ character_slug: "support-bot" })).data.sessions.data;

for (const s of sessions) {
  await elastic.index({
    index:        "voice-transcripts",
    id:           s.session_id,
    body: {
      slug:       s.character_slug,
      created_at: s.created_at,
      transcript: s.transcript_text,
      duration:   s.session_duration_seconds,
    }
  });
}