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.

4 min read

Base URL
https://voice-api.edesy.in/v1
Auth
Authorization: Bearer sk_...Get a key
POSThttps://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 Required Destination in E.164, for example +9198XXXXXXXX
from string Required 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 Optional Voice ID for text
language string Optional 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 Optional 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
stream_sample_rate integer Optional 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
status_callback string (URL) Optional Receives lifecycle events. See Status Callbacks
status_method string Optional POST (default, JSON body) or GET (query parameters)
timeout integer Optional Ring timeout in seconds, 5 to 600. Default 30
caller_name string Optional Display name, up to 50 characters
machine_detection string Optional 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
custom_parameters object Optional 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#

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)#

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

{
  "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. 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 and test your endpoint before going live.

3. Inline VoiceML#

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#

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.

5. Saved application#

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.

Response 201#

{
  "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.

Was this page helpful?