> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Connect a SIP trunk number

POST https://api.jelliu.co/api/phone-numbers/connect-sip
Content-Type: application/json

Brings a number the business ALREADY OWNS into the workspace over its SIP trunk, so the agent answers on it (`inbound`) and calls from it (`outbound`). Nothing is bought. The number is registered with the voice provider as `provider: sip_trunk` and takes the same call path as a purchased number. For a plain line without a trunk, use call forwarding instead (`GET /api/phone-numbers/{id}/forwarding-instructions`).

Give at least one of `inbound` / `outbound`; both is the full experience. `inbound` needs either `allowedAddresses` (an IP allow-list) or `credentials` (digest auth), because an open trunk answers to anyone. The agent must exist, be provisioned, and serve voice.

**One number, two channels.** If the number is already this workspace's WhatsApp line, voice is ADDED to that same entry (whether the line is live, offline, or was disconnected) and, while the line has a WhatsApp status, the agent must also serve WhatsApp (400), because one number is answered by one agent. A number that is already a voice number here, or that lives in another workspace, is a 409. The plan's cap counts active voice numbers only, and connecting adds one (402 when full; no add-on arithmetic applies).

**Side effects.** Registers the number and trunk configuration with the voice engine, binds the agent, stores the number (only the outbound trunk address is kept, as `sipHost`; trunk credentials go to the voice provider and are never stored by Jelliu), and writes an audit entry. If binding or storing fails, the registration is deleted before the error is returned.

**Idempotency.** Not idempotent: once the number is connected, a repeat returns 409. Concurrent connects of the same number are serialized by a 2-minute lock (409 for the loser).

**Access**

* **Required scope:** `full`. Signed-in users need the admin or owner role.
* **Rate limit:** General API — per plan: 120 to 600 requests/min per workspace. See [Rate limits](/rate-limits).
* **Plan:** Requires an active plan; counts towards the plan's voice-number cap (Starter 1, Growth 3, Business 10).

Reference: https://developer.jelliu.co/api-reference/phone-numbers/post-phone-numbers-connect-sip

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Request

### Body (application/json)

This endpoint expects an object.

- `agentId` (string, required) — A provisioned voice agent of this workspace. If the number is also the workspace's WhatsApp line, the agent must serve WhatsApp too.
- `phoneNumber` (string, required) — The business's own number, in E.164 format (`phoneNumber must be E.164 (+…)` otherwise).
- `label` (string, optional) — Display name, trimmed. Defaults to `<phoneNumber> (own number)`; when voice is added to the WhatsApp line, the line's existing label is kept unless you send one.
- `inbound` (object, optional) — What the business's PBX or carrier sends to the agent, so the agent answers on the number. Needs a non-empty `allowedAddresses` or `credentials` (400 `inbound needs either allowedAddresses (ACL) or credentials (digest auth) — an open trunk answers to anyone`).
  - `allowedAddresses` (list of string, optional) — IP addresses or CIDR blocks allowed to send calls (up to 50).
  - `allowedNumbers` (list of string, optional) — E.164 caller numbers allowed through the trunk (up to 200).
  - `credentials` (object, optional) — Digest-auth credentials for a SIP trunk. Sent to the voice provider and never stored or returned by Jelliu.
    - `username` (string, required) — SIP username, trimmed.
    - `password` (string, required) — SIP password. Write-only.
  - `mediaEncryption` (enum, optional) — SRTP media encryption: `disabled`, `allowed` (negotiated), or `required`.
    - Allowed values: `disabled`, `allowed`, `required`
  - `remoteDomains` (list of string, optional) — SIP domains the trunk accepts calls from (up to 20).
- `outbound` (object, optional) — Where the agent dials out through, so outbound calls show the business's own number.
  - `address` (string, required) — SIP hostname or IP, optionally with `:port`. Not a URL (`http://` / `https://` is rejected with `address is a SIP host, not a URL`). Returned later as `sipHost`.
  - `transport` (enum, optional) — SIP transport: `auto`, `udp`, `tcp` or `tls`. Provider default when omitted.
    - Allowed values: `auto`, `udp`, `tcp`, `tls`
  - `mediaEncryption` (enum, optional) — SRTP media encryption: `disabled`, `allowed` (negotiated), or `required`.
    - Allowed values: `disabled`, `allowed`, `required`
  - `credentials` (object, optional) — Digest-auth credentials for a SIP trunk. Sent to the voice provider and never stored or returned by Jelliu.
    - `username` (string, required) — SIP username, trimmed.
    - `password` (string, required) — SIP password. Write-only.
  - `headers` (map from string to string, optional) — Custom SIP headers sent on outbound calls. Header names up to 64 characters, values up to 512.
  - `enabledCodecs` (list of enum, optional) — Codecs to offer, up to 3: `G722/8000`, `PCMU/8000`, `PCMA/8000`.
    - Allowed values: `G722/8000`, `PCMU/8000`, `PCMA/8000`

## Response

### 201

The connected number (a new entry, or the WhatsApp line with voice added).

- `data` (object, required) — One number the workspace holds. `channels` says what it answers on; `provider` says where it lives. Field names are camelCase. Returned by every `/api/phone-numbers` endpoint.
  - `id` (string, required) — Unique identifier of the number entry. Use it in `/api/phone-numbers/{id}`.
  - `phoneNumber` (string, required) — The number in E.164 format. Cannot be changed.
  - `label` (string, required, nullable) — Display name. Defaults to `Jelliu <number>` for purchased numbers and `<number> (own number)` for SIP-trunk numbers.
  - `isActive` (boolean, required) — `false` when the number was switched off: it is not used as caller ID, does not accept forwarding and does not count toward the plan cap.
  - `agentId` (string, required, nullable) — The agent that answers calls (and WhatsApp messages, for a WhatsApp line) on this number. Null when unbound.
  - `agentName` (string, required, nullable) — The bound agent's name. Null when unbound.
  - `agentProvisioned` (boolean, required, nullable) — False while the bound agent is still being set up in the voice provider (inbound calls do not route to it yet); null when no agent is bound.
  - `ivrEnabled` (boolean, required) — Whether callers hear the IVR menu before reaching an agent. Only effective on numbers bought through Jelliu.
  - `ivrGreeting` (string, required, nullable) — What the IVR menu says before the options.
  - `ivrOptions` (list of object, required) — The IVR menu, in the order configured. Empty when there is no menu.
    - `digit` (string, required) — Key the caller presses (`0`-`9`, `*` or `#`).
    - `label` (string, required) — Name of the option.
    - `agentId` (string, required, nullable) — Agent the option routes to, when routed to an agent.
    - `department` (string, required, nullable) — Department the option routes to, resolved to an agent at call time.
  - `channels` (list of enum, required) — What the number answers on: `voice`, `whatsapp`, or both. `['whatsapp']` for a WhatsApp-only line; a voice number that is also the WhatsApp line carries both.
    - Allowed values: `voice`, `whatsapp`
  - `whatsappStatus` (string, required, nullable) — The WhatsApp sender's status when the number is a WhatsApp line (e.g. `ONLINE`, `VERIFYING`, `OFFLINE`), else null.
  - `provider` (enum, required) — Where the number lives. `twilio` = bought through Jelliu. `sip_trunk` = the business's own number, reached over its SIP trunk.
    - Allowed values: `twilio`, `sip_trunk`
  - `sipHost` (string, required, nullable) — The outbound trunk address a `sip_trunk` number is reached through. Null for numbers bought through Jelliu and for trunks connected for inbound only.
  - `createdAt` (datetime, required) — When the number was added to the workspace.
  - `updatedAt` (datetime, required) — Last change to the number's configuration.

## Errors

### 400 Bad Request Error

Invalid body (message `Invalid connect request`; `details` = zod `flatten()` output), or the agent does not serve the channels the number needs.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 402 Payment Required Error

The plan's voice-number cap is reached (`BILLING_ERROR`, `metadata` = `tier`, `activeNumbers`, `maxPhoneNumbers`).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

The API key lacks the `full` scope, or the signed-in user is not an admin or owner.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 404 Not Found Error

The agent does not exist in this workspace.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 409 Conflict Error

The number is already a voice number here or belongs to another workspace, the agent is still being provisioned (`AGENT_PROVISIONING`), or the same number is being connected concurrently (`CALL_ALREADY_IN_PROGRESS`).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

Rate limit exceeded for the current 60-second window (`RATE_LIMIT_EXCEEDED`). Wait `Retry-After` seconds, then retry. The `message` differs per limiter; the code does not. See [Rate limits](/rate-limits).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 502 Bad Gateway Error

The voice provider (`VOICE_AI_ERROR`) rejected the registration or the agent binding (message replaced by `Internal server error`). Any registration created is rolled back.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 503 Service Unavailable Error

Connecting numbers is temporarily unavailable because the lock service is down (message replaced by `Internal server error`). Retry.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

### Phone Numbers_postPhoneNumbersConnectSip_example

**Request**

```json
undefined
```

**Response**

```json
{
  "data": {
    "id": "a4c2e6f8-1b3d-4e5f-8a7b-6c5d4e3f2a1b",
    "phoneNumber": "+576015551234",
    "label": "Línea principal",
    "isActive": true,
    "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
    "agentName": "Sofía - Agendamiento",
    "agentProvisioned": true,
    "ivrEnabled": false,
    "ivrGreeting": null,
    "ivrOptions": [],
    "channels": [
      "voice"
    ],
    "whatsappStatus": null,
    "provider": "sip_trunk",
    "sipHost": "sip.example-carrier.com:5060",
    "createdAt": "2026-09-14T16:00:00.000Z",
    "updatedAt": "2026-09-14T16:00:00.000Z"
  }
}
```

**SDK Code**

```python Phone Numbers_postPhoneNumbersConnectSip_example
import requests

url = "https://api.jelliu.co/api/phone-numbers/connect-sip"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript Phone Numbers_postPhoneNumbersConnectSip_example
const url = 'https://api.jelliu.co/api/phone-numbers/connect-sip';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Phone Numbers_postPhoneNumbersConnectSip_example
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/phone-numbers/connect-sip"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Phone Numbers_postPhoneNumbersConnectSip_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/phone-numbers/connect-sip")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Phone Numbers_postPhoneNumbersConnectSip_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/phone-numbers/connect-sip")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Phone Numbers_postPhoneNumbersConnectSip_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/phone-numbers/connect-sip', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Phone Numbers_postPhoneNumbersConnectSip_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/phone-numbers/connect-sip");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Phone Numbers_postPhoneNumbersConnectSip_example
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/phone-numbers/connect-sip")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Answer and call over the trunk

**Request**

```json
{
  "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
  "phoneNumber": "+576015551234",
  "label": "Línea principal",
  "inbound": {
    "allowedAddresses": [
      "203.0.113.10/32"
    ]
  },
  "outbound": {
    "address": "sip.example-carrier.com:5060",
    "transport": "tcp",
    "credentials": {
      "username": "jelliu",
      "password": "s3cr3t-trunk-pass"
    }
  }
}
```

**Response**

```json
{
  "data": {
    "id": "a4c2e6f8-1b3d-4e5f-8a7b-6c5d4e3f2a1b",
    "phoneNumber": "+576015551234",
    "label": "Línea principal",
    "isActive": true,
    "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
    "agentName": "Sofía - Agendamiento",
    "agentProvisioned": true,
    "ivrEnabled": false,
    "ivrGreeting": null,
    "ivrOptions": [],
    "channels": [
      "voice"
    ],
    "whatsappStatus": null,
    "provider": "sip_trunk",
    "sipHost": "sip.example-carrier.com:5060",
    "createdAt": "2026-09-14T16:00:00.000Z",
    "updatedAt": "2026-09-14T16:00:00.000Z"
  }
}
```

**SDK Code**

```python Answer and call over the trunk
import requests

url = "https://api.jelliu.co/api/phone-numbers/connect-sip"

payload = {
    "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
    "phoneNumber": "+576015551234",
    "label": "Línea principal",
    "inbound": { "allowedAddresses": ["203.0.113.10/32"] },
    "outbound": {
        "address": "sip.example-carrier.com:5060",
        "transport": "tcp",
        "credentials": {
            "username": "jelliu",
            "password": "s3cr3t-trunk-pass"
        }
    }
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Answer and call over the trunk
const url = 'https://api.jelliu.co/api/phone-numbers/connect-sip';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"agentId":"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11","phoneNumber":"+576015551234","label":"Línea principal","inbound":{"allowedAddresses":["203.0.113.10/32"]},"outbound":{"address":"sip.example-carrier.com:5060","transport":"tcp","credentials":{"username":"jelliu","password":"s3cr3t-trunk-pass"}}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Answer and call over the trunk
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/phone-numbers/connect-sip"

	payload := strings.NewReader("{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+576015551234\",\n  \"label\": \"Línea principal\",\n  \"inbound\": {\n    \"allowedAddresses\": [\n      \"203.0.113.10/32\"\n    ]\n  },\n  \"outbound\": {\n    \"address\": \"sip.example-carrier.com:5060\",\n    \"transport\": \"tcp\",\n    \"credentials\": {\n      \"username\": \"jelliu\",\n      \"password\": \"s3cr3t-trunk-pass\"\n    }\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Answer and call over the trunk
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/phone-numbers/connect-sip")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+576015551234\",\n  \"label\": \"Línea principal\",\n  \"inbound\": {\n    \"allowedAddresses\": [\n      \"203.0.113.10/32\"\n    ]\n  },\n  \"outbound\": {\n    \"address\": \"sip.example-carrier.com:5060\",\n    \"transport\": \"tcp\",\n    \"credentials\": {\n      \"username\": \"jelliu\",\n      \"password\": \"s3cr3t-trunk-pass\"\n    }\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Answer and call over the trunk
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/phone-numbers/connect-sip")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+576015551234\",\n  \"label\": \"Línea principal\",\n  \"inbound\": {\n    \"allowedAddresses\": [\n      \"203.0.113.10/32\"\n    ]\n  },\n  \"outbound\": {\n    \"address\": \"sip.example-carrier.com:5060\",\n    \"transport\": \"tcp\",\n    \"credentials\": {\n      \"username\": \"jelliu\",\n      \"password\": \"s3cr3t-trunk-pass\"\n    }\n  }\n}")
  .asString();
```

```php Answer and call over the trunk
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/phone-numbers/connect-sip', [
  'body' => '{
  "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
  "phoneNumber": "+576015551234",
  "label": "Línea principal",
  "inbound": {
    "allowedAddresses": [
      "203.0.113.10/32"
    ]
  },
  "outbound": {
    "address": "sip.example-carrier.com:5060",
    "transport": "tcp",
    "credentials": {
      "username": "jelliu",
      "password": "s3cr3t-trunk-pass"
    }
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Answer and call over the trunk
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/phone-numbers/connect-sip");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+576015551234\",\n  \"label\": \"Línea principal\",\n  \"inbound\": {\n    \"allowedAddresses\": [\n      \"203.0.113.10/32\"\n    ]\n  },\n  \"outbound\": {\n    \"address\": \"sip.example-carrier.com:5060\",\n    \"transport\": \"tcp\",\n    \"credentials\": {\n      \"username\": \"jelliu\",\n      \"password\": \"s3cr3t-trunk-pass\"\n    }\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Answer and call over the trunk
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
  "phoneNumber": "+576015551234",
  "label": "Línea principal",
  "inbound": ["allowedAddresses": ["203.0.113.10/32"]],
  "outbound": [
    "address": "sip.example-carrier.com:5060",
    "transport": "tcp",
    "credentials": [
      "username": "jelliu",
      "password": "s3cr3t-trunk-pass"
    ]
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/phone-numbers/connect-sip")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Answer only, authenticated by digest credentials

**Request**

```json
{
  "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
  "phoneNumber": "+525512345678",
  "inbound": {
    "credentials": {
      "username": "pbx-oficina",
      "password": "Pbx-2026-segura"
    },
    "mediaEncryption": "allowed"
  }
}
```

**Response**

```json
{
  "data": {
    "id": "a4c2e6f8-1b3d-4e5f-8a7b-6c5d4e3f2a1b",
    "phoneNumber": "+576015551234",
    "label": "Línea principal",
    "isActive": true,
    "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
    "agentName": "Sofía - Agendamiento",
    "agentProvisioned": true,
    "ivrEnabled": false,
    "ivrGreeting": null,
    "ivrOptions": [],
    "channels": [
      "voice"
    ],
    "whatsappStatus": null,
    "provider": "sip_trunk",
    "sipHost": "sip.example-carrier.com:5060",
    "createdAt": "2026-09-14T16:00:00.000Z",
    "updatedAt": "2026-09-14T16:00:00.000Z"
  }
}
```

**SDK Code**

```python Answer only, authenticated by digest credentials
import requests

url = "https://api.jelliu.co/api/phone-numbers/connect-sip"

payload = {
    "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
    "phoneNumber": "+525512345678",
    "inbound": {
        "credentials": {
            "username": "pbx-oficina",
            "password": "Pbx-2026-segura"
        },
        "mediaEncryption": "allowed"
    }
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Answer only, authenticated by digest credentials
const url = 'https://api.jelliu.co/api/phone-numbers/connect-sip';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"agentId":"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11","phoneNumber":"+525512345678","inbound":{"credentials":{"username":"pbx-oficina","password":"Pbx-2026-segura"},"mediaEncryption":"allowed"}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Answer only, authenticated by digest credentials
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/phone-numbers/connect-sip"

	payload := strings.NewReader("{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+525512345678\",\n  \"inbound\": {\n    \"credentials\": {\n      \"username\": \"pbx-oficina\",\n      \"password\": \"Pbx-2026-segura\"\n    },\n    \"mediaEncryption\": \"allowed\"\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Answer only, authenticated by digest credentials
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/phone-numbers/connect-sip")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+525512345678\",\n  \"inbound\": {\n    \"credentials\": {\n      \"username\": \"pbx-oficina\",\n      \"password\": \"Pbx-2026-segura\"\n    },\n    \"mediaEncryption\": \"allowed\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Answer only, authenticated by digest credentials
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/phone-numbers/connect-sip")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+525512345678\",\n  \"inbound\": {\n    \"credentials\": {\n      \"username\": \"pbx-oficina\",\n      \"password\": \"Pbx-2026-segura\"\n    },\n    \"mediaEncryption\": \"allowed\"\n  }\n}")
  .asString();
```

```php Answer only, authenticated by digest credentials
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/phone-numbers/connect-sip', [
  'body' => '{
  "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
  "phoneNumber": "+525512345678",
  "inbound": {
    "credentials": {
      "username": "pbx-oficina",
      "password": "Pbx-2026-segura"
    },
    "mediaEncryption": "allowed"
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Answer only, authenticated by digest credentials
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/phone-numbers/connect-sip");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"agentId\": \"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11\",\n  \"phoneNumber\": \"+525512345678\",\n  \"inbound\": {\n    \"credentials\": {\n      \"username\": \"pbx-oficina\",\n      \"password\": \"Pbx-2026-segura\"\n    },\n    \"mediaEncryption\": \"allowed\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Answer only, authenticated by digest credentials
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "agentId": "2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11",
  "phoneNumber": "+525512345678",
  "inbound": [
    "credentials": [
      "username": "pbx-oficina",
      "password": "Pbx-2026-segura"
    ],
    "mediaEncryption": "allowed"
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/phone-numbers/connect-sip")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```