Quick Start Guide
This guide walks you through creating a real-time voice translation session using the Kallglot API.Choose Your Telephony Path
Before creating a session, decide how the call will reach Kallglot:- Kallglot-managed number: use an active number provisioned by Kallglot
- External provider connection: use your own Twilio or Telnyx account via
routing.connection_id - SIP / PBX: create the session first, then route your PBX call to
sip:<session_id>@sip.kallglot.com
Prerequisites
1
Get an API Key
Create your developer account at kallglot.com, then generate an API key in the Developer Portal.
2
Activate Billing
Make sure the organization has an active subscription and available API credits before sending production API requests.
Base URLs
| Environment | Base URL |
|---|---|
| REST API | https://api.kallglot.com/v1 |
| WebSocket | wss://api.kallglot.com/v1 |
Create a Session
A session establishes a real-time voice connection with Kallglot. The example below uses a Kallglot-managed number because it is the fastest setup path.Request shape: use
routing, not provider. For POST /v1/sessions, telephony selection belongs under routing (phone_number, connection_id, or SIP fields per Create Session). The session response may include a string field provider (for example "twilio") describing which carrier was resolved—that is not something you send as provider: { "type": "twilio", ... } in the request body. Older samples that used a nested provider object for outbound creation are not the public contract; integrations should always follow routing.Language auto-detection:
source_language and target_language are optional. When omitted, Kallglot infers each speaker’s language from the audio. Set them explicitly when you already know the pair for more predictable startup behavior.curl -X POST https://api.kallglot.com/v1/sessions \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"mode": "bidirectional_translation",
"source_language": "de",
"target_language": "en",
"routing": {
"phone_number": "+19788006140"
},
"metadata": {
"external_call_id": "call_abc123"
}
}'
require 'net/http'
require 'json'
require 'securerandom'
def create_session(source_lang:, target_lang:, metadata: {})
uri = URI('https://api.kallglot.com/v1/sessions')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV['KALLGLOT_API_KEY']}"
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = SecureRandom.uuid
request.body = {
mode: 'bidirectional_translation',
source_language: source_lang,
target_language: target_lang,
routing: {
phone_number: '+19788006140'
},
metadata: metadata
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)
end
# Usage
session = create_session(
source_lang: 'de',
target_lang: 'en',
metadata: { call_id: 'call_abc123' }
)
puts "Session ID: #{session['id']}"
puts "WebSocket URL: #{session['stream']['url']}"
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/google/uuid"
)
type SessionRequest struct {
Mode string `json:"mode"`
SourceLanguage string `json:"source_language"`
TargetLanguage string `json:"target_language"`
Routing map[string]string `json:"routing"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type SessionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Status string `json:"status"`
Stream struct {
URL string `json:"url"`
Token string `json:"token"`
ExpiresAt string `json:"expires_at"`
} `json:"stream"`
}
func createSession(sourceLang, targetLang string) (*SessionResponse, error) {
reqBody := SessionRequest{
Mode: "bidirectional_translation",
SourceLanguage: sourceLang,
TargetLanguage: targetLang,
Routing: map[string]string{"phone_number": "+19788006140"},
Metadata: map[string]string{"call_id": "call_abc123"},
}
jsonBody, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "https://api.kallglot.com/v1/sessions", bytes.NewBuffer(jsonBody))
req.Header.Set("Authorization", "Bearer "+os.Getenv("KALLGLOT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.New().String())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var session SessionResponse
json.NewDecoder(resp.Body).Decode(&session)
return &session, nil
}
func main() {
session, err := createSession("de", "en")
if err != nil {
panic(err)
}
fmt.Printf("Session ID: %s\n", session.ID)
fmt.Printf("WebSocket URL: %s\n", session.Stream.URL)
}
async function createSession(sourceLang, targetLang, metadata = {}) {
const response = await fetch('https://api.kallglot.com/v1/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KALLGLOT_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({
mode: 'bidirectional_translation',
source_language: sourceLang,
target_language: targetLang,
routing: {
phone_number: '+19788006140'
},
metadata
})
});
return response.json();
}
// Usage
const session = await createSession('de', 'en', { call_id: 'call_abc123' });
console.log('Session ID:', session.id);
console.log('WebSocket URL:', session.stream.url);
import os
import uuid
import requests
def create_session(source_lang: str, target_lang: str, metadata: dict = None):
response = requests.post(
'https://api.kallglot.com/v1/sessions',
headers={
'Authorization': f'Bearer {os.environ["KALLGLOT_API_KEY"]}',
'Content-Type': 'application/json',
'Idempotency-Key': str(uuid.uuid4())
},
json={
'mode': 'bidirectional_translation',
'source_language': source_lang,
'target_language': target_lang,
'routing': {
'phone_number': '+19788006140'
},
'metadata': metadata or {}
}
)
return response.json()
# Usage
session = create_session('de', 'en', {'call_id': 'call_abc123'})
print(f"Session ID: {session['id']}")
print(f"WebSocket URL: {session['stream']['url']}")
Response
{
"id": "sess_01HXYZ",
"object": "session",
"status": "created",
"mode": "bidirectional_translation",
"source_language": "de",
"target_language": "en",
"stream": {
"url": "wss://api.kallglot.com/v1/sessions/sess_01HXYZ/connect",
"token": "kst_live_xxxxxxxx",
"expires_at": "2026-03-10T12:00:00Z"
},
"call": null,
"created_at": "2026-03-10T11:55:00Z"
}
Successful
POST /v1/sessions requests currently consume 1 API credit. Check X-API-Credits-Remaining and X-API-Credits-Used in the response headers to track usage. If the organization subscription is inactive, the API returns 403 organization_inactive. If credits are exhausted, it returns 402 insufficient_api_credits.Connect to the WebSocket
The session response includes a WebSocket URL for real-time streaming. The token expires in 5 minutes, so connect promptly. Once connected, token expiry does NOT terminate the stream.async function connectToSession(sessionId, streamToken) {
const ws = new WebSocket(
`wss://api.kallglot.com/v1/sessions/${sessionId}/connect?token=${streamToken}`
);
ws.onopen = () => {
console.log('Connected to Kallglot stream');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'session.ready':
console.log('Session ready');
break;
case 'transcript.partial':
console.log(`[Partial] ${data.speaker}: ${data.text}`);
break;
case 'transcript.final':
console.log(`[${data.speaker}] ${data.text}`);
if (data.translation) {
console.log(` → ${data.translation.text}`);
}
break;
case 'audio.output':
// Play translated audio
playAudio(data.audio.payload);
break;
case 'session.ended':
console.log('Session ended:', data.reason);
ws.close();
break;
case 'error':
console.error(`Error [${data.code}]: ${data.message}`);
break;
}
};
return ws;
}
// Send audio to the stream
function sendAudio(ws, audioBase64, speaker) {
ws.send(JSON.stringify({
type: 'audio.input',
sequence: Date.now(),
timestamp_ms: Date.now(),
speaker: speaker,
audio: {
encoding: 'mulaw',
sample_rate_hz: 8000,
payload: audioBase64
}
}));
}
require 'websocket-client-simple'
require 'json'
def connect_to_session(session_id, stream_token)
url = "wss://api.kallglot.com/v1/sessions/#{session_id}/connect?token=#{stream_token}"
ws = WebSocket::Client::Simple.connect(url)
ws.on :open do
puts 'Connected to Kallglot stream'
end
ws.on :message do |msg|
data = JSON.parse(msg.data)
case data['type']
when 'session.ready'
puts 'Session ready'
when 'transcript.partial'
puts "[Partial] #{data['speaker']}: #{data['text']}"
when 'transcript.final'
puts "[#{data['speaker']}] #{data['text']}"
if data['translation']
puts " → #{data['translation']['text']}"
end
when 'audio.output'
# Handle translated audio
handle_audio(data['audio']['payload'])
when 'session.ended'
puts "Session ended: #{data['reason']}"
ws.close
when 'error'
puts "Error [#{data['code']}]: #{data['message']}"
end
end
ws.on :error do |e|
puts "Error: #{e.message}"
end
ws
end
def send_audio(ws, audio_base64, speaker)
ws.send({
type: 'audio.input',
sequence: Time.now.to_i,
timestamp_ms: (Time.now.to_f * 1000).to_i,
speaker: speaker,
audio: {
encoding: 'mulaw',
sample_rate_hz: 8000,
payload: audio_base64
}
}.to_json)
end
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/gorilla/websocket"
)
type AudioInput struct {
Type string `json:"type"`
Sequence int64 `json:"sequence"`
TimestampMs int64 `json:"timestamp_ms"`
Speaker string `json:"speaker"`
Audio struct {
Encoding string `json:"encoding"`
SampleRateHz int `json:"sample_rate_hz"`
Payload string `json:"payload"`
} `json:"audio"`
}
func connectToSession(sessionID, streamToken string) (*websocket.Conn, error) {
url := fmt.Sprintf("wss://api.kallglot.com/v1/sessions/%s/connect?token=%s", sessionID, streamToken)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
return nil, err
}
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
return
}
var data map[string]interface{}
json.Unmarshal(message, &data)
switch data["type"] {
case "session.ready":
fmt.Println("Session ready")
case "transcript.partial":
fmt.Printf("[Partial] %s: %s\n", data["speaker"], data["text"])
case "transcript.final":
fmt.Printf("[%s] %s\n", data["speaker"], data["text"])
if trans, ok := data["translation"].(map[string]interface{}); ok {
fmt.Printf(" → %s\n", trans["text"])
}
case "session.ended":
fmt.Printf("Session ended: %s\n", data["reason"])
return
case "error":
fmt.Printf("Error [%s]: %s\n", data["code"], data["message"])
}
}
}()
return conn, nil
}
func sendAudio(conn *websocket.Conn, audioBase64, speaker string) error {
input := AudioInput{
Type: "audio.input",
Sequence: time.Now().Unix(),
TimestampMs: time.Now().UnixMilli(),
Speaker: speaker,
}
input.Audio.Encoding = "mulaw"
input.Audio.SampleRateHz = 8000
input.Audio.Payload = audioBase64
return conn.WriteJSON(input)
}
import asyncio
import websockets
import json
import time
async def connect_to_session(session_id: str, stream_token: str):
url = f"wss://api.kallglot.com/v1/sessions/{session_id}/connect?token={stream_token}"
async with websockets.connect(url) as ws:
async for message in ws:
data = json.loads(message)
if data['type'] == 'session.ready':
print('Session ready')
elif data['type'] == 'transcript.partial':
print(f"[Partial] {data['speaker']}: {data['text']}")
elif data['type'] == 'transcript.final':
print(f"[{data['speaker']}] {data['text']}")
if 'translation' in data:
print(f" → {data['translation']['text']}")
elif data['type'] == 'audio.output':
# Handle translated audio
handle_audio(data['audio']['payload'])
elif data['type'] == 'session.ended':
print(f"Session ended: {data['reason']}")
break
elif data['type'] == 'error':
print(f"Error [{data['code']}]: {data['message']}")
async def send_audio(ws, audio_base64: str, speaker: str):
await ws.send(json.dumps({
'type': 'audio.input',
'sequence': int(time.time()),
'timestamp_ms': int(time.time() * 1000),
'speaker': speaker,
'audio': {
'encoding': 'mulaw',
'sample_rate_hz': 8000,
'payload': audio_base64
}
}))
End the Session
When the conversation is complete, end the session to stop billing:curl -X POST https://api.kallglot.com/v1/sessions/sess_01HXYZ/end \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Idempotency-Key: $(uuidgen)"
def end_session(session_id)
uri = URI("https://api.kallglot.com/v1/sessions/#{session_id}/end")
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV['KALLGLOT_API_KEY']}"
request['Idempotency-Key'] = SecureRandom.uuid
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)
end
session = end_session('sess_01HXYZ')
puts "Duration: #{session['duration_seconds']} seconds"
func endSession(sessionID string) (*SessionResponse, error) {
req, _ := http.NewRequest("POST",
fmt.Sprintf("https://api.kallglot.com/v1/sessions/%s/end", sessionID),
nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KALLGLOT_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.New().String())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var session SessionResponse
json.NewDecoder(resp.Body).Decode(&session)
return &session, nil
}
const response = await fetch(`https://api.kallglot.com/v1/sessions/${sessionId}/end`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KALLGLOT_API_KEY}`,
'Idempotency-Key': crypto.randomUUID()
}
});
const session = await response.json();
console.log(`Duration: ${session.duration_seconds} seconds`);
response = requests.post(
f'https://api.kallglot.com/v1/sessions/{session_id}/end',
headers={
'Authorization': f'Bearer {os.environ["KALLGLOT_API_KEY"]}',
'Idempotency-Key': str(uuid.uuid4())
}
)
session = response.json()
print(f"Duration: {session['duration_seconds']} seconds")
Response
{
"id": "sess_01HXYZ",
"object": "session",
"status": "ended",
"duration_seconds": 312,
"ended_at": "2026-03-10T12:05:44Z"
}
Retrieve the Transcript
After the session ends, retrieve the complete transcript:curl https://api.kallglot.com/v1/sessions/sess_01HXYZ/transcript \
-H "Authorization: Bearer sk_live_your_api_key"
def get_transcript(session_id)
uri = URI("https://api.kallglot.com/v1/sessions/#{session_id}/transcript")
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{ENV['KALLGLOT_API_KEY']}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)
end
transcript = get_transcript('sess_01HXYZ')
transcript['turns'].each do |turn|
puts "[#{turn['speaker']}] #{turn['text']}"
puts " → #{turn['translation']['text']}" if turn['translation']
end
func getTranscript(sessionID string) (map[string]interface{}, error) {
req, _ := http.NewRequest("GET",
fmt.Sprintf("https://api.kallglot.com/v1/sessions/%s/transcript", sessionID),
nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KALLGLOT_API_KEY"))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var transcript map[string]interface{}
json.NewDecoder(resp.Body).Decode(&transcript)
return transcript, nil
}
const response = await fetch(`https://api.kallglot.com/v1/sessions/${sessionId}/transcript`, {
headers: {
'Authorization': `Bearer ${process.env.KALLGLOT_API_KEY}`
}
});
const transcript = await response.json();
transcript.turns.forEach(turn => {
console.log(`[${turn.speaker}] ${turn.text}`);
if (turn.translation) {
console.log(` → ${turn.translation.text}`);
}
});
response = requests.get(
f'https://api.kallglot.com/v1/sessions/{session_id}/transcript',
headers={
'Authorization': f'Bearer {os.environ["KALLGLOT_API_KEY"]}'
}
)
transcript = response.json()
for turn in transcript['turns']:
print(f"[{turn['speaker']}] {turn['text']}")
if 'translation' in turn:
print(f" → {turn['translation']['text']}")
Response
{
"id": "trn_01HXYZ",
"object": "transcript",
"session_id": "sess_01HXYZ",
"status": "completed",
"turns": [
{
"sequence": 1,
"speaker": "customer",
"language": "de",
"text": "Ich möchte meine Bestellung ändern",
"translation": {
"language": "en",
"text": "I want to change my order"
},
"confidence": 0.96,
"timestamp": "2026-03-10T11:58:02Z"
}
],
"metadata": {
"total_turns": 14,
"duration_seconds": 312
}
}
Next Steps
Session Modes
Learn about different translation modes
Webhooks
Set up real-time event notifications
AI Agents
Deploy intelligent voice agents
Error Handling
Handle errors gracefully