Why mint from your backend
Calling the session endpoint from your server (not the browser) lets you:
- Keep your Oshara credentials server-side
- Pass your logged-in user’s
user_id,name,email, plan, etc. to the agent viametadata - Enforce your own session limits and logging
- Reuse the same flow for native apps that don’t load the widget
Browser → POST /your-api/voice-token → Your backend → POST /api/agents/agent-session/ → Oshara
│
Browser ← { token, livekit_url } ◄──────────────────────────────────────────────────────┘Endpoint
POST /api/agents/agent-session/| Field | Required | Description |
|---|---|---|
agent | ✓ | Character slug (e.g. support-bot) |
language | BCP-47 code — overrides character default for STT/TTS | |
system_prompt | Per-session prompt override | |
greeting | Per-session greeting override | |
voice_model | Named voice (e.g. chatterbox-en-v1) | |
mcp_headers | Headers merged into every MCP server call this session | |
metadata | Arbitrary user/session context (user_id, email, plan, …) |
Full endpoint reference: API Reference → Sessions.
Server-side example (Node.js)
// POST /your-api/voice-token
app.post("/your-api/voice-token", requireAuth, async (req, res) => {
const user = req.user;
const session = await fetch("https://api.oshara.ai/api/agents/agent-session/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Origin": "https://yoursite.com"
},
body: JSON.stringify({
agent: "support-bot",
language: req.body.language || "en",
metadata: {
user_id: user.id,
user_name: user.fullName,
user_email: user.email,
account_tier: user.plan,
org_id: user.orgId
}
})
}).then(r => r.json());
res.json({
token: session.token,
livekit_url: session.livekit_url,
session_id: session.session_id
});
});Server-side example (Python)
import httpx
from fastapi import APIRouter, Depends
router = APIRouter()
@router.post("/voice-token")
async def get_voice_token(user=Depends(get_current_user)):
async with httpx.AsyncClient() as client:
r = await client.post(
"https://api.oshara.ai/api/agents/agent-session/",
headers={"Origin": "https://yoursite.com"},
json={
"agent": "support-bot",
"metadata": {
"user_id": str(user.id),
"user_name": user.full_name,
"user_email": user.email,
}
}
)
data = r.json()
return {"token": data["token"], "livekit_url": data["livekit_url"]}Connect the browser with the LiveKit SDK
import { Room } from "livekit-client";
const { token, livekit_url } = await fetch("/your-api/voice-token", { method: "POST" })
.then(r => r.json());
const room = new Room();
await room.connect(livekit_url, token);
// Mic, audio in/out, data channel are now activeResponse shape
{
"token": "eyJhbGci...",
"livekit_url": "wss://audio-inference.oshara.ai",
"room_name": "oshara-voice-support-bot-user123-abc12",
"participant_identity": "user-123-abc12",
"session_id": "sess_a1b2c3",
"expires_in_seconds": 3600,
"character_slug": "support-bot",
"system_prompt": "You are a helpful support agent...",
"greeting": "Hi! How can I help you today?"
}Use session_id to filter form responses and logs after the call.
Errors
| Status | Cause | Fix |
|---|---|---|
403 Forbidden | Origin header not on the character’s allowed_origins list | Add your domain via dashboard or PATCH /api/ai-characters/{slug}/ |
404 Not Found | Wrong agent slug | Check spelling in the dashboard |
429 Too Many Requests | Concurrent session limit reached | Wait or upgrade plan |
Last updated on