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 via metadata
  • 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/
FieldRequiredDescription
agentCharacter slug (e.g. support-bot)
languageBCP-47 code — overrides character default for STT/TTS
system_promptPer-session prompt override
greetingPer-session greeting override
voice_modelNamed voice (e.g. chatterbox-en-v1)
mcp_headersHeaders merged into every MCP server call this session
metadataArbitrary 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 active

Response 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

StatusCauseFix
403 ForbiddenOrigin header not on the character’s allowed_origins listAdd your domain via dashboard or PATCH /api/ai-characters/{slug}/
404 Not FoundWrong agent slugCheck spelling in the dashboard
429 Too Many RequestsConcurrent session limit reachedWait or upgrade plan