Streaming-first architecture. Persistent WebSocket audio. Auto language detection with mid-call code-switching. One API, every language.
From zero to a running multilingual voice agent. No telephony expertise needed.
# Set in your environment export PARLO_API_KEY="parlo_live_xxxxxxxxxxxxxxxx"
# Node.js npm install @parlo/sdk # Python pip install parlo # Go go get github.com/parlo-ai/go-sdk
const agent = await client.agents.create({ name: 'support-agent', language: 'auto', // auto-detect any of 50+ languages voice: 'nova', instructions: 'You are a helpful support agent.' });
// Provision a phone number const number = await client.numbers.provision({ country: 'US', agentId: agent.id }); // Or use your own SIP trunk await client.agents.update(agent.id, { sipIngest: 'sip:ingest.parlo.ai' });
// Express handler โ receives signed POST requests app.post('/webhooks/parlo', (req, res) => { const { event, call, transcript } = req.body; if (event === 'call.ended') { saveTranscript(call.id, transcript); } res.sendStatus(200); });
Full examples for creating an agent, starting a call, and consuming the WebSocket audio stream.
import { ParloClient } from '@parlo/sdk'; import WebSocket from 'ws'; const client = new ParloClient({ apiKey: process.env.PARLO_API_KEY }); // โโ 1. Create agent const agent = await client.agents.create({ name: 'support-agent', language: 'auto', voice: 'nova', instructions: 'You are a helpful multilingual support agent. ' + 'Respond in the caller\'s language. Be concise.' }); console.log('Agent:', agent.id); // agt_01j9k... // โโ 2. Outbound call const call = await client.calls.create({ agentId: agent.id, to: '+1-555-0100', from: '+1-555-0199', metadata: { ticketId: 'TKT-4411' }, webhook: 'https://your-server.com/webhooks/parlo' }); console.log('Call:', call.id); // call_01j9k... // โโ 3. Connect WebSocket stream (raw PCM-16 audio) const ws = new WebSocket( `wss://stream.parlo.ai/v1/calls/${call.id}`, { headers: { 'Authorization': `Bearer ${process.env.PARLO_API_KEY}` } } ); ws.on('message', (raw) => { const msg = JSON.parse(raw); switch (msg.type) { case 'transcript.final': console.log(`[${msg.lang}] ${msg.speaker}: ${msg.text}`); break; case 'language.detected': console.log('Detected:', msg.language, 'confidence:', msg.confidence); break; case 'call.ended': ws.close(); break; } });
import os import json import websocket from parlo import ParloClient client = ParloClient(api_key=os.environ["PARLO_API_KEY"]) # โโ 1. Create agent agent = client.agents.create( name="support-agent", language="auto", voice="nova", instructions=( "You are a helpful multilingual support agent. " "Respond in the caller's language. Be concise." ) ) print(f"Agent: {agent.id}") # agt_01j9k... # โโ 2. Outbound call call = client.calls.create( agent_id=agent.id, to="+1-555-0100", from_="+1-555-0199", metadata={"ticket_id": "TKT-4411"}, webhook="https://your-server.com/webhooks/parlo" ) print(f"Call: {call.id}") # call_01j9k... # โโ 3. WebSocket stream def on_message(ws, message): msg = json.loads(message) if msg["type"] == "transcript.final": print(f"[{msg['lang']}] {msg['speaker']}: {msg['text']}") elif msg["type"] == "language.detected": print(f"Detected: {msg['language']}") elif msg["type"] == "call.ended": ws.close() ws_app = websocket.WebSocketApp( f"wss://stream.parlo.ai/v1/calls/{call.id}", header=[f"Authorization: Bearer {os.environ['PARLO_API_KEY']}"], on_message=on_message ) ws_app.run_forever()
package main import ( "fmt" "os" "github.com/parlo-ai/go-sdk/parlo" ) func main() { client := parlo.New(os.Getenv("PARLO_API_KEY")) // โโ 1. Create agent agent, err := client.Agents.Create(parlo.AgentParams{ Name: "support-agent", Language: "auto", Voice: "nova", Instructions: "You are a helpful multilingual support agent.", }) if err != nil { panic(err) } fmt.Println("Agent:", agent.ID) // โโ 2. Outbound call call, err := client.Calls.Create(parlo.CallParams{ AgentID: agent.ID, To: "+1-555-0100", From: "+1-555-0199", Metadata: map[string]any{"ticketId": "TKT-4411"}, Webhook: "https://your-server.com/webhooks/parlo", }) if err != nil { panic(err) } fmt.Println("Call:", call.ID) // โโ 3. WebSocket stream (goroutine) stream, err := client.Calls.Stream(call.ID) if err != nil { panic(err) } defer stream.Close() for msg := range stream.Messages() { switch msg.Type() { case parlo.EventTranscriptFinal: t := msg.(parlo.TranscriptEvent) fmt.Printf("[%s] %s: %s\n", t.Lang, t.Speaker, t.Text) case parlo.EventCallEnded: return } } }
# โโ 1. Create agent curl -X POST https://api.parlo.ai/v1/agents \ -H "Authorization: Bearer $PARLO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "support-agent", "language": "auto", "voice": "nova", "instructions": "You are a helpful multilingual support agent." }' # Response: # { "id": "agt_01j9kxxxxxx", "name": "support-agent", "language": "auto" } # โโ 2. Start a call curl -X POST https://api.parlo.ai/v1/calls \ -H "Authorization: Bearer $PARLO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "agt_01j9kxxxxxx", "to": "+1-555-0100", "webhook": "https://your-server.com/webhooks/parlo" }' # โโ 3. Fetch call status curl https://api.parlo.ai/v1/calls/call_01j9kxxxxxx \ -H "Authorization: Bearer $PARLO_API_KEY" # โโ 4. Get transcript curl https://api.parlo.ai/v1/transcripts/call_01j9kxxxxxx \ -H "Authorization: Bearer $PARLO_API_KEY"
Base URL: https://api.parlo.ai/v1 โ All requests require Authorization: Bearer <key>
| Method | Endpoint | Description |
|---|---|---|
| POST | /agents | Create a voice agent with name, language, voice, and system instructions. Returns agent ID used to start calls. |
| GET | /agents/:agentId | Retrieve agent configuration, voice, language settings, and associated phone numbers. |
| POST | /calls | Initiate an outbound call or register an inbound call session. Returns call ID and WebSocket stream URL. |
| GET | /calls/:callId | Fetch call status, duration, detected language, and metadata. States: queued โ ringing โ active โ ended. |
| POST | /calls/:callId/transfer | Warm or cold transfer an active call to a SIP URI or E.164 number. Optionally pass a JSON context object to the destination. |
| GET | /transcripts/:callId | Full call transcript with per-turn language tags, speaker labels, timestamps, and confidence scores. |
| POST | /webhooks | Register a webhook endpoint. Parlo signs every delivery with HMAC-SHA256. Retry policy: exponential backoff, 3 attempts. |
| GET | /numbers | List provisioned phone numbers, their assigned agents, and inbound call statistics broken down by country and language. |
Connect to wss://stream.parlo.ai/v1/calls/:callId for real-time audio and transcript events.
state: "start") or finished (state: "end") a turn.reason, final duration, and language summary.// transcript.final { type: "transcript.final", callId: "call_01j9k...", speaker: "caller", // | "agent" text: "Tengo un problema con mi pedido", lang: "es", confidence: 0.97, startMs: 4120, endMs: 6440 } // language.detected { type: "language.detected", callId: "call_01j9k...", language: "es", confidence: 0.99, detectedMs: 820 } // code_switch { type: "code_switch", callId: "call_01j9k...", from: "es", to: "en", atMs: 12300 }
All webhook deliveries are HMAC-SHA256 signed. Verify with the X-Parlo-Signature header against your webhook secret.
{
"event": "call.ended",
"version": "2024-01",
"call": {
"id": "call_01j9kxxxxxx",
"agentId": "agt_01j9kxxxxxx",
"direction": "inbound",
"from": "+52-555-0100",
"to": "+1-800-555-0199",
"startedAt": "2026-05-06T22:14:08Z",
"endedAt": "2026-05-06T22:17:34Z",
"durationSec": 206,
"hangupCause": "caller_hangup",
"language": "es",
"languageLog": ["es", "en"],
"metadata": { "ticketId": "TKT-4411" }
},
"resolved": true,
"escalated": false
}Early access is open. Keys ship in the onboarding email within 48 hours. First 250 minutes on us.