Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
В данном разделе происходит настройка часов и календаря




Описание настроек основных параметров системы



















Настройка почты и уведомлений


















https://192.168.100.60/pbxcore/api/v3/mail-settings/oauth2-callbackhttps://192.168.100.71/pbxcore/api/v3/mail-settings/oauth2-callback

























Описание возможностей раздела "Кастомизация системных файлов"
[general](+)
allowtransfer=yes[user2_pingtel]
type=friend
username=user2_pingtel
secret=blah
host=dynamic
qualify=1000 ; Рассматриваем клиента как неработающего,
; если ответ от него идет более 1 сек.
callgroup=1,3-4 ; Клиент является членом групп вызовов: 1, 3 и 4
pickupgroup=1,3-4 ; Мы можем совершать "pick-up" вызовов, нажатием *8,
; для вызовов из групп 1, 3 и 4
defaultip=192.168.0.60
disallow=all
allow=ulaw
allow=alaw
allow=g729[outgoing-custom]
exten => _X!,1,NoOp(--- hangup - ${CHANNEL} ---)
same => n,return






















Короткое описание ARI (Asterisk REST Interface)





# REST API запрос через curl
curl -u username:password https://your-mikopbx.com:8089/asterisk/ari/asterisk/infowscat -c "wss://username:password@your-mikopbx.com:8089/asterisk/ari/events?app=hello-world"1,Answer()
n,Stasis(hello-world)
n,Hangup(){
"type": "StasisStart",
"timestamp": "2026-03-25T07:18:27.423+0300",
"args": [],
"channel": {
"id": "mikopbx-1774412307.28",
"name": "Local/10003258@internal-incoming-0000000a;2",
"state": "Up",
"protocol_id": "",
"caller": {
"name": "79257184275",
"number": "79257184275"
},
"connected": {
"name": "",
"number": "252"
},
"accountcode": "",
"dialplan": {
"context": "internal",
"exten": "10003258",
"priority": 2,
"app_name": "Stasis",
"app_data": "hello-world"
},
"creationtime": "2026-03-25T07:18:27.371+0300",
"language": "en-en"
},
"asterisk_id": "82:6a:9e:68:10:11",
"application": "hello-world"
}curl -u username:password -X POST \
"https://your-mikopbx.com:8089/asterisk/ari/channels/mikopbx-1774412307.28/play?media=sound:/storage/usbdisk1/mikopbx/media/custom/miko_hello"{
"id": "a0ee0d43-2af5-4250-a303-43825507a06c",
"media_uri": "sound:/storage/usbdisk1/mikopbx/media/custom/miko_hello",
"target_uri": "channel:mikopbx-1774412307.28",
"language": "en-en",
"state": "playing"
}{
"type": "StasisEnd",
"timestamp": "2026-03-25T07:19:44.982+0300",
"channel": {
"id": "mikopbx-1774412307.28",
"name": "Local/10003258@internal-incoming-0000000a;2",
"state": "Up",
"protocol_id": "",
"caller": {
"name": "79257184275",
"number": "79257184275"
},
"connected": {
"name": "",
"number": "252"
},
"accountcode": "",
"dialplan": {
"context": "internal",
"exten": "10003258",
"priority": 2,
"app_name": "Stasis",
"app_data": "hello-world"
},
"creationtime": "2026-03-25T07:18:27.371+0300",
"language": "en-en"
},
"asterisk_id": "82:6a:9e:68:10:11",
"application": "hello-world"
}pip install requests websocketsimport asyncio
import websockets
import json
import os
from datetime import datetime
ARI_HOST = 'your-mikopbx.com'
ARI_USER = 'ari_user'
ARI_PASS = 'your-ari-password'
peers = {}
STATES = {
'NOT_INUSE': ('🟢', 'Свободен'),
'BUSY': ('🔴', 'Занят'),
'UNAVAILABLE': ('⚫', 'Недоступен'),
}
def draw():
print('\033[2J\033[H', end='')
now = datetime.now().strftime('%H:%M:%S')
print(f'MikoPBX — монитор присутствия [{now}]')
print('─' * 50)
print(f' {"Номер":<10} {"Имя":<20} {"Статус":<15} {"Обновлён"}')
print('─' * 50)
for number, info in sorted(peers.items()):
icon, label = STATES.get(info['state'], ('❓', info['state']))
print(f' {number:<10} {info["name"]:<20} {icon} {label:<12} {info["updated"]}')
print('─' * 50)
print(f' Сотрудников: {len(peers)}')
async def run():
uri = (
f"wss://{ARI_USER}:{ARI_PASS}@{ARI_HOST}:8089/asterisk/ari/events"
f"?app=auto-receptionist&subscribeAll=true"
)
async with websockets.connect(uri) as ws:
draw()
async for message in ws:
event = json.loads(message)
etype = event.get('type')
if etype == 'DeviceStateChanged':
ds = event.get('device_state', {})
name = ds.get('name', '')
state = ds.get('state', '')
if not name.startswith('PJSIP/'):
continue
number = name.replace('PJSIP/', '')
if number not in peers:
peers[number] = {'name': number, 'state': state, 'updated': '—'}
peers[number]['state'] = state
peers[number]['updated'] = datetime.now().strftime('%H:%M:%S')
draw()
elif etype == 'PeerStatusChange':
ep = event.get('endpoint', {})
number = ep.get('resource', '')
state = ep.get('state', '')
if not number:
continue
if number not in peers:
peers[number] = {'name': number, 'state': 'unknown', 'updated': '—'}
if state == 'online':
peers[number]['state'] = 'NOT_INUSE'
elif state == 'offline':
peers[number]['state'] = 'UNAVAILABLE'
peers[number]['updated'] = datetime.now().strftime('%H:%M:%S')
draw()
elif etype == 'ContactStatusChange':
ep = event.get('endpoint', {})
number = ep.get('resource', '')
ci = event.get('contact_info', {})
status = ci.get('contact_status', '')
if not number:
continue
if number not in peers:
peers[number] = {'name': number, 'state': 'unknown', 'updated': '—'}
if status == 'Reachable':
peers[number]['state'] = 'NOT_INUSE'
elif status in ('Unreachable', 'NonQualified'):
peers[number]['state'] = 'UNAVAILABLE'
peers[number]['updated'] = datetime.now().strftime('%H:%M:%S')
draw()
asyncio.run(run())MikoPBX — монитор присутствия [11:34:43]
──────────────────────────────────────────────────
Номер Имя Статус Обновлён
──────────────────────────────────────────────────
202 202 🟢 Свободен 11:34:25
243 243 ⚫ Недоступен 11:34:43
252 252 🔴 Занят 11:34:41
──────────────────────────────────────────────────
Сотрудников: 3Описание раздела "Система" в MikoPBX

Инструкция с примерами по созданию и использованию API-ключей




pip install requestsimport requests
BASE_URL = 'https://your-mikopbx.com/pbxcore/api/v3'
API_KEY = 'ваш-api-ключ'
HEADERS = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}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='[email protected]',
mobile='79001234567',
record_calls=True,
fwd_ringlength=30,
){
"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": []}
} Создан: 243 (Иванов Иван), id=113
Создан: 244 (Петрова Анна), id=114
Process finished with exit code 0def 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', '')}") 202 Brown Brandon
203 Collins Melanie
201 Smith James
243 Иванов Иван
244 Петрова Анна
Process finished with exit code 0import 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)}') 251 Иванов Иван
252 Петрова Анна
253 Сидоров Пётр
Создано: 3, Ошибок: 0
Process finished with exit code 0def 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',
) Провайдер создан: Zadarma
Process finished with exit code 0def 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', '')}]") SIP-TRUNK-34F7CAFE [SIP]
SIP-TRUNK-7B5977ED [SIP]
Process finished with exit code 0from 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), 'с'
)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 0def 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']}с")Звонков за 7 дней: 13
Отвечено: 2
Пропущено: 5
Средняя длит.: 25с
Process finished with exit code 0from 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()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 [email protected] [rejected]
🟢 Zadarma [email protected] [registered]
Process finished with exit code 0def 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', '')}]")Активных звонков: 1
243 → 252 [Иванов Иван → Петрова Анна]
Process finished with exit code 0Настройка почты для сервиса Yandex


Описание документации и таблицы эндпоинтов для работы с REST API в MikoPBX






