Buying a number gives your tenant ownership of it. Registering it on the carrier enables outbound caller-ID, and binding inbound routes incoming calls to your agent. Each step is a separate call, so you can change inbound routing without touching outbound.
All endpoints use the base URL https://voice-api.edesy.in/v1 and a Bearer API key. See Authentication.
| Method | Path | What it does |
|---|---|---|
POST |
/dids/purchase-live |
Buy an Indian DID |
GET |
/phone-numbers |
List your numbers |
GET |
/phone-numbers/:id/telephony |
Read a number's live wiring state |
POST |
/phone-numbers/:id/carrier |
Register for outbound calling |
DELETE |
/phone-numbers/:id/carrier |
Deregister |
PUT |
/phone-numbers/:id/inbound |
Bind inbound calls |
DELETE |
/phone-numbers/:id/inbound |
Unbind inbound calls (outbound stays on) |
Buy a number#
POST /dids/purchase-live
The easiest way to pick and buy a number is the portal at voice-app.edesy.in. Through the API, send the live_ref of the number you picked from the available-numbers listing. KYC must be complete before a purchase goes through.
| Field | Type | Required | Notes |
|---|---|---|---|
live_ref |
string | Yes | Opaque reference for the number you picked |
friendly_name |
string | No | Your label for the number |
months |
integer | No | Billing months to prepay |
acknowledged_total |
number | No | The total you accepted; the purchase fails with QUOTE_STALE if the price changed |
curl -X POST https://voice-api.edesy.in/v1/dids/purchase-live \
-H "Authorization: Bearer vp_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "live_ref": "LIVE_REF_FROM_LISTING", "friendly_name": "Support line" }'
Response 201:
{
"data": {
"phone_number_id": "8f1c2e4a-5b6d-4c7e-9f80-1a2b3c4d5e6f",
"number": "+917969002802",
"total_charged": 1000,
"currency": "INR",
"new_wallet_balance": 4000,
"next_billing_at": "2026-11-08T00:00:00Z"
}
}
The amounts above are illustrative. Use phone_number_id as :id in the calls below.
| HTTP | code |
When |
|---|---|---|
| 400 | VALIDATION_ERROR |
Missing or invalid field |
| 402 | INSUFFICIENT_BALANCE |
Wallet balance is too low |
| 404 | NOT_FOUND |
The number is no longer listed |
| 409 | NUMBER_NOT_OWNED |
Someone else just bought this number |
| 409 | QUOTE_STALE |
The price changed since you saw it |
List your numbers#
GET /phone-numbers
| Query | Default | Notes |
|---|---|---|
status |
Filter by status | |
number_type |
Filter by type | |
region |
Filter by region | |
page |
1 |
|
per_page |
20 |
curl https://voice-api.edesy.in/v1/phone-numbers \
-H "Authorization: Bearer vp_YOUR_API_KEY"
Each number includes id (the UUID used as :id below), number in E.164 (e.g. +919876543210), friendly_name, status, number_type, voice_enabled, sms_enabled, monthly_cost, inbound_target_type, created_at and updated_at. meta carries total, page, per_page and total_pages.
Read the wiring state#
GET /phone-numbers/:id/telephony
curl https://voice-api.edesy.in/v1/phone-numbers/$ID/telephony \
-H "Authorization: Bearer vp_YOUR_API_KEY"
{
"data": {
"phone_number_id": "8f1c2e4a-5b6d-4c7e-9f80-1a2b3c4d5e6f",
"number": "+917969002802",
"carrier_registered": true,
"outbound_capable": true,
"inbound_bound": true,
"inbound_target": "voice_agent",
"agent_id": "a1b2c3d4-0000-4000-8000-000000000001",
"workspace_id": "w1b2c3d4-0000-4000-8000-000000000002"
}
}
| Field | Meaning |
|---|---|
carrier_registered |
The number is registered on the carrier |
outbound_capable |
You can use the number as caller-ID (same as carrier_registered) |
inbound_bound |
Inbound calls are routed somewhere |
inbound_target |
Where inbound goes: voice_agent, webhook or media_stream |
agent_id, workspace_id |
The bound agent, when inbound_target is voice_agent |
The register, bind and unbind calls below return this same object.
Register for outbound#
POST /phone-numbers/:id/carrier
No body. The call is idempotent, so it is safe to repeat. After it succeeds, outbound_capable is true and you can use the number as from in Calls.
curl -X POST https://voice-api.edesy.in/v1/phone-numbers/$ID/carrier \
-H "Authorization: Bearer vp_YOUR_API_KEY"
DELETE /phone-numbers/:id/carrier deregisters the number and returns {"data": {"status": "deregistered"}}. If inbound is still bound, pass ?force=true or unbind first.
Bind inbound calls#
PUT /phone-numbers/:id/inbound
Choose one target. Binding also registers the number on the carrier if it isn't registered yet.
target |
Required fields | Optional fields |
|---|---|---|
voice_agent |
agent_id, workspace_id |
|
webhook |
webhook_url |
webhook_method (default POST), status_webhook_url |
media_stream |
stream_url (must start with ws:// or wss://) |
sample_rate (8000 or 16000) |
Pass "force": true to take over a number whose inbound is bound elsewhere.
# Stream inbound audio to your own WebSocket agent
curl -X PUT https://voice-api.edesy.in/v1/phone-numbers/$ID/inbound \
-H "Authorization: Bearer vp_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "target": "media_stream", "stream_url": "wss://agent.example.com/ws", "sample_rate": 8000 }'
DELETE /phone-numbers/:id/inbound removes the binding. Outbound calling keeps working.
Errors#
The wiring endpoints (/telephony, /carrier, /inbound) use lowercase codes:
| HTTP | code |
When |
|---|---|---|
| 400 | validation |
:id is not a valid UUID, or the body is invalid |
| 400 | bind_failed |
The inbound binding was rejected; check the target fields |
| 404 | not_found |
The number doesn't exist or isn't yours |
| 409 | inbound_conflict |
Inbound is bound elsewhere; retry with "force": true |
| 409 | inbound_bound |
Inbound is still bound; unbind first or pass force=true |
| 503 | not_configured |
Telephony provisioning is temporarily unavailable |
See Error Codes for the error format.