You need an SLNG API key and an agent created via the API or Agent Infra. See Configuring voice agents for system prompt and tool setup.
Outbound calls require the agent to be configured with an outbound SIP trunk (sip_outbound_trunk_id).
Configure this in the Dashboard Telephony page and attach it to your agent.
Basic Call Dispatch
Send a single outbound call by providing the agent ID and a phone number in E.164 format.
const API_KEY = "your-api-key";
const BASE_URL = "https://api.agents.slng.ai";
async function dispatchCall(agentId, phoneNumber, args = {}) {
const response = await fetch(`${BASE_URL}/v1/agents/${agentId}/calls`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
phone_number: phoneNumber,
arguments: args,
}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(`Failed to dispatch call: ${error.error || error.detail || response.status}`);
}
return response.json();
}
// Basic call
const result = await dispatchCall(
"550e8400-e29b-41d4-a716-446655440000",
"+14155551234",
);
console.log(`Call dispatched: ${result.call_id}`);
Calls with Template Variables
If your agent’s system prompt or greeting contains {{variables}}, pass them via the arguments field. These override any template_defaults set on the agent.
// Agent system_prompt uses: {{patient_name}}, {{practice_name}}
const result = await dispatchCall(
"550e8400-e29b-41d4-a716-446655440000",
"+14155551234",
{
patient_name: "Maria",
practice_name: "Greenfield Family Medicine",
},
);
Batch Call Dispatch
When you need to call a list of contacts, you can loop through them one at a time or fire multiple calls in parallel with a concurrency cap.
Sequential Dispatch
async function dispatchBatch(agentId, contacts) {
const results = [];
for (const contact of contacts) {
try {
const result = await dispatchCall(agentId, contact.phone, contact.args);
results.push({
phone: contact.phone,
success: true,
call_id: result.call_id,
});
// Small delay between calls to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 500));
} catch (error) {
results.push({
phone: contact.phone,
success: false,
error: error.message,
});
}
}
return results;
}
// Usage
const contacts = [
{
phone: "+14155551234",
args: { patient_name: "Maria", practice_name: "Greenfield Family Medicine" },
},
{
phone: "+14155555678",
args: { patient_name: "John", practice_name: "Greenfield Family Medicine" },
},
{
phone: "+14155559012",
args: { patient_name: "Jane", practice_name: "Greenfield Family Medicine" },
},
];
const results = await dispatchBatch(
"550e8400-e29b-41d4-a716-446655440000",
contacts,
);
console.log("Batch results:", results);
Parallel Dispatch (with concurrency limit)
Process calls in chunks to stay within rate limits while dispatching faster than sequential.
async function dispatchBatchParallel(agentId, contacts, concurrency = 5) {
const results = [];
// Process in chunks
for (let i = 0; i < contacts.length; i += concurrency) {
const chunk = contacts.slice(i, i + concurrency);
const chunkResults = await Promise.allSettled(
chunk.map((contact) =>
dispatchCall(agentId, contact.phone, contact.args)
.then((result) => ({
phone: contact.phone,
success: true,
call_id: result.call_id,
}))
.catch((error) => ({
phone: contact.phone,
success: false,
error: error.message,
})),
),
);
results.push(...chunkResults.map((r) => r.value || r.reason));
}
return results;
}
Common Use Cases
Outbound voice agents work well for automated calls that follow a predictable structure. Common examples include:
- Appointment reminders — confirm or reschedule upcoming visits
- Payment collection — notify customers of outstanding balances
- Survey and feedback — gather post-purchase or post-service input
- Lead qualification — follow up on inbound interest and route warm leads to sales
Use template variables to personalize each call with contact-specific details like names, dates, and amounts.