# Calls

> Place outbound calls from your Indian number, stream them to your agent, check call status, hang up, and receive status webhooks.

Source: https://edesy.in/docs/sip-trunk-for-ai-voice-agents/api-reference/calls

Place an outbound call from a number you own, then track it with status webhooks or by polling.

| Method | Path | What it does |
|--------|------|--------------|
| `POST` | `/calls` | Place an outbound call |
| `GET` | `/calls/{callSid}` | Get a call |
| `DELETE` | `/calls/{callSid}` | Hang up |
| `POST` `GET` `DELETE` | `/Accounts/{accountSid}/Calls[/{callSid}]` | The same three endpoints, account-scoped |

The `/Accounts/{accountSid}/Calls` routes behave exactly like `/calls` and return the same response. `accountSid` is your account SID (`AC_…`) or workspace ID; any other value returns `403 FORBIDDEN`.

Base URL `https://voice-api.edesy.in/v1`, Bearer API key. See [Authentication](https://edesy.in/docs/sip-trunk-for-ai-voice-agents/getting-started/authentication).

## Place a call

`POST /calls`

The `from` number must be one you own and have [registered for outbound](https://edesy.in/docs/sip-trunk-for-ai-voice-agents/api-reference/phone-numbers#register-for-outbound).

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `from` | string | Yes | Your number, E.164 |
| `to` | string or object | Yes | E.164, or `{ "type": "phone", "number": "+91…" }` |
| `stream_url` | string | One of | Stream call audio to your WebSocket agent |
| `application_id` | string | One of | A saved application; `application_sid` is accepted as an alias |
| `url` | string | One of | A URL that returns call instructions (`method`: `GET` or `POST`) |
| `text` | string | One of | Text to speak (max 2000 characters), with optional `voice` and `language` |
| `stream_sample_rate` | integer | No | `8000` or `16000`, for `stream_url` |
| `status_callback` | string | No | URL for [status webhooks](#status-webhooks) |
| `status_method` | string | No | `POST` (default) or `GET` |
| `timeout` | integer | No | Seconds to ring, 5 to 600 |
| `caller_name` | string | No | Max 50 characters |
| `machine_detection` | string | No | `Enable` or `DetectMessageEnd` |
| `custom_parameters` | object | No | Your own key-values, returned with the call; `tag` is an alias |

Send exactly one of `stream_url`, `application_id`, `url` or `text`. Unknown fields are ignored and listed in `meta.ignored_fields`, so check that list if a setting seems to have no effect.

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer vp_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+917969002802",
    "to": "+919812345678",
    "stream_url": "wss://agent.example.com/ws",
    "status_callback": "https://example.com/hooks/call-status",
    "timeout": 60,
    "custom_parameters": { "lead_id": "L-5521" }
  }'
```

Response `201`:

```json
{
  "data": {
    "call_sid": "f4590c42-1a14-4a5f-b526-32f2a4c09018",
    "sid": "f4590c42-1a14-4a5f-b526-32f2a4c09018",
    "account_sid": "AC_1a2b3c4d",
    "from": "+917969002802",
    "to": "+919812345678",
    "direction": "outbound",
    "status": "queued",
    "created_at": "2026-10-08T09:31:02Z",
    "custom_parameters": { "lead_id": "L-5521" }
  },
  "meta": { "request_id": "req_8c1d", "timestamp": "2026-10-08T09:31:02Z" }
}
```

`sid` is the same value as `call_sid`. Once known, the call also carries `answered_at`, `ended_at`, `duration`, `price` (`amount`, `currency`), `hangup_cause` and `answered_by`.

## Get a call

`GET /calls/{callSid}` returns the same `data` object with the latest status.

## Hang up

`DELETE /calls/{callSid}` ends an active call and returns `204` with no body. A call that has already ended returns `409 CALL_ENDED`.

## Status webhooks

Set `status_callback` on the call, or save one on the application, and the platform reports each status change:

| `CallStatus` | Meaning |
|--------------|---------|
| `initiated` | The call is being set up |
| `ringing` | The callee's phone is ringing |
| `in-progress` | Answered |
| `completed` | Ended normally |
| `busy` | The line was busy |
| `no-answer` | Not answered before `timeout` |
| `failed` | The call couldn't be connected |
| `stream-failed` | The audio stream to your `stream_url` failed; `ErrorMessage` says why |

`POST` sends JSON and `GET` sends query parameters:

```json
{
  "CallSid": "f4590c42-1a14-4a5f-b526-32f2a4c09018",
  "From": "+917969002802",
  "To": "+919812345678",
  "CallStatus": "in-progress",
  "Direction": "outbound",
  "Timestamp": "2026-10-08T09:31:09Z",
  "Custom": { "lead_id": "L-5521" }
}
```

`Duration` is added when the call ends. Match events to calls by `CallSid`, or by your `custom_parameters` in `Custom`.

- Delivery times out after 30 seconds and is retried up to 5 times, so make your handler idempotent.
- Requests carry `User-Agent: VoicePlatform-Webhook/1.0`.
- If a signing secret is set on your account, each request also carries `X-Webhook-Timestamp` and `X-Webhook-Signature: v1=<hex>`, an HMAC-SHA256 of `"<timestamp>.<body>"`. Recompute it and reject mismatches.

## Errors

| HTTP | `code` | When |
|------|--------|------|
| 400 | `VALIDATION_ERROR` | Invalid body or no call instructions; `details.errors` lists each field |
| 402 | `INSUFFICIENT_BALANCE` | Wallet balance is too low |
| 403 | `NUMBER_NOT_OWNED` | `from` isn't a number on your account |
| 403 | `FORBIDDEN` | `accountSid` in the path isn't yours |
| 404 | `NOT_FOUND` | No call with that SID |
| 409 | `CALL_ENDED` | The call has already ended |
| 503 | `SERVICE_UNAVAILABLE` | No carrier capacity right now; retry with backoff |

See [Error Codes](https://edesy.in/docs/sip-trunk-for-ai-voice-agents/api-reference/errors) for the error format.
