# Trigger Outbound Call via API

> Step-by-step guide to trigger outbound calls programmatically using the Edesy Voice Agent API.

Source: https://edesy.in/docs/voice-agent/api/trigger-call

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:

1. **An API Key** - Generate one from **Settings > API Keys** in the [Edesy Dashboard](https://voice-agent.edesy.in)
2. **An Agent ID** - The voice agent that will handle the call. Find it in **Agents** page in the dashboard
3. **A Phone Number** - Either a platform-provided number or your own (BYOP)

---

## Quick Start

Make a single API call to trigger an outbound call:

```bash
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)**

```json
{
  "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:

```json
{
  "success": false,
  "error": "Missing agentId",
  "code": "MISSING_AGENT_ID"
}
```

---

## Examples

### Basic Call

Trigger a call with just the agent and phone number:

```bash
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:

```bash
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:

```bash
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.

```bash
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](https://voice-agent.edesy.in), 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:

```bash
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

```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

```javascript
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

```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](https://docs.guzzlephp.org/) for HTTP. Install with `composer require guzzlehttp/guzzle`:

```php
<?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.):

```typescript
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

```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:

```java
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+):

```csharp
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

```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:

```bash
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

```bash
curl "https://voice-agent.edesy.in/api/v1/calls?agentId=123&limit=10" \
  -H "Authorization: Bearer vp_live_your_api_key_here"
```

### Response

```json
{
  "success": true,
  "data": {
    "calls": [
      {
        "conversationId": "a1b2c3d4-...",
        "callSid": "edesy-sip-xxxxxxxx",
        "agentId": 123,
        "agentName": "Sales Agent",
        "phoneNumber": "9876543210",
        "status": "completed",
        "duration": 120,
        "recordingUrl": "https://voice-agent.edesy.in/api/public/recordings/a1b2c3d4-...?token=...",
        "source": "api",
        "startTime": "2026-04-10T06:25:00.000Z",
        "endTime": "2026-04-10T06:27:00.000Z",
        "metadata": null
      }
    ],
    "total": 1,
    "limit": 10,
    "offset": 0
  }
}
```

### Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `conversationId` | string | Unique ID of the call. Use this to fetch full details |
| `callSid` | string \| null | Telephony provider's call identifier |
| `agentId` | number \| null | Agent that handled the call |
| `agentName` | string \| null | Agent display name |
| `phoneNumber` | string \| null | Recipient phone number |
| `status` | string | `initiated`, `in-progress`, or `completed` |
| `duration` | number \| null | Call duration in seconds |
| `recordingUrl` | string \| null | Playable recording URL (permanent link, or `null` if no recording) |
| `source` | string \| null | How the call was created (`api`, `campaign`, `inbound`, etc.) |
| `startTime` | string \| null | Call start time (ISO 8601) |
| `endTime` | string \| null | Call end time (ISO 8601) |
| `metadata` | object \| null | Custom metadata attached when the call was created |

---

## Get Call Details

Retrieve the full details of a single call — including the recording, AI-generated summary, and full transcript:

```bash
curl "https://voice-agent.edesy.in/api/v1/calls/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer vp_live_your_api_key_here"
```

### Endpoint

```
GET /api/v1/calls/{conversation_id}
```

### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `conversation_id` | string | The `conversationId` returned by the trigger or list endpoints |

### Response

```json
{
  "success": true,
  "data": {
    "conversationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "callSid": "edesy-sip-xxxxxxxx",
    "agentId": 123,
    "agentName": "Sales Agent",
    "phoneNumber": "9876543210",
    "status": "completed",
    "duration": 120,
    "disposition": "QUALIFIED",
    "summary": "Customer confirmed their order ORD-12345 and requested delivery on Friday.",
    "recordingUrl": "https://voice-agent.edesy.in/api/public/recordings/a1b2c3d4-...?token=...",
    "source": "api",
    "startTime": "2026-04-10T06:25:00.000Z",
    "endTime": "2026-04-10T06:27:00.000Z",
    "transcript": [
      {
        "role": "agent",
        "text": "Hello, am I speaking with Rahul?",
        "timestamp": "2026-04-10T06:25:02.000Z"
      },
      {
        "role": "user",
        "text": "Yes, that's me.",
        "timestamp": "2026-04-10T06:25:04.000Z"
      }
    ],
    "fullText": "Agent: Hello, am I speaking with Rahul?\nUser: Yes, that's me.",
    "metadata": null,
    "extraction": {
      "customer_name": "Rahul",
      "order_id": "ORD-12345"
    }
  }
}
```

### Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `conversationId` | string | Unique ID of the call |
| `callSid` | string \| null | Telephony provider's call identifier |
| `agentId` | number \| null | Agent that handled the call |
| `agentName` | string \| null | Agent display name |
| `phoneNumber` | string \| null | Recipient phone number |
| `status` | string | `initiated`, `in-progress`, or `completed` |
| `duration` | number \| null | Call duration in seconds |
| `disposition` | string \| null | Call outcome classification: `QUALIFIED`, `NOT_QUALIFIED`, `CALLBACK_REQUESTED`, `NO_ANSWER`, `VOICEMAIL`, `TRANSFERRED`, or `CUSTOM` |
| `summary` | string \| null | AI-generated post-call summary. `null` until the summary has been generated |
| `recordingUrl` | string \| null | Playable recording URL (permanent link), or `null` if no recording |
| `source` | string \| null | How the call was created (`api`, `campaign`, `inbound`, etc.) |
| `startTime` | string \| null | Call start time (ISO 8601) |
| `endTime` | string \| null | Call end time (ISO 8601) |
| `transcript` | array | Ordered turns: `{ role: "agent" \| "user", text, timestamp }` |
| `fullText` | string | Flattened transcript (`"Agent: ...\nUser: ..."`) for quick display |
| `metadata` | object \| null | Custom metadata attached when the call was created |
| `extraction` | object \| null | Structured data extracted from the call, if configured on the agent |

> 💡 **Tip:** The `summary` and `transcript` are populated after the call completes and post-call processing finishes. If you fetch details immediately after a call ends, these fields may still be `null` or empty — poll again after a few seconds.

### Code Examples

#### Python

```python
import requests

API_KEY = "vp_live_your_api_key_here"
BASE_URL = "https://voice-agent.edesy.in/api/v1"
conversation_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

response = requests.get(
    f"{BASE_URL}/calls/{conversation_id}",
    headers={"Authorization": f"Bearer {API_KEY}"},
)
data = response.json()

if data["success"]:
    call = data["data"]
    print(f"Status:    {call['status']}")
    print(f"Summary:   {call['summary']}")
    print(f"Recording: {call['recordingUrl']}")
    for turn in call["transcript"]:
        print(f"{turn['role']}: {turn['text']}")
else:
    print(f"Error: {data['error']}")
```

#### Node.js

```javascript
const API_KEY = "vp_live_your_api_key_here";
const BASE_URL = "https://voice-agent.edesy.in/api/v1";
const conversationId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";

const response = await fetch(`${BASE_URL}/calls/${conversationId}`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
});
const { success, data, error } = await response.json();

if (success) {
  console.log("Status:   ", data.status);
  console.log("Summary:  ", data.summary);
  console.log("Recording:", data.recordingUrl);
  data.transcript.forEach((turn) => {
    console.log(`${turn.role}: ${turn.text}`);
  });
} else {
  console.error("Error:", error);
}
```

---

## 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](https://edesy.in/docs/voice-agent/campaigns) feature in the dashboard instead.

```python
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

1. Go to the [Edesy Dashboard](https://voice-agent.edesy.in)
2. Navigate to **Agents**
3. Click on the agent you want to use
4. The Agent ID is shown in the URL: `voice-agent.edesy.in/agents/{AGENT_ID}`

## Generating an API Key

1. Go to **Settings > API Keys** in the dashboard
2. Click **Generate API Key**
3. Copy the key immediately - it will not be shown again
4. 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: Bearer` header 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 `fromNumber` is 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 `POST` method (not `GET`)
- Verify the `Content-Type: application/json` header 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](https://edesy.in/services/voice-agent-crm-integration)** — 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](https://edesy.in/services/voice-agent-development)** — 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](https://edesy.in/services/voice-agent-crm-integration/hubspot)
- [Salesforce Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/salesforce)
- [Zoho CRM Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/zoho-crm)
- [GoHighLevel Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/gohighlevel)
- [Pipedrive Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/pipedrive)
- [Freshsales Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/freshsales)
- [LeadSquared Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/leadsquared)
- [Zoho Bigin Voice Agent Integration](https://edesy.in/services/voice-agent-crm-integration/zoho-bigin)

---

## Next Steps

- [Webhook Subscriptions](https://edesy.in/docs/voice-agent/api/webhook-subscriptions) - Get notified on call events
- [Function Calling](https://edesy.in/docs/voice-agent/features/function-calling) - Let your agent call external APIs during conversations
- [Variables & Templates](https://edesy.in/docs/voice-agent/features/variables) - Personalize conversations with dynamic data
- [Campaigns](https://edesy.in/docs/voice-agent/campaigns) - Run large-scale outbound calling campaigns
- [CRM Integration Service](https://edesy.in/services/voice-agent-crm-integration) - Done-for-you native CRM connectors
