Talk to the world's first real-time AI voice astrologer. Real charts.
The world's first real-time AI voice astrologer API. Audio streams in both directions over a clean WebSocket — sub-second turn-taking, barge-in support, multilingual, chart-grounded. One REST call to load your user's chart, one WebSocket for the live conversation. And the only AI voice astrologer API with native two-chart compatibility sessions.
The world's first real-time AI voice astrologer — your users can interrupt mid-sentence.
Every other "voice astrologer" on the market is batch — record an audio file, wait several seconds, get a file back. Ours is genuinely real-time. One REST call loads the user's chart. One WebSocket carries a live, bidirectional PCM audio conversation. The astrologer speaks while thinking. Your user barges in and gets a fresh reply — exactly like a real consultation. Sub-second turn-taking. No third-party accounts, no SDK to bundle, fully white-label.
Sub-second turn-taking
Server-side voice activity detection. The astrologer starts replying as soon as you stop speaking. No round-trips, no batching, no queueing.
One chart or two
Single-chart voice consultations or two-chart kundli-matching voice calls. The astrologer speaks knowing both partners' charts — uncontested among voice astrology APIs.
Fully white-label
No third-party branding, no third-party account, no leaked provider identity. Customers connect to one WebSocket and see your astrologer, your voice, your product.
Real chart consultations, spoken in real time.
The AI voice astrologer carries the same astrological depth as a real consultation. Vedic Jyotish backbone with KP timing precision, Lal Kitab remedies where useful, and two-chart compatibility for matchmaking calls. Topics are as broad as any astrologer's day — relationships, career, finance, health, family, decisions.
𑖀Vedic chart grounding
Birth chart computed once at session start and held server-side for the whole call. Houses, lordships, divisional charts, current Mahadasha and Antardasha, active transits. The astrologer speaks to your user's current moment.
⏱Timing & transits
Vimshottari dasha, sade sati, kantaka shani, retrograde windows. When a user asks "when will my career change," the astrologer can point to specific planetary periods, not vague generalities.
⚯Two-chart voice calls
Native kundli matching by voice. Ashtakoot, Manglik, beeja-kshetra, house overlay — all available to the astrologer in a single conversation. Ask anything about the couple, get answers grounded in both charts.
◈KP & remedial systems
KP for sub-lord precision on timing-critical questions. Lal Kitab for plain, practical remedies. The astrologer applies them when the conversation calls for it — without lecturing.
⟐Topics
- Career, profession, business timing
- Marriage, partner search, matchmaking
- Love & relationship dynamics
- Finance, wealth, investment timing
- Health and wellbeing direction
- Family, children, parenting
- Travel, education, major decisions
🗣Conversation feel
- Sub-second turn-taking
- Barge-in mid-sentence
- Multilingual switching mid-call
- Live transcript while speaking
- Off-topic requests politely declined
- Tone that fits a consultation, not a chatbot
One REST call. One WebSocket. Done.
Phase 1 (REST): POST birth data to /start or /match-start, get a session_id. Phase 2 (WebSocket): open one connection to wss://starsapi.com/voice-relay-v2, authenticate, exchange audio frames. The relay handles VAD, turn-taking, barge-in, language detection, and billing events.
- 1
Create the session (REST)
POST /api/v2/partner/ai/voice/start(single chart) or/match-start(two charts) with the birth data. Server computes the chart, returnssession_id. One HTTP call — done. - 2
Open the WebSocket
Connect to
wss://starsapi.com/voice-relay-v2. Send the first frame:{ "type": "auth", "session_id", "api_key", "name", "lang" }. Wait for{ "type": "ready", "astrologer": {...}, "language" }. - 3
Trigger the greeting
Send
{ "type": "greet", "name", "lang" }(or withperson1_name/person2_namefor couple calls). The astrologer's greeting audio + transcript streams back. Hold mic capture until you receiveassistant.donefor the first turn. - 4
Stream mic audio
Capture mic at 24 kHz mono PCM s16le, base64-encode each chunk, send as
{ "type": "audio.chunk", "data": "BASE64..." }. Server VAD signals when the user starts and stops talking viauser.speech_started/user.speech_stoppedevents. - 5
Play assistant audio + render transcript
Incoming
assistant.audioframes carry base64 PCM (same 24 kHz mono format). Queue and play them in order.assistant.transcriptevents carrydeltastrings for live caption rendering. - 6
Handle barge-in
When
user.speech_startedarrives while assistant audio is still playing, stop your playback and send{ "type": "interrupt" }. The relay replies withassistant.cancelledand starts listening to the new turn. - 7
React to billing events
billing.warningwarns when wallet is getting low.billing.exhaustedmeans the session is being closed — show your top-up flow. No webhook wiring needed.
JSON frames in both directions.
All frames are one-line JSON objects. Audio is base64-encoded PCM (24 kHz mono, signed 16-bit little-endian). Same format inbound and outbound. Every frame has a type field — switch on it to drive your client.
↑ Client → server
- auth{ session_id, api_key, name, lang }
First frame. Establishes the session against the relay. - greet{ name, lang }
Triggers the astrologer's opening response. For couples:{ person1_name, person2_name, lang }. - audio.chunk{ data: BASE64_PCM }
One mic chunk. 24 kHz mono PCM s16le. Send continuously while user speaks. - interrupt{}
Cancel the current assistant response. Pair with stopping local audio playback.
↓ Server → client
- ready{ astrologer: { name }, language }
Auth accepted, session bound, astrologer loaded. - user.speech_started{}
Server VAD detected the user is speaking. - user.speech_stopped{}
VAD detected end of user turn. - user.transcript{ text }
Live transcript of what the user just said. - assistant.speech_started{}
Astrologer is about to speak. Show speaking state in UI. - assistant.audio{ data: BASE64_PCM }
One chunk of astrologer audio. Queue and play in order. - assistant.transcript{ delta }
Incremental caption string for the current astrologer turn. - assistant.done{}
Astrologer finished speaking. Open mic for next user turn. - assistant.cancelled{}
Acknowledgement that a previousinterruptwas applied. - billing.warning{}
Wallet running low — show a soft top-up nudge. - billing.exhausted{}
Wallet empty — session ending. Show top-up flow. - error{ code, message }
Recoverable error or fatal error with a code your client can switch on.
Drop-in code for any stack.
Single-chart flow shown below. Two-chart flow is identical except you POST to /match-start with person1 and person2 objects, and the auth/greet frames carry person1_name and person2_name instead of name. Mic capture and audio playback are platform-specific (24 kHz mono PCM s16le) — only the protocol is shown.
# Phase 1 — create the session (REST)
curl -X POST https://starsapi.com/api/v2/partner/ai/voice/start \
-H "X-Api-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"astrologer": "guruji",
"year": 1990, "month": 6, "day": 15,
"hour": 14, "minute": 30,
"timezone": "Asia/Kolkata",
"latitude": 28.6139, "longitude": 77.2090,
"name": "Ravi",
"gender": "male",
"preferred_language": "en"
}\'
# Returns: data.session_id
# Phase 2 — open the WebSocket (use a tool like websocat for testing)
websocat wss://starsapi.com/voice-relay-v2
# Send auth frame as your first message:
{"type":"auth","session_id":"abc123...","api_key":"YOUR_KEY","name":"Ravi","lang":"en"}
# Wait for: {"type":"ready", ...}
# Then send: {"type":"greet","name":"Ravi","lang":"en"}
# Then stream audio chunks: {"type":"audio.chunk","data":"BASE64_PCM_24KHZ_MONO"}
const BASE = "https://starsapi.com/api/v2/partner/ai/voice";
const RELAY = "wss://starsapi.com/voice-relay-v2";
// Phase 1 — REST: create the session
const r = await fetch(`${BASE}/start`, {
method: "POST",
headers: { "X-Api-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
astrologer: "guruji",
year: 1990, month: 6, day: 15, hour: 14, minute: 30, timezone: "Asia/Kolkata",
latitude: 28.6139, longitude: 77.2090,
name: "Ravi", gender: "male",
preferred_language: "en",
}),
}).then(r => r.json());
const sessionId = r.data.session_id;
// Phase 2 — WebSocket: real-time conversation
const ws = new WebSocket(RELAY);
ws.onopen = () => ws.send(JSON.stringify({
type: "auth", session_id: sessionId, api_key: API_KEY,
name: "Ravi", lang: "en",
}));
ws.onmessage = (e) => {
const f = JSON.parse(e.data);
switch (f.type) {
case "ready": ws.send(JSON.stringify({ type: "greet", name: "Ravi", lang: "en" })); break;
case "user.speech_started": if (assistantPlaying) ws.send(JSON.stringify({ type: "interrupt" })); break;
case "user.transcript": renderUserBubble(f.text); break;
case "assistant.audio": playPcmChunk(base64ToBytes(f.data)); break;
case "assistant.transcript": appendAssistantCaption(f.delta); break;
case "assistant.done": enableMic(); break;
case "billing.exhausted": showTopUp(); break;
case "error": console.error(f.code, f.message); break;
}
};
// Send mic chunks as they arrive (24kHz mono PCM s16le, base64-encoded)
function onMicChunk(int16Pcm) {
ws.send(JSON.stringify({ type: "audio.chunk", data: bytesToBase64(int16Pcm) }));
}
import 'package:http/http.dart' as http;
import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:convert';
const BASE = 'https://starsapi.com/api/v2/partner/ai/voice';
const RELAY = 'wss://starsapi.com/voice-relay-v2';
// Phase 1 — REST: create the session
final r = await http.post(
Uri.parse('$BASE/start'),
headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' },
body: jsonEncode({
'astrologer': 'guruji',
'year': 1990, 'month': 6, 'day': 15,
'hour': 14, 'minute': 30, 'timezone': 'Asia/Kolkata',
'latitude': 28.6139, 'longitude': 77.2090,
'name': 'Ravi',
'gender': 'male',
'preferred_language': 'en',
}),
);
final sessionId = jsonDecode(r.body)['data']['session_id'];
// Phase 2 — WebSocket: real-time conversation
final ws = WebSocketChannel.connect(Uri.parse(RELAY));
ws.sink.add(jsonEncode({
'type': 'auth', 'session_id': sessionId, 'api_key': apiKey,
'name': 'Ravi', 'lang': 'en',
}));
ws.stream.listen((raw) {
final f = jsonDecode(raw);
switch (f['type']) {
case 'ready':
ws.sink.add(jsonEncode({ 'type': 'greet', 'name': 'Ravi', 'lang': 'en' })); break;
case 'user.speech_started':
if (assistantPlaying) ws.sink.add(jsonEncode({ 'type': 'interrupt' })); break;
case 'user.transcript': renderUserBubble(f['text']); break;
case 'assistant.audio': playPcmChunk(base64Decode(f['data'])); break;
case 'assistant.transcript': appendCaption(f['delta']); break;
case 'assistant.done': enableMic(); break;
case 'billing.exhausted': showTopUp(); break;
}
});
// Stream mic — capture 24kHz mono s16le PCM and send base64 chunks
void onMicChunk(Uint8List pcm) {
ws.sink.add(jsonEncode({ 'type': 'audio.chunk', 'data': base64Encode(pcm) }));
}
import requests, json, base64
import websocket # pip install websocket-client
BASE = "https://starsapi.com/api/v2/partner/ai/voice"
RELAY = "wss://starsapi.com/voice-relay-v2"
# Phase 1 — REST: create the session
r = requests.post(f"{BASE}/start",
headers={ "X-Api-Key": API_KEY, "Content-Type": "application/json" },
json={
"astrologer": "guruji",
"year": 1990, "month": 6, "day": 15,
"hour": 14, "minute": 30, "timezone": "Asia/Kolkata",
"latitude": 28.6139, "longitude": 77.2090,
"name": "Ravi",
"gender": "male",
"preferred_language": "en",
}).json()
session_id = r["data"]["session_id"]
# Phase 2 — WebSocket: real-time conversation
def on_message(ws, raw):
f = json.loads(raw)
t = f["type"]
if t == "ready":
ws.send(json.dumps({ "type": "greet", "name": "Ravi", "lang": "en" }))
elif t == "user.speech_started" and assistant_playing:
ws.send(json.dumps({ "type": "interrupt" }))
elif t == "user.transcript": render_user_bubble(f["text"])
elif t == "assistant.audio": play_pcm_chunk(base64.b64decode(f["data"]))
elif t == "assistant.transcript": append_caption(f["delta"])
elif t == "assistant.done": enable_mic()
elif t == "billing.exhausted": show_top_up()
def on_open(ws):
ws.send(json.dumps({
"type": "auth", "session_id": session_id, "api_key": API_KEY,
"name": "Ravi", "lang": "en",
}))
ws = websocket.WebSocketApp(RELAY, on_open=on_open, on_message=on_message)
# Stream mic chunks (24kHz mono s16le PCM, base64-encoded)
def send_mic_chunk(pcm: bytes):
ws.send(json.dumps({ "type": "audio.chunk", "data": base64.b64encode(pcm).decode() }))
ws.run_forever()
// Requires: java-websocket (org.java-websocket:Java-WebSocket) + Jackson.
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import com.fasterxml.jackson.databind.*;
import java.net.URI;
import java.net.http.*;
import java.util.Base64;
static final String BASE = "https://starsapi.com/api/v2/partner/ai/voice";
static final URI RELAY = URI.create("wss://starsapi.com/voice-relay-v2");
static final ObjectMapper M = new ObjectMapper();
// Phase 1 — REST: create the session
String body = M.writeValueAsString(Map.of(
"astrologer", "guruji",
"year", 1990, "month", 6, "day", 15,
"hour", 14, "minute", 30, "timezone", 5.5,
"latitude", 28.6139, "longitude", 77.2090,
"name", "Ravi", "gender", "male",
"preferred_language", "en"
));
HttpResponse<String> rest = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(BASE + "/start"))
.header("X-Api-Key", API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.ofString());
String sessionId = M.readTree(rest.body()).get("data").get("session_id").asText();
// Phase 2 — WebSocket
WebSocketClient ws = new WebSocketClient(RELAY) {
@Override public void onOpen(ServerHandshake h) {
send(M.writeValueAsString(Map.of(
"type","auth","session_id",sessionId,"api_key",API_KEY,
"name","Ravi","lang","en")));
}
@Override public void onMessage(String raw) {
JsonNode f; try { f = M.readTree(raw); } catch(Exception e){ return; }
switch (f.get("type").asText()) {
case "ready": send(M.writeValueAsString(Map.of(
"type","greet","name","Ravi","lang","en"))); break;
case "user.speech_started": if (assistantPlaying) send("{\"type\":\"interrupt\"}"); break;
case "assistant.audio": playPcmChunk(Base64.getDecoder().decode(f.get("data").asText())); break;
case "assistant.transcript":appendCaption(f.get("delta").asText()); break;
case "assistant.done": enableMic(); break;
case "billing.exhausted": showTopUp(); break;
}
}
@Override public void onClose(int c, String r, boolean rem) {}
@Override public void onError(Exception e) {}
};
ws.connect();
// Stream mic — base64 24kHz mono s16le chunks
void onMicChunk(byte[] pcm) throws Exception {
ws.send(M.writeValueAsString(Map.of(
"type", "audio.chunk", "data", Base64.getEncoder().encodeToString(pcm))));
}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"net/http"
"github.com/gorilla/websocket"
)
const (
base = "https://starsapi.com/api/v2/partner/ai/voice"
relay = "wss://starsapi.com/voice-relay-v2"
)
func main() {
// Phase 1 — REST: create the session
payload, _ := json.Marshal(map[string]any{
"astrologer": "guruji",
"year": 1990, "month": 6, "day": 15,
"hour": 14, "minute": 30, "timezone": "Asia/Kolkata",
"latitude": 28.6139, "longitude": 77.2090,
"name": "Ravi", "gender": "male",
"preferred_language": "en",
})
req, _ := http.NewRequest("POST", base+"/start", bytes.NewReader(payload))
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
var startResp struct{ Data struct{ SessionID string `json:"session_id"` } }
json.NewDecoder(resp.Body).Decode(&startResp)
sid := startResp.Data.SessionID
// Phase 2 — WebSocket
ws, _, _ := websocket.DefaultDialer.Dial(relay, nil)
defer ws.Close()
ws.WriteJSON(map[string]any{
"type": "auth", "session_id": sid, "api_key": apiKey,
"name": "Ravi", "lang": "en",
})
go func() {
for {
var f map[string]any
if err := ws.ReadJSON(&f); err != nil { return }
switch f["type"] {
case "ready":
ws.WriteJSON(map[string]any{ "type": "greet", "name": "Ravi", "lang": "en" })
case "user.speech_started":
if assistantPlaying { ws.WriteJSON(map[string]any{ "type": "interrupt" }) }
case "assistant.audio":
pcm, _ := base64.StdEncoding.DecodeString(f["data"].(string))
playPcmChunk(pcm)
case "assistant.transcript":
appendCaption(f["delta"].(string))
case "assistant.done":
enableMic()
case "billing.exhausted":
showTopUp()
}
}
}()
// Stream mic — base64-encoded 24kHz mono s16le
onMicChunk := func(pcm []byte) {
ws.WriteJSON(map[string]any{ "type": "audio.chunk", "data": base64.StdEncoding.EncodeToString(pcm) })
}
_ = onMicChunk
select {}
}
// Requires: cpprestsdk (HTTP) + Boost.Beast or libwebsockets (WS) + base64 helper.
// Sketch using cpprestsdk for HTTP and a generic WS client interface.
#include <cpprest/http_client.h>
#include <cpprest/json.h>
#include "WsClient.h" // your WebSocket wrapper
using namespace web;
using namespace web::http;
using namespace web::http::client;
const auto BASE = U("https://starsapi.com/api/v2/partner/ai/voice");
const auto RELAY = U("wss://starsapi.com/voice-relay-v2");
// Phase 1 — REST
http_client rest(BASE);
json::value body;
body[U("astrologer")] = json::value::string(U("guruji"));
body[U("year")] = 1990; body[U("month")] = 6; body[U("day")] = 15;
body[U("hour")] = 14; body[U("minute")] = 30; body[U("timezone")] = 5.5;
body[U("latitude")] = 28.6139; body[U("longitude")] = 77.2090;
body[U("name")] = json::value::string(U("Ravi"));
body[U("gender")] = json::value::string(U("male"));
body[U("preferred_language")] = json::value::string(U("en"));
http_request req(methods::POST);
req.set_request_uri(U("/start"));
req.headers().add(U("X-Api-Key"), U(API_KEY));
req.headers().add(U("Content-Type"), U("application/json"));
req.set_body(body);
auto resp = rest.request(req).get();
auto json = resp.extract_json().get();
auto sid = json[U("data")][U("session_id")].as_string();
// Phase 2 — WebSocket
WsClient ws(RELAY);
ws.on_open([&] {
json::value auth;
auth[U("type")] = json::value::string(U("auth"));
auth[U("session_id")] = json::value::string(sid);
auth[U("api_key")] = json::value::string(U(API_KEY));
auth[U("name")] = json::value::string(U("Ravi"));
auth[U("lang")] = json::value::string(U("en"));
ws.send(auth.serialize());
});
ws.on_message([&](const std::string& raw) {
auto f = json::value::parse(raw);
auto type = f[U("type")].as_string();
if (type == U("ready")) ws.send(R"({"type":"greet","name":"Ravi","lang":"en"})");
else if (type == U("user.speech_started")) { if (assistantPlaying) ws.send(R"({"type":"interrupt"})"); }
else if (type == U("assistant.audio")) playPcmChunk(base64_decode(f[U("data")].as_string()));
else if (type == U("assistant.transcript")) appendCaption(f[U("delta")].as_string());
else if (type == U("assistant.done")) enableMic();
else if (type == U("billing.exhausted")) showTopUp();
});
ws.connect();
// Stream mic — base64-encoded 24kHz mono s16le PCM
void on_mic_chunk(const std::vector<uint8_t>& pcm) {
ws.send("{\"type\":\"audio.chunk\",\"data\":\"" + base64_encode(pcm) + "\"}");
}
Libraries you need on each platform.
Voice integration needs five capabilities: HTTP for the REST call, WebSocket for the relay, mic capture at 24 kHz mono PCM s16le, audio playback at the same format, and base64 encoding. Below are the recommended libraries for each target — most of them ship in the platform's standard library.
🌐Web / Browser
All native — zero dependencies.
fetch— RESTWebSocket— relaynavigator.mediaDevices.getUserMedia({audio: {sampleRate:24000, channelCount:1, echoCancellation:true, noiseSuppression:true}})AudioContextatsampleRate: 24000+AudioWorkletNode(orScriptProcessor) — mic captureAudioBufferSourceNode— PCM playbackbtoa/atobwithInt16Arrayconversion — base64- HTTPS required for mic access in production
🎯Flutter / Dart
Four pub.dev packages.
http: ^1.0.0— RESTweb_socket_channel: ^3.0.0— relayflutter_sound: ^9.0.0— mic capture and raw PCM playback (both at 24 kHz mono s16le)- Alternative:
mic_stream+just_audioif you prefer separate packages permission_handler: ^11.0.0— runtime mic permissiondart:convertbase64 (built-in)- iOS: add
NSMicrophoneUsageDescriptiontoInfo.plist - Android:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>in manifest
📱iOS native (Swift)
Native Foundation + AVFoundation — no third-party deps.
URLSession— RESTURLSessionWebSocketTask— relay (iOS 13+)AVAudioEngine+AVAudioInputNodewith tap — mic captureAVAudioPlayerNode+ scheduledAVAudioPCMBuffer— PCM playbackAVAudioConverter— sample-rate conversion if hardware ≠ 24 kHzAVAudioSessionset to.playAndRecordwith mode.voiceChat— echo cancellationData.base64EncodedString()— base64- Info.plist:
NSMicrophoneUsageDescription - Minimum iOS 13 (for native WebSocket task)
🤖Android native (Kotlin)
OkHttp + native audio APIs.
com.squareup.okhttp3:okhttp:4.x— REST and WebSocket (same library)AudioRecordwith sourceMediaRecorder.AudioSource.VOICE_COMMUNICATION(built-in echo cancellation) atSAMPLE_RATE_HZ=24000,CHANNEL_IN_MONO,ENCODING_PCM_16BIT— mic captureAudioTrackwithMODE_STREAMat the same format — playbackandroid.util.Base64orjava.util.Base64(API 26+)com.squareup.moshi:moshiorgson— JSON parsing- Manifest:
<uses-permission android:name="android.permission.RECORD_AUDIO"/> - Runtime: request
RECORD_AUDIOpermission before opening the session
⚛React Native
Native fetch + WebSocket, package for PCM streaming.
fetch— REST (native)WebSocket— relay (native)react-native-live-audio-stream— mic at PCM 24 kHz mono- Or bridge native modules (
AVAudioEngine/AudioRecord) for fine-grained control - PCM playback: typically requires a small native module per platform (or wrap chunks into WAV headers and use
react-native-sound) react-native-permissions— mic permission- iOS:
NSMicrophoneUsageDescriptioninInfo.plist - Android:
RECORD_AUDIOin manifest
⚙Server-side bots
Headless callers — transcript loggers, automated consultations, bridge bots.
- Node:
ws(npm) + nativefetch - Python:
websocket-client+requests+ (optionally)pyaudiofor I/O - Go:
github.com/gorilla/websocket+net/http - Java:
org.java-websocket:Java-WebSocket+HttpClient+ Jackson - C++: Boost.Beast or libwebsockets + cpprestsdk or libcurl + nlohmann/json
- Audio I/O usually not needed (transcript-only bots, archivers, IVR bridges) — you can just consume
user.transcriptandassistant.transcriptevents
Audio format — the one constant across every platform
Mic input and assistant output are both 24 kHz, mono, signed 16-bit linear PCM, little-endian, base64-encoded inside JSON frames. If your platform captures at 44.1 kHz or 48 kHz (most do natively), you'll need a one-line resampler — every audio library above supports this. Echo cancellation should be enabled on the input device wherever the platform offers it (VOICE_COMMUNICATION on Android, .voiceChat mode on iOS, echoCancellation: true in getUserMedia on web).
Session bootstrap payload & response.
One POST creates the session and returns the session_id you use on the WebSocket. The chart is computed and cached server-side for the entire call.
// Request
{
"astrologer": "guruji",
"year": 1990, "month": 6, "day": 15,
"hour": 14, "minute": 30,
"timezone": "Asia/Kolkata",
"latitude": 28.6139,
"longitude": 77.2090,
"name": "Ravi", // optional
"gender": "male", // optional
"preferred_language": "en", // optional, ISO 639-1
"device_language": "en-US", // optional
"country_code": "IN" // optional
}
// Response
{
"success": true,
"timestamp": "2026-05-17T13:45:00+00:00",
"response_time_ms": 412,
"data": {
"session_id": "abc123def456abc123def456abc12345",
"turn": 0,
"astrologer": {
"key": "guruji",
"name": "Guruji",
"avatar_emoji": "🙏",
"astrology_system": "vedic"
},
"language": { "resolved": "en", "hint": "en" },
"billing": { /* current wallet state */ }
}
}
// Request
{
"astrologer": "matchmaking",
"preferred_language": "hi",
"boy": {
"name": "Raj", "gender": "male",
"year": 1985, "month": 6, "day": 15,
"hour": 10, "minute": 30,
"latitude": 28.6139, "longitude": 77.2090,
"timezone": "Asia/Kolkata"
},
"girl": {
"name": "Priya", "gender": "female",
"year": 1990, "month": 8, "day": 22,
"hour": 14, "minute": 45,
"latitude": 19.0760, "longitude": 72.8777,
"timezone": "Asia/Kolkata"
}
}
// Response — same envelope, includes both partner names
{
"success": true,
"data": {
"session_id": "xyz789...",
"turn": 0,
"astrologer": { "key": "matchmaking", "name": "...", ... },
"language": { "resolved": "hi", "hint": "hi" },
"boy": { "name": "Raj" },
"girl": { "name": "Priya" },
"billing": { ... }
}
}
{
"success": false,
"timestamp": "...",
"error": {
"code": "SESSION_NOT_FOUND",
"message": "Session not found"
}
}
// Codes:
// INVALID_JSON 400 — body not parseable
// METHOD_NOT_ALLOWED 405 — only POST on REST endpoints
// AUTH_ERROR 401 — API key validation failed
// VALIDATION_ERROR 400 — missing/invalid field
// SESSION_NOT_FOUND 404 — session_id unknown
// FORBIDDEN 403 — session belongs to a different API key
// SESSION_CLOSED 409 — session marked inactive
// CALCULATION_ERROR 500 — chart computation failed
// AI_ERROR 502 — upstream voice service failed; safe to retry
// CONFIG_ERROR 500 — astrologer config not found
// INTERNAL_ERROR 500 — unhandled server error
Questions developers ask before integrating.
How does the integration work end-to-end?
/api/v2/partner/ai/voice/start (or /match-start for couple sessions) with skip_ai_greeting: true. Server computes the chart, returns a session_id. Step 2: Open a WebSocket to wss://starsapi.com/voice-relay-v2, send an auth frame with the session ID, then exchange audio and event frames. That's the whole integration. What does the WebSocket protocol look like?
auth, greet, audio.chunk (base64 PCM), and interrupt. Inbound from server: ready, user.speech_started/stopped, user.transcript, assistant.speech_started, assistant.audio, assistant.transcript, assistant.done, assistant.cancelled, plus billing and error events. Every frame is a one-line JSON object — easy to debug, easy to log. What audio format does the relay use?
How fast is the turn-taking?
user.speech_started and user.speech_stopped in real time, so your UI can react immediately. Does it support barge-in (interrupt mid-sentence)?
{"type": "interrupt"} frame. The relay stops the current astrologer audio, fires an assistant.cancelled event back, and starts listening fresh. Stop playback on your client when you send the interrupt; the relay confirms the cancellation with the event. What about two-chart kundli-matching voice calls?
/match-start instead of /start, passing boy and girl birth objects. The session loads both charts plus the compatibility analysis (Ashtakoot scores, Manglik analysis, beeja-kshetra, house overlay). On the WebSocket auth frame, send boy_name and girl_name instead of user_name. Same audio protocol, same event types. The astrologer speaks knowing both partners. What astrological systems does the voice astrologer use?
Is it multilingual?
lang on the auth frame (and preferred_language on the REST start call). The astrologer greets in that language and detects language switches mid-conversation from the user's speech. English, Hindi, and others on request. Is it fully white-label?
wss://starsapi.com/voice-relay-v2 — there's no third-party branding, no third-party account on your side, no leaked provider identity. The protocol is provider-agnostic by design. Display the astrologer's name, voice, and identity as your own product. How is billing handled?
billing.warning when wallet is getting low and billing.exhausted when it runs out — your UI can react to both before the session is force-closed. No webhook wiring needed; the relay handles it. Ship an AI voice astrologer this week.
One REST call, one WebSocket. Sub-second turn-taking. Two-chart compatibility voice. Fully white-label.
View pricing Talk to us