# Make a Call

> POST /v1/calls - place an outbound call and tell it what to do with text, an answer URL, inline VoiceML, a WebSocket stream or a saved application. Every parameter explained.

Source: https://edesy.in/docs/voice-api/calls/make-a-call

`POST https://voice-api.edesy.in/v1/calls`

`POST /v1/calls` dials `to` from a number you own and runs one instruction source when the callee answers. There are five sources: `text` (speak a message), `url` (fetch VoiceML from your server), `voiceml` (inline VoiceML), `stream_url` (stream audio to your bot) and `application_id` (use a saved application). Send exactly one.

## Five ways to drive the call

| Source | Use it when | Minimal body |
|---|---|---|
| `text` | You only need to speak a message | `{"from","to","text"}` |
| `url` | Your server decides what happens, per call | `{"from","to","url"}` |
| `voiceml` | You know the steps up front and want no webhook | `{"from","to","voiceml"}` |
| `stream_url` | Your own voice bot (Pipecat or custom) handles the audio | `{"from","to","stream_url"}` |
| `application_id` | You reuse a saved routing preset | `{"from","to","application_id"}` |

If you send none of them, Edesy uses the voice URL configured on the `from` number. If that is not set either, the request fails with `400 VALIDATION_ERROR`.

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `to` | string | yes | Destination in E.164, for example `+9198XXXXXXXX` |
| `from` | string | yes | A number in your workspace, E.164. Used as the caller ID |
| `text` | string | one source | Up to 2,000 characters, spoken with the default voice after answer |
| `voice` | string | no | Voice ID for `text` |
| `language` | string | no | Language for `text`, for example `en-IN` or `hi-IN` |
| `url` | string (URL) | one source | Answer URL. Fetched when the callee answers; must return VoiceML JSON |
| `method` | string | no | `GET` or `POST` for the answer URL. Edesy currently requests outbound answer URLs with `POST` |
| `voiceml` | string | one source | A VoiceML document as a JSON string, for example `"{\"instructions\":[...]}"` |
| `stream_url` | string (URI) | one source | `wss://` endpoint for a bidirectional media stream. See [Media Streams](https://edesy.in/docs/voice-api/streaming/media-streams) |
| `stream_sample_rate` | integer | no | `16000` (default) or `8000`. Must match your bot |
| `application_id` | string (UUID) | one source | A saved application. `application_sid` is accepted as an alias. See [Applications](https://edesy.in/docs/voice-api/calls/applications) |
| `status_callback` | string (URL) | no | Receives lifecycle events. See [Status Callbacks](https://edesy.in/docs/voice-api/calls/status-callbacks) |
| `status_method` | string | no | `POST` (default, JSON body) or `GET` (query parameters) |
| `timeout` | integer | no | Ring timeout in seconds, 5 to 600. Default 30 |
| `caller_name` | string | no | Display name, up to 50 characters |
| `machine_detection` | string | no | `Enable` or `DetectMessageEnd`. Accepted for Twilio compatibility. Answering-machine detection with a recorded verdict runs on campaigns today; see [AMD, Fallback Caller ID and DNC](https://edesy.in/docs/voice-api/campaigns/amd-fallback-caller-id-and-dnc) |
| `custom_parameters` | object | no | Your metadata, up to 20 keys and 2 KB. Echoed back on webhooks and in the media-stream metadata frame. `tag` is accepted as an alias |

`to` may also be sent as an object, `{"type": "phone", "number": "+9198XXXXXXXX"}`, for code copied from other platforms. Unknown fields are not an error: they are ignored and listed in `meta.ignored_fields` so you can spot a typo.

## 1. Speak text

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+9180XXXXXXXX",
    "to": "+9198XXXXXXXX",
    "text": "नमस्ते, आपका ऑर्डर भेज दिया गया है।",
    "language": "hi-IN"
  }'
```

## 2. Answer URL (VoiceML webhook)

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+9180XXXXXXXX",
    "to": "+9198XXXXXXXX",
    "url": "https://example.com/edesy/answer",
    "status_callback": "https://example.com/edesy/status",
    "custom_parameters": { "customer_id": "C-881" }
  }'
```

When the callee answers, Edesy sends a form-encoded `POST` to your `url`:

```
POST /edesy/answer
Content-Type: application/x-www-form-urlencoded

CallSid=CA_6f1c...&From=%2B9180XXXXXXXX&To=%2B9198XXXXXXXX&Direction=outbound-api&customer_id=C-881
```

The fields are `CallSid`, `AccountSid`, `From`, `To`, `CallStatus` and `Direction`, plus each of your `custom_parameters` as its own field. Reply with `200` and a VoiceML JSON body (up to 1 MB):

```json
{
  "instructions": [
    { "verb": "say", "text": "Hi, this is Acme. Your order is out for delivery." },
    { "verb": "play", "url": "https://cdn.example.com/jingle.mp3" },
    { "verb": "hangup" }
  ]
}
```

The answer URL must return VoiceML, not TwiML. See the [VoiceML Reference](https://edesy.in/docs/voice-api/calls/voiceml). If your URL fails, times out, returns a non-2xx status or returns invalid VoiceML, the callee hears a short spoken error message and the call ends. Check the call's [event timeline](https://edesy.in/docs/voice-api/calls/call-events-and-outcomes) and test your endpoint before going live.

## 3. Inline VoiceML

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+9180XXXXXXXX",
    "to": "+9198XXXXXXXX",
    "voiceml": "{\"instructions\":[{\"verb\":\"say\",\"text\":\"Your appointment is tomorrow at 10 AM.\"},{\"verb\":\"pause\",\"length\":1},{\"verb\":\"say\",\"text\":\"Thank you.\"},{\"verb\":\"hangup\"}]}"
  }'
```

`voiceml` is a string, so escape the inner quotes, or build it in code with `JSON.stringify(doc)` / `json.dumps(doc)`.

## 4. Stream to your bot

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+9180XXXXXXXX",
    "to": "+9198XXXXXXXX",
    "stream_url": "wss://bot.example.com/ws",
    "stream_sample_rate": 16000,
    "custom_parameters": { "assistant_id": "asst_456" }
  }'
```

When the callee answers, Edesy opens a WebSocket to your bot and streams raw L16 PCM both ways. Run `POST /v1/stream-test` first to check that your bot is reachable. See [Media Streams](https://edesy.in/docs/voice-api/streaming/media-streams).

## 5. Saved application

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+9180XXXXXXXX",
    "to": "+9198XXXXXXXX",
    "application_id": "8d5c1e2a-0000-4000-8000-000000000000"
  }'
```

The application's webhook or media-stream target drives the call, and its status callback and recording settings apply. Only applications whose target is a webhook or a media stream can drive an outbound call. See [Applications](https://edesy.in/docs/voice-api/calls/applications).

## Response `201`

```json
{
  "data": {
    "call_sid": "CA_6f1c2d3e-0000-4000-8000-000000000000",
    "sid": "CA_6f1c2d3e-0000-4000-8000-000000000000",
    "account_sid": "2b7e0a1c-0000-4000-8000-000000000000",
    "from": "+9180XXXXXXXX",
    "to": "+9198XXXXXXXX",
    "direction": "outbound-api",
    "status": "queued",
    "custom_parameters": { "customer_id": "C-881" },
    "created_at": "2026-10-28T09:30:00Z"
  },
  "meta": { "request_id": "req_...", "timestamp": "2026-10-28T09:30:00Z" }
}
```

## Errors

| HTTP | Code | Cause |
|---|---|---|
| 400 | `VALIDATION_ERROR` | Missing `to`/`from`, more than one instruction source, no source at all, or a field out of range |
| 401 | `UNAUTHORIZED` | Missing or invalid credentials |
| 402 | `INSUFFICIENT_BALANCE` | Wallet too low to place the call |
| 403 | `NUMBER_NOT_OWNED` | `from` is not a number in your workspace |
| 429 | `CONCURRENCY_LIMIT` / `CPS_LIMIT` | All channels in use, or calls per second exceeded. Wait for `Retry-After` seconds |
| 503 | `SERVICE_UNAVAILABLE` | No route available right now; retry shortly |

See [Error Codes](https://edesy.in/docs/voice-api/calls/errors).
