Trigger Outbound Call via API
Use the Edesy Calls API to programmatically trigger outbound voice calls from your application, CRM, or automation workflow.
Prerequisites
Before you begin, make sure you have:
- An API Key - Generate one from Settings > API Keys in the Edesy Dashboard
- An Agent ID - The voice agent that will handle the call. Find it in Agents page in the dashboard
- A Phone Number - Either a platform-provided number or your own (BYOP)
Quick Start
Make a single API call to trigger an outbound call:
curl -X POST https://voice-agent.edesy.in/api/v1/calls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"agentId": 123,
"phoneNumber": "9876543210"
}'
That is it. The agent will call the number and start the conversation using its configured prompt, language, and voice.
API Reference
Endpoint
POST /api/v1/calls
Authentication
Include your API key in the Authorization header:
Authorization: Bearer vp_live_xxxxxxxxxxxxxxxx
Your API key starts with vp_live_ (production) or vp_test_ (testing). The workspace is automatically identified from your API key - you do not need to pass a workspace ID.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
agentId |
number | Yes | ID of the voice agent to use for the call |
phoneNumber |
string | Yes | Phone number to call (with or without country code) |
fromNumber |
string | No | Caller ID / from number. If not provided, uses the default number for your workspace |
credentialId |
string | No | ID of a saved telephony credential (BYOP) to use for this call. Selects which stored Twilio / Plivo / Exotel / SIP credential — and the phone number tied to it — should place the call. Useful when your workspace has multiple credentials saved |
provider |
string | No | Telephony provider (twilio, plivo, edesy-sip). Defaults to the agent's configured provider |
variables |
object | No | Dynamic variables to inject into the agent's prompt |
metadata |
object | No | Custom metadata to attach to the call record |
callbackUrl |
string | No | Webhook URL to receive call events (must be HTTPS in production) |
Choosing the from number: fromNumber vs credentialId
Both fields control which number the call originates from, but they work at different levels:
fromNumber— a literal phone number string (e.g."8065934975"). The platform finds a matching saved credential in your workspace and uses it. Use this when you know the number you want to call from.credentialId— the unique ID of a saved credential record. Use this when you have multiple credentials sharing the same number, or when you want to pin a specific provider account (e.g. a particular Twilio sub-account). You can find credential IDs under Phone Numbers in the dashboard.
If you provide both, credentialId takes precedence for the auth context, and fromNumber is used as the displayed caller ID. If you provide neither, the agent's default configured number is used.
Response
Success (200)
{
"success": true,
"data": {
"conversationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"callSid": "edesy-sip-xxxxxxxx",
"status": "initiated"
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | MISSING_AGENT_ID |
agentId is required |
| 400 | MISSING_PHONE_NUMBER |
phoneNumber is required |
| 401 | MISSING_API_KEY |
No API key provided |
| 401 | INVALID_KEY_FORMAT |
API key format is incorrect |
| 401 | INVALID_API_KEY |
API key not found or expired |
| 402 | CALL_FAILED |
Insufficient credits |
Error response format:
{
"success": false,
"error": "Missing agentId",
"code": "MISSING_AGENT_ID"
}
Examples
Basic Call
Trigger a call with just the agent and phone number:
curl -X POST https://voice-agent.edesy.in/api/v1/calls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vp_live_your_api_key_here" \
-d '{
"agentId": 123,
"phoneNumber": "9876543210"
}'
Call with Variables
Pass dynamic data that the agent can use during the conversation. Variables are injected into the agent's prompt context:
curl -X POST https://voice-agent.edesy.in/api/v1/calls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vp_live_your_api_key_here" \
-d '{
"agentId": 123,
"phoneNumber": "9876543210",
"variables": {
"customer_name": "Rahul",
"order_id": "ORD-12345",
"amount_due": "2500"
}
}'
Tip: Reference variables in your agent's system prompt using the variable name. For example: "The customer's name is {customer_name} and their order ID is {order_id}."
Call with Specific From Number
Specify which phone number the call should come from:
curl -X POST https://voice-agent.edesy.in/api/v1/calls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vp_live_your_api_key_here" \
-d '{
"agentId": 123,
"phoneNumber": "9876543210",
"fromNumber": "8065934975",
"provider": "edesy-sip"
}'
Call with a Specific Saved Credential
If your workspace has multiple telephony credentials saved (for example, two Twilio sub-accounts or several SIP trunks), use credentialId to choose exactly which one places the call. The phone number associated with that credential is used as the caller ID automatically.
curl -X POST https://voice-agent.edesy.in/api/v1/calls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vp_live_your_api_key_here" \
-d '{
"agentId": 123,
"phoneNumber": "9876543210",
"credentialId": "clxyz123abc456def789ghi"
}'
Where to find credential IDs: Open the Edesy Dashboard, go to Phone Numbers, and select a saved number. The credential ID is shown in the details panel.
Call with Webhook Callback
Get notified when the call ends by providing a callback URL:
curl -X POST https://voice-agent.edesy.in/api/v1/calls \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vp_live_your_api_key_here" \
-d '{
"agentId": 123,
"phoneNumber": "9876543210",
"callbackUrl": "https://your-server.com/call-webhook",
"variables": {
"customer_name": "Priya"
}
}'
The callback URL will receive a POST request when the call ends with details like duration, transcript, and outcome.
Code Examples
Python
import requests
API_KEY = "vp_live_your_api_key_here"
BASE_URL = "https://voice-agent.edesy.in/api/v1"
response = requests.post(
f"{BASE_URL}/calls",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"agentId": 123,
"phoneNumber": "9876543210",
"variables": {
"customer_name": "Rahul",
"order_id": "ORD-12345",
},
},
)
data = response.json()
if data.get("success"):
print(f"Call initiated: {data['data']['conversationId']}")
else:
print(f"Error: {data.get('error')}")
Node.js
const API_KEY = "vp_live_your_api_key_here";
const BASE_URL = "https://voice-agent.edesy.in/api/v1";
const response = await fetch(`${BASE_URL}/calls`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
agentId: 123,
phoneNumber: "9876543210",
variables: {
customer_name: "Rahul",
order_id: "ORD-12345",
},
}),
});
const data = await response.json();
if (data.success) {
console.log(`Call initiated: ${data.data.conversationId}`);
} else {
console.error(`Error: ${data.error}`);
}
PHP
$apiKey = "vp_live_your_api_key_here";
$baseUrl = "https://voice-agent.edesy.in/api/v1";
$ch = curl_init("$baseUrl/calls");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer $apiKey",
],
CURLOPT_POSTFIELDS => json_encode([
"agentId" => 123,
"phoneNumber" => "9876543210",
"variables" => [
"customer_name" => "Rahul",
"order_id" => "ORD-12345",
],
]),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
if ($data["success"]) {
echo "Call initiated: " . $data["data"]["conversationId"];
} else {
echo "Error: " . $data["error"];
}
PHP (Guzzle)
Most modern PHP projects use Guzzle for HTTP. Install with composer require guzzlehttp/guzzle:
<?php
require "vendor/autoload.php";
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$apiKey = "vp_live_your_api_key_here";
$baseUrl = "https://voice-agent.edesy.in/api/v1";
$client = new Client([
"base_uri" => $baseUrl,
"headers" => [
"Authorization" => "Bearer $apiKey",
"Content-Type" => "application/json",
],
"timeout" => 30,
]);
try {
$response = $client->post("/calls", [
"json" => [
"agentId" => 123,
"phoneNumber" => "9876543210",
"variables" => [
"customer_name" => "Rahul",
"order_id" => "ORD-12345",
],
],
]);
$data = json_decode($response->getBody()->getContents(), true);
if ($data["success"]) {
echo "Call initiated: " . $data["data"]["conversationId"];
} else {
echo "Error: " . $data["error"];
}
} catch (RequestException $e) {
$body = $e->hasResponse() ? $e->getResponse()->getBody()->getContents() : "";
echo "Request failed: " . $e->getMessage() . " " . $body;
}
Tip: in Laravel, you can skip Guzzle setup entirely and use the built-in Http facade — Http::withToken($apiKey)->post("$baseUrl/calls", [...]) returns a fluent response object with ->successful(), ->json(), and ->failed() helpers.
TypeScript
A typed version of the Node.js example. Drop into any TypeScript backend (Express, NestJS, Next.js route handlers, etc.):
const API_KEY = "vp_live_your_api_key_here";
const BASE_URL = "https://voice-agent.edesy.in/api/v1";
interface TriggerCallResponse {
success: boolean;
data?: {
conversationId: string;
callSid: string;
status: string;
};
error?: string;
code?: string;
}
async function triggerCall(): Promise<TriggerCallResponse> {
const response = await fetch(`${BASE_URL}/calls`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
agentId: 123,
phoneNumber: "9876543210",
variables: {
customer_name: "Rahul",
order_id: "ORD-12345",
},
}),
});
return (await response.json()) as TriggerCallResponse;
}
const result = await triggerCall();
if (result.success && result.data) {
console.log(`Call initiated: ${result.data.conversationId}`);
} else {
console.error(`Error: ${result.error}`);
}
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
const (
apiKey = "vp_live_your_api_key_here"
baseURL = "https://voice-agent.edesy.in/api/v1"
)
type TriggerCallResponse struct {
Success bool `json:"success"`
Error string `json:"error,omitempty"`
Data struct {
ConversationID string `json:"conversationId"`
CallSid string `json:"callSid"`
Status string `json:"status"`
} `json:"data"`
}
func main() {
payload := map[string]any{
"agentId": 123,
"phoneNumber": "9876543210",
"variables": map[string]string{
"customer_name": "Rahul",
"order_id": "ORD-12345",
},
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/calls", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Request failed:", err)
return
}
defer resp.Body.Close()
var result TriggerCallResponse
json.NewDecoder(resp.Body).Decode(&result)
if result.Success {
fmt.Println("Call initiated:", result.Data.ConversationID)
} else {
fmt.Println("Error:", result.Error)
}
}
Java
Uses the built-in java.net.http.HttpClient (Java 11+) so there are no extra dependencies:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TriggerCall {
private static final String API_KEY = "vp_live_your_api_key_here";
private static final String BASE_URL = "https://voice-agent.edesy.in/api/v1";
public static void main(String[] args) throws Exception {
String payload = """
{
"agentId": 123,
"phoneNumber": "9876543210",
"variables": {
"customer_name": "Rahul",
"order_id": "ORD-12345"
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/calls"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}
For richer parsing, add Jackson or Gson and deserialize the response into a POJO with success, data.conversationId, and error fields.
C# / .NET
Uses HttpClient from System.Net.Http (built into .NET 6+):
using System.Net.Http.Json;
using System.Net.Http.Headers;
const string ApiKey = "vp_live_your_api_key_here";
const string BaseUrl = "https://voice-agent.edesy.in/api/v1";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", ApiKey);
var payload = new
{
agentId = 123,
phoneNumber = "9876543210",
variables = new
{
customer_name = "Rahul",
order_id = "ORD-12345"
}
};
var response = await client.PostAsJsonAsync($"{BaseUrl}/calls", payload);
var result = await response.Content.ReadFromJsonAsync<TriggerCallResponse>();
if (result?.Success == true)
{
Console.WriteLine($"Call initiated: {result.Data?.ConversationId}");
}
else
{
Console.WriteLine($"Error: {result?.Error}");
}
record TriggerCallResponse(bool Success, CallData? Data, string? Error, string? Code);
record CallData(string ConversationId, string CallSid, string Status);
Ruby
require "net/http"
require "json"
require "uri"
API_KEY = "vp_live_your_api_key_here"
BASE_URL = "https://voice-agent.edesy.in/api/v1"
uri = URI("#{BASE_URL}/calls")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer #{API_KEY}"
request.body = {
agentId: 123,
phoneNumber: "9876543210",
variables: {
customer_name: "Rahul",
order_id: "ORD-12345"
}
}.to_json
response = http.request(request)
data = JSON.parse(response.body)
if data["success"]
puts "Call initiated: #{data["data"]["conversationId"]}"
else
puts "Error: #{data["error"]}"
end
List Calls
Retrieve call history for your workspace:
curl "https://voice-agent.edesy.in/api/v1/calls" \
-H "Authorization: Bearer vp_live_your_api_key_here"
Query Parameters
| Parameter | Type | Description |
|---|---|---|
agentId |
number | Filter calls by agent |
status |
string | Filter by status (initiated, completed, failed) |
phoneNumber |
string | Filter by recipient phone number |
startDate |
string | Filter calls after this date (ISO 8601) |
endDate |
string | Filter calls before this date (ISO 8601) |
limit |
number | Number of results (default: 50, max: 100) |
offset |
number | Pagination offset |
Example: Get Recent Calls for an Agent
curl "https://voice-agent.edesy.in/api/v1/calls?agentId=123&limit=10" \
-H "Authorization: Bearer vp_live_your_api_key_here"
Response
{
"success": true,
"data": {
"calls": [
{
"conversationId": "a1b2c3d4-...",
"callSid": "edesy-sip-xxxxxxxx",
"agentId": 123,
"agentName": "Sales Agent",
"phoneNumber": "9876543210",
"status": "completed",
"duration": 120,
"source": "api",
"startTime": "2026-04-10T06:25:00.000Z",
"endTime": "2026-04-10T06:27:00.000Z"
}
],
"total": 1,
"limit": 10,
"offset": 0
}
}
Bulk Calls
To trigger calls to multiple numbers, make separate API requests for each number. For large-scale campaigns (100+ contacts), we recommend using the Campaigns feature in the dashboard instead.
import requests
import time
API_KEY = "vp_live_your_api_key_here"
BASE_URL = "https://voice-agent.edesy.in/api/v1"
contacts = [
{"phone": "9876543210", "name": "Rahul", "order_id": "ORD-001"},
{"phone": "9876543211", "name": "Priya", "order_id": "ORD-002"},
{"phone": "9876543212", "name": "Amit", "order_id": "ORD-003"},
]
for contact in contacts:
response = requests.post(
f"{BASE_URL}/calls",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"agentId": 123,
"phoneNumber": contact["phone"],
"variables": {
"customer_name": contact["name"],
"order_id": contact["order_id"],
},
},
)
data = response.json()
status = "OK" if data.get("success") else data.get("error")
print(f"{contact['name']}: {status}")
# Add a small delay between calls to avoid rate limits
time.sleep(1)
Finding Your Agent ID
- Go to the Edesy Dashboard
- Navigate to Agents
- Click on the agent you want to use
- The Agent ID is shown in the URL:
voice-agent.edesy.in/agents/{AGENT_ID}
Generating an API Key
- Go to Settings > API Keys in the dashboard
- Click Generate API Key
- Copy the key immediately - it will not be shown again
- Store it securely (environment variable, secrets manager, etc.)
Security: Never expose your API key in client-side code or public repositories. Always call the API from your backend server.
Troubleshooting
"Invalid API key" error
- Make sure the key starts with
vp_live_ - Check that you are using the
Authorization: Bearerheader format - Verify the key has not been revoked in Settings > API Keys
"Insufficient credits" error
- Check your credit balance in Billing > Credits
- Purchase more credits or upgrade your plan
Call not connecting
- Verify the phone number is valid and reachable
- Check if the
fromNumberis configured correctly for your provider - If using
credentialId, confirm the credential is active and not revoked under Phone Numbers in the dashboard - Ensure your agent is set to Active in the dashboard
No response from API
- Check that you are using
POSTmethod (notGET) - Verify the
Content-Type: application/jsonheader is included - Make sure the request body is valid JSON
Want us to build it for you?
The code samples above let you wire your voice agent into any CRM yourself. If you would rather have it done for you — built, deployed, and maintained by our team — we offer two related flat-priced services:
- CRM Integration Service — Native CRM connectors for HubSpot, Salesforce, Zoho, GoHighLevel, Pipedrive, Freshsales, LeadSquared, and Zoho Bigin. From ₹45,000 one-time + ₹2,000/month maintenance. Delivered in 7 working days.
- Voice Agent Build Service — Done-for-you voice agent development on Vapi, Retell, Bolna, Bland, Synthflow, Voiceflow, or our own Edesy platform. Prompt engineering, telephony, function calling included. From ₹75,000 one-time + ₹5,000/month maintenance. Delivered in 2 weeks.
Per-CRM landing pages with use cases, data flow, and pricing:
- HubSpot Voice Agent Integration
- Salesforce Voice Agent Integration
- Zoho CRM Voice Agent Integration
- GoHighLevel Voice Agent Integration
- Pipedrive Voice Agent Integration
- Freshsales Voice Agent Integration
- LeadSquared Voice Agent Integration
- Zoho Bigin Voice Agent Integration
Next Steps
- Webhook Subscriptions - Get notified on call events
- Function Calling - Let your agent call external APIs during conversations
- Variables & Templates - Personalize conversations with dynamic data
- Campaigns - Run large-scale outbound calling campaigns
- CRM Integration Service - Done-for-you native CRM connectors