# Примеры использования REST API

Работа с REST API построена по стандарту OpenAPI. Для получения актуального списка эндпоинтов используйте раздел «Документация» внутри АТС. Ниже приведены примеры работы с основными возможностями REST интерфейса MikoPBX.

{% hint style="info" %}
Если у вас отсутствует доверенный сертификат — добавьте `verify=False` в каждый запрос и отключите предупреждения:

```python
import urllib3
urllib3.disable_warnings()
```

Настоятельно рекомендуется выпустить доверенный сертификат. Самый простой способ сделать это — с помощью [модуля Let's Encrypt](https://docs.mikopbx.com/mikopbx/modules/miko/module-get-ssl-lets-encrypt).
{% endhint %}

### Подключение

Для выполнения всех примеров из этой инструкции создайте API-ключ и настройте следующие права доступа (подробнее в [общей статье](https://docs.mikopbx.com/mikopbx/manual/system/api-keys/broken-reference)):

| Ресурс               | Уровень доступа | Для каких примеров                        |
| -------------------- | --------------- | ----------------------------------------- |
| Employees Management | Чтение и запись | Создание и редактирование сотрудников     |
| Providers            | Чтение и запись | Создание и редактирование провайдеров     |
| SIP                  | Чтение          | Статусы регистрации сотрудников и транков |
| Call Records         | Чтение          | История звонков (CDR)                     |
| PBX Status           | Чтение          | Активные звонки в реальном времени        |
| SIP Providers        | Чтение и запись | Создание и редактирование SIP провайдеров |

<figure><img src="https://3704471835-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPK4TuzRBnP7rt8htho-887967055%2Fuploads%2FFSO60Kt7tJwTL2EYxa8N%2FAPIKeyCallRecords.png?alt=media&#x26;token=b90db42f-b05c-440d-8986-bd46b36e234b" alt=""><figcaption><p>Пример настройки прав доступа (разрешение Call Records)</p></figcaption></figure>

В этой статье, мы будем работать с Python, поэтому необходимо установить необходимые зависимости:

```bash
pip install requests
```

Ниже приведён шаблон подключения к станции через API-ключ. Используйте его перед всеми скриптами из этой инструкции. API-ключ передаётся напрямую в заголовке запроса — никакой дополнительной аутентификации не требуется:

```python
import requests

BASE_URL = 'https://your-mikopbx.com/pbxcore/api/v3'
API_KEY  = 'ваш-api-ключ'

HEADERS = {
    'Authorization': f'Bearer {API_KEY}',
    'Content-Type':  'application/json',
}
```

{% hint style="info" %}
В шаблоне замените следующие параметры:

* `your-mikopbx.com` — на IP-адрес или URL вашей станции.
* `ваш-api-ключ` — на ранее созданный API-ключ с необходимыми правами.
  {% endhint %}

### Работа с сотрудниками

**Эндпоинт:** `POST /pbxcore/api/v3/employees`

Ниже приведена таблица с параметрами для такого запроса.

<table><thead><tr><th width="254.3671875">Поле</th><th width="97.87109375">Обяз.</th><th width="164.66796875">Тип / ограничения</th><th>Описание</th></tr></thead><tbody><tr><td><code>number</code></td><td>✅</td><td>string, 2–8 цифр</td><td>Добавочный номер</td></tr><tr><td><code>user_username</code></td><td>✅</td><td>string, 1–100 символов</td><td>ФИО сотрудника</td></tr><tr><td><code>sip_secret</code></td><td>✅</td><td>string, 5–100 символов</td><td>Пароль SIP-аккаунта</td></tr><tr><td><code>user_email</code></td><td>—</td><td>string email, ≤255</td><td>Email для уведомлений</td></tr><tr><td><code>mobile_number</code></td><td>—</td><td>string E.164, ≤50</td><td>Мобильный (+7...) для переадресации</td></tr><tr><td><code>mobile_dialstring</code></td><td>—</td><td>string, ≤255</td><td>Строка набора мобильного</td></tr><tr><td><code>sip_transport</code></td><td>—</td><td><code>udp</code> / <code>tcp</code> / <code>tls</code> / <code>udp,tcp</code></td><td>Транспорт SIP (по умолч.: <code>udp</code>)</td></tr><tr><td><code>sip_dtmfmode</code></td><td>—</td><td><code>auto</code> / <code>rfc4733</code> / <code>inband</code> / <code>info</code></td><td>Режим DTMF (по умолч.: <code>auto</code>)</td></tr><tr><td><code>sip_enableRecording</code></td><td>—</td><td>boolean</td><td>Запись разговоров (по умолч.: <code>true</code>)</td></tr><tr><td><code>sip_networkfilterid</code></td><td>—</td><td>number | <code>"none"</code></td><td>ID сетевого фильтра</td></tr><tr><td><code>sip_manualattributes</code></td><td>—</td><td>string, ≤1024</td><td>Дополнительные SIP-параметры</td></tr><tr><td><code>fwd_ringlength</code></td><td>—</td><td>integer, ≤180</td><td>Время дозвона до переадресации (сек, по умолч.: 45)</td></tr><tr><td><code>fwd_forwarding</code></td><td>—</td><td>number | <code>hangup</code> | <code>busy</code></td><td>Безусловная переадресация</td></tr><tr><td><code>fwd_forwardingonbusy</code></td><td>—</td><td>number | <code>hangup</code> | <code>busy</code></td><td>Переадресация при занятости</td></tr><tr><td><code>fwd_forwardingonunavailable</code></td><td>—</td><td>number | <code>hangup</code> | <code>busy</code></td><td>Переадресация при недоступности</td></tr></tbody></table>

#### Создание одного сотрудника

```python
def create_employee(
    number: str,
    name: str,
    sip_secret: str,
    email: str = '',
    mobile: str = '',
    record_calls: bool = True,
    fwd_ringlength: int = 45,
) -> dict:
    payload = {
        'number':              number,
        'user_username':       name,
        'sip_secret':          sip_secret,
        'sip_enableRecording': record_calls,
        'fwd_ringlength':      fwd_ringlength,
    }
    if email:  payload['user_email']    = email
    if mobile: payload['mobile_number'] = mobile

    r = requests.post(f'{BASE_URL}/employees', headers=HEADERS, json=payload)
    result = r.json()
    if result.get('result'):
        print(f" Создан: {number} ({name}), id={result['data']['id']}")
    else:
        print(f" Ошибка: {result.get('messages', {}).get('error', [])}")
    return result


# Минимальный пример (только обязательные поля)
create_employee(
    number='243',
    name='Иванов Иван',
    sip_secret='Secure#Pass9201',
)

# Полный пример
create_employee(
    number='244',
    name='Петрова Анна',
    sip_secret='Secure#Pass9202',
    email='anna@company.ru',
    mobile='79001234567',
    record_calls=True,
    fwd_ringlength=30,
)
```

Пример ответа API (HTTP 201):

```json
{
  "result": true,
  "data": {
    "number": "201",
    "user_username": "Иванов Иван",
    "sip_secret": "Secure#Pass9201",
    "sip_dtmfmode": "auto",
    "sip_transport": "udp",
    "sip_enableRecording": true,
    "sip_networkfilterid": "none",
    "fwd_ringlength": 45,
    "id": "1",
    "extensions_length": 3
  },
  "messages": {"error": [], "info": [], "warning": []}
}
```

Возможные коды ответов:

| Код | Описание                                                             |
| --- | -------------------------------------------------------------------- |
| 201 | Сотрудник успешно создан                                             |
| 400 | Ошибка валидации (слабый пароль <5 символов, неверный формат номера) |
| 401 | Неверный или отсутствующий API-ключ                                  |
| 403 | Нет прав на запись для ресурса `/employees`                          |
| 409 | Конфликт — номер уже занят                                           |

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
 Создан: 243 (Иванов Иван), id=113
 Создан: 244 (Петрова Анна), id=114

Process finished with exit code 0
```

{% endcode %}

На станции будут созданы сотрудники 243 и 244.

<figure><img src="https://3704471835-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPK4TuzRBnP7rt8htho-887967055%2Fuploads%2FvMYKPJNMM0gcaf1ku6Hq%2FcreatedExtensionsWithAPI.png?alt=media&#x26;token=4d1b0c06-d683-42ca-af70-301f0b07f125" alt=""><figcaption><p>Созданные сотрудники с помощью REST API</p></figcaption></figure>

#### Вывод списка сотрудников

```python
def list_employees(search: str = '', limit: int = 100, offset: int = 0) -> list:
    params = {'limit': limit, 'offset': offset}
    if search: params['search'] = search
    r = requests.get(f'{BASE_URL}/employees', headers=HEADERS, params=params)
    return r.json().get('data', {}).get('data', [])

for emp in list_employees():
    print(f"  {emp.get('number'):>6}  {emp.get('user_username', '')}")
```

В случае успешного выполнения запроса Вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
     202  Brown Brandon
     203  Collins Melanie
     201  Smith James
     243  Иванов Иван
     244  Петрова Анна

Process finished with exit code 0
```

{% endcode %}

#### Массовое создание сотрудников

```python
import time

employees = [
    {'number': '251', 'name': 'Иванов Иван',  'secret': 'Pass#9201'},
    {'number': '252', 'name': 'Петрова Анна', 'secret': 'Pass#9202'},
    {'number': '253', 'name': 'Сидоров Пётр', 'secret': 'Pass#9203'},
]

created, failed = [], []
for emp in employees:
    r = requests.post(
        f'{BASE_URL}/employees',
        headers=HEADERS,
        json={
            'number':        emp['number'],
            'user_username': emp['name'],
            'sip_secret':    emp['secret'],
        }
    )
    result = r.json()
    if result.get('result'):
        created.append(emp['number'])
        print(f" {emp['number']} {emp['name']}")
    else:
        failed.append(emp['number'])
        print(f" {emp['number']}: {result.get('messages', {}).get('error', [])}")
    time.sleep(0.2)  # небольшая пауза между запросами

print(f'Создано: {len(created)}, Ошибок: {len(failed)}')
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
 251 Иванов Иван
 252 Петрова Анна
 253 Сидоров Пётр
Создано: 3, Ошибок: 0

Process finished with exit code 0
```

{% endcode %}

На станции будут создано 3 сотрудника.

<figure><img src="https://3704471835-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPK4TuzRBnP7rt8htho-887967055%2Fuploads%2FVpHffvGcpOLVWgEIjEIW%2Fcreated3ExtensionsWithAPI.png?alt=media&#x26;token=84c89f06-152d-4596-9030-67ede572681c" alt=""><figcaption><p>Созданные сотрудники с помощью REST API</p></figcaption></figure>

### Работа с SIP-провайдерами

**Эндпоинт:** `POST /pbxcore/api/v3/sip-providers`

| Поле                | Обяз. | Тип     | Описание                                                   |
| ------------------- | ----- | ------- | ---------------------------------------------------------- |
| `description`       | ✅     | string  | Название провайдера                                        |
| `host`              | ✅     | string  | Адрес SIP-сервера провайдера                               |
| `username`          | —     | string  | Логин на сервере провайдера                                |
| `secret`            | —     | string  | Пароль                                                     |
| `registration_type` | —     | string  | `inbound` / `outbound` / `none`                            |
| `qualify`           | —     | boolean | Мониторинг доступности (по умолч.: `true`)                 |
| `transport`         | —     | string  | `udp` / `tcp` / `tls` / `udp,tcp` (по умолч.: `udp,tcp`)   |
| `dtmfmode`          | —     | string  | `auto` / `rfc4733` / `inband` / `info` (по умолч.: `auto`) |
| `port`              | —     | integer | Порт подключения (по умолч.: `5060`)                       |
| `disabled`          | —     | boolean | Отключить провайдера (по умолч.: `false`)                  |

#### Создание провайдера

```python
def create_sip_provider(
    description: str,
    host: str,
    username: str = '',
    password: str = '',
    registration_type: str = 'outbound',
    qualify: bool = True,
) -> dict:
    payload = {
        'description': description,
        'host':        host,
    }
    if username:          payload['username']          = username
    if password:          payload['secret']            = password
    if registration_type: payload['registration_type'] = registration_type
    if not qualify:       payload['qualify']           = qualify

    r = requests.post(f'{BASE_URL}/sip-providers', headers=HEADERS, json=payload)
    result = r.json()
    if result.get('result'):
        print(f" Провайдер создан: {description}")
    else:
        print(f" Ошибка: {result.get('messages', {}).get('error', [])}")
    return result


create_sip_provider(
    description='Zadarma',
    host='sip.zadarma.com',
    username='316811',
    password='mysecretpass',
)
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
 Провайдер создан: Zadarma

Process finished with exit code 0
```

{% endcode %}

На станции будет создан провайдер:

<figure><img src="https://3704471835-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPK4TuzRBnP7rt8htho-887967055%2Fuploads%2FX7QzWkQbibCO1VsScQXl%2FcreatedProviderWithAPI.png?alt=media&#x26;token=76839641-f581-4af6-9941-a3053300cd7c" alt=""><figcaption><p>Созданный провайдер с помощью REST API</p></figcaption></figure>

#### Вывод списка всех провайдеров

```python
def list_providers() -> list:
    r = requests.get(f'{BASE_URL}/sip-providers', headers=HEADERS)
    return r.json().get('data', [])

for prov in list_providers():
    print(f"  {prov.get('id'):<20} {prov.get('description', '')}  [{prov.get('type', '')}]")
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
  SIP-TRUNK-34F7CAFE     [SIP]
  SIP-TRUNK-7B5977ED     [SIP]

Process finished with exit code 0
```

{% endcode %}

### Вывод истории звонков (CDR)

**Эндпоинт:** `GET /pbxcore/api/v3/cdr` — только чтение.

| Параметр      | Тип     | Описание                                     |
| ------------- | ------- | -------------------------------------------- |
| `offset`      | integer | Смещение для пагинации (по умолч.: 0)        |
| `limit`       | integer | Кол-во записей, макс. 100                    |
| `dateFrom`    | string  | Начало периода: `%Y-%m-%dT%H:%M:%S`          |
| `dateTo`      | string  | Конец периода: `%Y-%m-%dT%H:%M:%S`           |
| `src_num`     | string  | Фильтр по номеру звонящего                   |
| `dst_num`     | string  | Фильтр по номеру назначения                  |
| `disposition` | string  | `ANSWERED` / `NO ANSWER` / `BUSY` / `FAILED` |

```python
from datetime import datetime, timedelta

def get_cdr(
    offset: int = 0,
    limit: int = 20,
    date_from: str = None,
    date_to: str = None,
    src_num: str = None,
    dst_num: str = None,
    disposition: str = None,
) -> list:
    params = {'offset': offset, 'limit': min(limit, 100)}
    if date_from:   params['dateFrom'] = date_from
    if date_to:     params['dateTo']   = date_to
    if src_num:     params['src_num']  = src_num
    if dst_num:     params['dst_num']  = dst_num
    if disposition: params['disposition'] = disposition

    r = requests.get(f'{BASE_URL}/cdr', headers=HEADERS, params=params)
    return r.json().get('data', {}).get('records', [])


now  = datetime.now()
then = now - timedelta(days=7)

for row in get_cdr(
    date_from=then.strftime('%Y-%m-%dT%H:%M:%S'),
    date_to=now.strftime('%Y-%m-%dT%H:%M:%S'),
):
    print(
        str(row.get('start', ''))[:16],
        row.get('src_num', ''), '→', row.get('dst_num', ''),
        row.get('disposition', ''), row.get('totalBillsec', 0), 'с'
    )
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
2026-03-17 13:30 252 → 202 ANSWERED 48 с
2026-03-17 13:30 243 → 252 BUSY 0 с
2026-03-17 13:30 243 → 89161111111 CHANUNAVAIL 0 с
2026-03-17 13:29 202 → 243 NOANSWER 0 с
2026-03-17 13:29 202 → 202 ANSWERED 2 с
2026-03-17 13:29 202 → 243 NOANSWER 0 с
2026-03-17 13:29 202 → 10003246 NOANSWER 0 с
2026-03-17 13:28 202 → 243 NOANSWER 0 с

Process finished with exit code 0
```

{% endcode %}

#### Статистика за период

```python
def cdr_stats(days: int = 1) -> dict:
    now  = datetime.now()
    then = now - timedelta(days=days)
    records = get_cdr(
        date_from=then.strftime('%Y-%m-%dT%H:%M:%S'),
        date_to=now.strftime('%Y-%m-%dT%H:%M:%S'),
        limit=100
    )
    answered  = [r for r in records if r.get('disposition') == 'ANSWERED']
    missed = [r for r in records if r.get('disposition') in ('NO ANSWER', 'NOANSWER')]
    total_dur = sum(r.get('totalBillsec', 0) for r in answered)
    return {
        'total':    len(records),
        'answered': len(answered),
        'missed':   len(missed),
        'avg_sec':  total_dur // len(answered) if answered else 0,
    }

stats = cdr_stats(days=7)
print(f"Звонков за 7 дней: {stats['total']}")
print(f"Отвечено:          {stats['answered']}")
print(f"Пропущено:         {stats['missed']}")
print(f"Средняя длит.:     {stats['avg_sec']}с")
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
Звонков за 7 дней: 13
Отвечено:          2
Пропущено:         5
Средняя длит.:     25с

Process finished with exit code 0
```

{% endcode %}

{% hint style="info" %}
Звонки со статусом `CHANUNAVAIL` не учитываются в статистике «Отвечено», «Пропущено», «Средняя длит.».
{% endhint %}

#### Поля CDR-записи

| Поле                      | Тип      | Описание                                                                  |
| ------------------------- | -------- | ------------------------------------------------------------------------- |
| `linkedid`                | string   | Уникальный идентификатор звонка                                           |
| `start`                   | datetime | Время начала звонка                                                       |
| `src_num`                 | string   | Номер звонящего                                                           |
| `src_name`                | string   | Имя звонящего                                                             |
| `dst_num`                 | string   | Номер назначения                                                          |
| `dst_name`                | string   | Имя вызываемого                                                           |
| `disposition`             | string   | `ANSWERED` / `NO ANSWER` / `NOANSWER` / `BUSY` / `CHANUNAVAIL` / `FAILED` |
| `totalBillsec`            | integer  | Длительность разговора (секунды)                                          |
| `totalDuration`           | integer  | Полная длительность (включая дозвон)                                      |
| `records`                 | array    | Детальные записи по каждому плечу звонка                                  |
| `records[].recordingfile` | string   | Путь к файлу записи                                                       |
| `records[].playback_url`  | string   | URL для воспроизведения записи                                            |
| `records[].download_url`  | string   | URL для скачивания записи                                                 |
| `records[].dtmf_digits`   | string   | DTMF цифры, нажатые в IVR                                                 |

### Мониторинг: статусы SIP и активные звонки

#### Статусы регистрации сотрудников и SIP-провайдеров

**Эндпоинты:** `GET /pbxcore/api/v3/sip` , `GET /pbxcore/api/v3/sip-providers`

```python
from datetime import datetime

def show_employees():
    r = requests.get(f'{BASE_URL}/sip:getStatuses', headers=HEADERS)
    peers = r.json().get('data', {})
    for number, info in peers.items():
        icon = '🟢' if info.get('status') == 'Available' else '🔴'
        print(f"  {icon}  {number:>6}  {info.get('callerid', '')}  [{info.get('status', '')}]")


def show_providers():
    r = requests.get(f'{BASE_URL}/sip-providers:getStatuses', headers=HEADERS)
    providers = r.json().get('data', {}).get('sip', {})
    for prov_id, info in providers.items():
        icon = '🟢' if info.get('state') == 'registered' else '🔴'
        print(f"  {icon}  {info.get('description', prov_id):>20}  {info.get('username', '')}@{info.get('host', '')}  [{info.get('state', '')}]")


if __name__ == '__main__':
    print(f'MikoPBX Monitor [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]')
    print('\n── Сотрудники ──────────────────────────────')
    show_employees()
    print('\n── Провайдеры ───────────────────────────────')
    show_providers()
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
MikoPBX Monitor [2026-03-17 16:47:35]

── Сотрудники ──────────────────────────────
  🔴     201  Smith James  [Unavailable]
  🟢     202  Brown Brandon  [Available]
  🔴     203  Collins Melanie  [Unavailable]
  🔴     243  Иванов Иван  [Unavailable]
  🟢     244  Петрова Анна  [Available]
  🔴     251  Иванов Иван  [Unavailable]
  🟢     252  Петрова Анна  [Available]
  🔴     253  Сидоров Пeтр  [Unavailable]

── Провайдеры ───────────────────────────────
  🔴         Demo provider  SIP-PROVIDER-122642725b9265fd7151c@demo.askozia.ru  [rejected]
  🟢               Zadarma  316811@sip.zadarma.com  [registered]

Process finished with exit code 0
```

{% endcode %}

**Статусы сотрудников (поле `status`)**

| Значение      | Описание                     |
| ------------- | ---------------------------- |
| `Available`   | Зарегистрирован и доступен   |
| `Unavailable` | Не зарегистрирован (оффлайн) |

**Статусы провайдеров (поле `state`)**

| Значение       | Описание                              |
| -------------- | ------------------------------------- |
| `registered`   | Зарегистрирован на сервере провайдера |
| `rejected`     | Регистрация отклонена сервером        |
| `unregistered` | Не зарегистрирован                    |

#### Активные звонки в реальном времени

**Эндпоинт:** `GET /pbxcore/api/v3/pbx-status`

```python
def get_active_calls() -> list:
    r = requests.get(f'{BASE_URL}/pbx-status:getActiveCalls', headers=HEADERS)
    return r.json().get('data', [])

calls = get_active_calls()

print(f'Активных звонков: {len(calls)}')
for call in calls:
    print(f"  {call.get('src_num', '?')} → {call.get('dst_num', '?')}  [{call.get('src_name', '')} → {call.get('dst_name', '')}]")
```

В случае успешного выполнения запроса вы увидите следующий вывод в консоль:

{% code overflow="wrap" %}

```python
Активных звонков: 1
  243 → 252  [Иванов Иван → Петрова Анна]

Process finished with exit code 0
```

{% endcode %}

> Полный список эндпоинтов и интерактивная документация — в разделе [Интерактивная документация и список эндпоинтов.](https://docs.mikopbx.com/mikopbx/manual/system/api-keys/endpoints)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mikopbx.com/mikopbx/manual/system/api-keys/examples.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
