# Phone Numbers

> Buy an Indian DID, list your numbers, register a number for outbound calling, and bind inbound calls to a voice agent, a webhook or a WebSocket media stream.

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

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](https://edesy.in/docs/sip-trunk-for-ai-voice-agents/getting-started/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](https://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 |

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

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

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

```bash
curl https://voice-api.edesy.in/v1/phone-numbers/$ID/telephony \
  -H "Authorization: Bearer vp_YOUR_API_KEY"
```

```json
{
  "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](https://edesy.in/docs/sip-trunk-for-ai-voice-agents/api-reference/calls).

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

```bash
# 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](https://edesy.in/docs/sip-trunk-for-ai-voice-agents/api-reference/errors) for the error format.
