Skip to Content

List sessions

GET /api/billing/usage/

Returns a paginated list of AgentSessionSummary objects plus aggregate totals. Filter by character to get the session history of a specific AI character.

AuthJWT bearer or X-API-Key (see API Keys)
ScopingStaff see all sessions. All other callers see only sessions for AI characters they created — results are auto-filtered, no extra params needed.
PaginationPage-based, default 50 / page, max 200
# Using an API key (recommended for backends) curl "https://api.oshara.ai/api/billing/usage/?character_slug=support-bot" \ -H "X-API-Key: sk_..." # Or using a JWT curl "https://api.oshara.ai/api/billing/usage/?character_slug=support-bot" \ -H "Authorization: Bearer <token>"

Query parameters

ParamTypeDescription
character_slugstringFilter by character. Returns only sessions for this agent slug.
tenant_idstringFilter by tenant.
startISO datetimeLower bound on created_at (inclusive).
endISO datetimeUpper bound on created_at (inclusive).
pageint1-based page number (default 1).
page_sizeintResults per page (default 50, max 200).

Examples

# All sessions for one character GET /api/billing/usage/?character_slug=support-bot # Sessions in May 2026 for one character GET /api/billing/usage/?character_slug=support-bot&start=2026-05-01T00:00:00Z&end=2026-05-31T23:59:59Z # Second page, 100 per page GET /api/billing/usage/?character_slug=support-bot&page=2&page_size=100

Response

Wrapped in the standard envelope ({ success, message, data, errors }). The data field contains:

{ "total_sessions": 1284, "total_llm_input_tokens": 1840234, "total_llm_output_tokens": 612480, "total_stt_audio_seconds": 18420.6, "total_tts_characters": 1284512, "total_estimated_cost_usd": "18.250400", "sessions": { "data": [ { "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" }, "session_duration_seconds": 184.7, "turn_count": 14, "interruption_count": 2, "llm_input_tokens": 1840, "llm_output_tokens": 612, "llm_model": "gpt-4o-mini", "stt_audio_seconds": 92.3, "tts_characters": 1284, "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 } ], "transcript_text": "Assistant: Hi!...\nUser: I want to book a demo.", "created_at": "2026-05-28T14:20:43Z" } ], "pagination": { "count": 1284, "next": "https://api.oshara.ai/api/billing/usage/?character_slug=support-bot&page=2", "previous": null } } }

Aggregate totals

The top-level fields reflect the filtered queryset — totals for sessions matching your character_slug / date range, not the whole tenant.

FieldMeaning
total_sessionsCount of matching sessions
total_llm_input_tokens / total_llm_output_tokensSum across all matching sessions
total_stt_audio_secondsTotal seconds of audio transcribed
total_tts_charactersTotal characters spoken by the agent
total_estimated_cost_usdSum of estimated_cost_usd

Use these for billing dashboards without having to fetch every page.

Per-session fields

Each entry in sessions.data is a full AgentSessionSummary — same shape as Chat History. That means you get the transcript inline for every session in the list, no follow-up calls needed.

Notable fields for character history:

FieldUse
session_idUnique ID; pass to chat-history endpoint for a single-session view
created_atWhen the session started
finalized_atWhen it ended (null for in-progress)
session_duration_secondsLength of the call
turn_countConversation depth
transcript / transcript_textFull conversation, inline
metadataThe metadata you passed at session start
origin_urlPage the call came from
recording_presigned_urlTime-limited URL to play the audio recording

Pagination

sessions.pagination.next and previous are absolute URLs you can fetch directly. count is the total before pagination.

async function* iterAllSessions(slug) { let url = `https://api.oshara.ai/api/billing/usage/?character_slug=${slug}`; while (url) { const { data } = await fetch(url, { headers: { "X-API-Key": process.env.OSHARA_API_KEY } }).then(r => r.json()); for (const session of data.sessions.data) yield session; url = data.sessions.pagination.next; } } for await (const s of iterAllSessions("support-bot")) { console.log(s.session_id, s.session_duration_seconds, s.transcript.length, "turns"); }

Errors

StatusCause
401 UnauthorizedMissing / expired JWT, or invalid / inactive X-API-Key
400 Bad RequestMalformed start / end datetime

Note: callers never get a 403 here. If you filter by a character_slug you don’t own, the response is a 200 with an empty sessions.data array — the scoping is enforced silently at the queryset level.

Common patterns

Daily report for one character

const today = new Date().toISOString().slice(0, 10); const tomorrow = new Date(Date.now() + 86_400_000).toISOString().slice(0, 10); const stats = await fetch( `https://api.oshara.ai/api/billing/usage/?character_slug=support-bot` + `&start=${today}T00:00:00Z&end=${tomorrow}T00:00:00Z`, { headers: { "X-API-Key": process.env.OSHARA_API_KEY } } ).then(r => r.json()); console.log(`Today: ${stats.data.total_sessions} calls, ` + `${stats.data.total_estimated_cost_usd} USD`);

Compare characters

const slugs = ["support-bot", "sales-agent", "onboarding-bot"]; const results = await Promise.all( slugs.map(slug => fetch(`https://api.oshara.ai/api/billing/usage/?character_slug=${slug}`, { headers: { "X-API-Key": process.env.OSHARA_API_KEY } }).then(r => r.json()).then(j => ({ slug, ...j.data })) ) ); console.table(results.map(r => ({ slug: r.slug, sessions: r.total_sessions, cost: r.total_estimated_cost_usd, avg_len: r.total_stt_audio_seconds / r.total_sessions, })));

Sync new sessions hourly into your DB

const since = await db.kv.get("oshara:last_synced") ?? new Date(Date.now() - 3600_000).toISOString(); for await (const s of iterAllSessions("support-bot", { start: since })) { await db.sessions.upsert({ session_id: s.session_id, started_at: s.created_at, duration: s.session_duration_seconds, cost: s.estimated_cost_usd, transcript: s.transcript_text, user_metadata: s.metadata, }); } await db.kv.set("oshara:last_synced", new Date().toISOString());
Last updated on