C cenaly.ru
Начать бесплатно

REST API своей АТС

Все методы REST API АТС с правами, параметрами и ответами: журнал звонков, записи и расшифровки, звонок в клик, переадресация и «не беспокоить», метрики, правила звонков, импорт настроек, ошибки и готовые сценарии

13 мин чтения Демо админ-панели
На этой странице 13

REST API своей АТС

Для разработчика CRM, учётной системы или своей панели: REST API читает звонки и записи, запускает звонок с телефона сотрудника и управляет настройками АТС. Владелец создаёт ключ с нужными правами — интеграция действует от его имени и только в пределах этих прав. Ключи, права, OAuth и лимиты — в обзоре API и MCP своей АТС.

В статье: адрес и ключ, ошибки, журнал, звонок в клик, добавочные, метрики, правила, конфигурация, события, открыто и закрыто, сценарии.

Адрес и ключ#

Адрес API — https://api.cenaly.ru/pbx-api/v1. Ключ передаётся в заголовке Authorization: Bearer cpx_… или X-Api-Key: cpx_…. Не передавайте ключ в адресе запроса и не кладите его в браузерный JavaScript.

BASE='https://api.cenaly.ru/pbx-api/v1'
KEY='cpx_…'
curl -sS "$BASE/me" -H "Authorization: Bearer $KEY"

Ключ создаёт владелец: Телефония → API и MCP → Ключи → Создать ключ. Секрет показывается один раз; храните его в менеджере секретов. В примерах вместо секрета — заглушка cpx_….

Набор Права
CRM calls:read, recordings:read, calls:dial, extensions:read
ИИ-ассистент CRM + extensions:write, presence:read
Только чтение calls:read, recordings:read, extensions:read, routes:read, presence:read, reports:read
Всё все обычные права, кроме пяти прав отдельного согласия

Права routes:write, config:export, config:import, webhooks:write, daynight:write не входят ни в один набор — отмечайте их отдельно, если они нужны.

location — id точки из /locations. Для GET он передаётся в query, для POST/PUT/PATCH — в JSON или query. Если ключу доступна ровно одна точка, параметр можно опустить; иначе ответ 400 location_required перечислит доступные точки. В интеграции лучше передавать location всегда: у ключа может появиться вторая точка.

Спецификация: OpenAPI 3.1; описание с примерами — api.cenaly.ru/pbx-api/v1/docs. Примеры ответов в этой статье составлены по коду на вымышленных данных; необязательные поля зависят от данных и настроек АТС.

Лимиты и ошибки#

120 запросов в минуту и 20 000 в сутки на ключ или подключение — общий бюджет REST и MCP. Звонок в клик дополнительно ограничен: 10 в минуту и 300 в сутки. Журнал отдаёт до 100 звонков за запрос, лента событий — до 500.

Ошибка — JSON с полем error, например {"error":"scope_required","scope":"calls:read"}. Не рассчитывайте на поле message.

HTTP Примеры error Что делать
400 invalid_json, location_required, time_invalid, cursor_expired исправить JSON или параметры; при истёкшем курсоре начать выборку заново
401 api_key_missing, api_key_invalid проверить ключ, срок и отзыв; токен OAuth — обновить
403 scope_required, locations_outside_key выдать право; сузить точки вебхука до точек ключа
404 not_found, location_not_found, call_not_found, recording_not_found проверить путь, id, доступ ключа к точке и окно хранения
409 rev_conflict, transcript_in_progress, daynight_code_off перечитать состояние; дождаться расшифровки; включить закрытие кодом *28
429 rate_limited подождать Retry-After секунд (60)
429 dial_rate_limited в теле ответа retryAfterSec: 60; заголовка Retry-After у этой ошибки нет
503 auth_unavailable временный сбой проверки ключа — повторить позже

Не повторяйте запуск звонка автоматически после сетевого обрыва с неизвестным исходом: звонок мог уже начаться. Отдельного ключа идемпотентности у /dial нет.

Ключ и точки#

Метод Путь Право Параметры Поля ответа
GET /me любой ключ — key{id,name,hint,scopes,locations,expiresAt?}, limits, direct?
GET /locations любой ключ — items[]{id,name,pbx,direct?}; pbx может быть null
curl -sS "$BASE/me" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/locations" -H "Authorization: Bearer $KEY"

Ответ /me для ключа на все точки (locations: [] — все точки, включая будущие):

{"key":{"id":"pk_0123456789ab","name":"CRM","hint":"cpx_exampl","scopes":["calls:read"],"locations":[]},"limits":{"perMinute":120,"perDay":20000,"dialsPerMinute":10,"dialsPerDay":300},"direct":{"loc1":{"ready":false,"reason":"no_feature"}}}

Пример /locations для точки без настроенной АТС:

{"items":[{"id":"loc1","name":"Главная точка","pbx":null}]}

Прямой адрес АТС — готовится#

У точки со своей АТС запросы смогут идти прямо на сервер АТС, минуя облако. Поле direct в /me (карта по id точки) и в элементах /locations уже показывает, готов ли такой адрес: ready, reason, а когда имя сервера известно — rest и mcp. Пока эта возможность внедряется, ready равен false: работайте с адресом https://api.cenaly.ru/pbx-api/v1. Он остаётся рабочим и после появления прямого адреса — как запасной путь. Подключениям по OAuth поле direct не выдаётся.

Журнал звонков#

Метод Путь Право Параметры Поля ответа
GET /calls calls:read location, since, until, direction, status, number, extension, limit, cursor location, items, nextCursor?, window{maxCalls,maxDays,full}
GET /calls/{id} calls:read location, id в пути location, call с steps[]
GET /calls/{id}/recording recordings:read location, id callId,url,expiresIn,format
GET /calls/{id}/transcript recordings:read location, id callId,recId,language?,piiRedacted?,text,segments[],summary?

since — включительно, until — исключительно: ISO-время со смещением удобно для местного дня. Направления — inbound, outbound, internal, transfer. Статус — точное значение или группа: answered включает forwarded; missed — missed, busy, cancelled, abandoned; ai — bot, secretary, voicemail. number ищет часть цифр номера, extension — добавочный стороны или ответившего, а не всех телефонов, которым звонили.

По умолчанию limit=50, максимум 100; новые звонки первыми. Передавайте полученный nextCursor без изменений и с теми же фильтрами, пока поле не исчезнет.

curl -sS -G "$BASE/calls" -H "Authorization: Bearer $KEY" \
  --data-urlencode 'location=loc1' --data-urlencode 'status=missed' \
  --data-urlencode 'since=2026-10-23T00:00:00+04:00' \
  --data-urlencode 'until=2026-10-24T00:00:00+04:00' --data-urlencode 'limit=100'
{"location":"loc1","items":[{"id":"1792742400.1","startedAt":"2026-10-23T08:00:00Z","endedAt":"2026-10-23T08:00:20Z","direction":"inbound","status":"missed","statusGroup":"missed","from":{"number":"+995555123456"},"to":{"extension":"101"},"durationSec":20}],"window":{"maxCalls":400,"maxDays":90,"full":false}}

API видит последние 400 звонков точки за 90 дней (window); full:true значит, что окно заполнено и старые звонки уже не видны. Нужен полный архив — сохраняйте журнал у себя.

curl -sS "$BASE/calls/1792742400.1?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/calls/1792742400.2/recording?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/calls/1792742400.2/transcript?location=loc1" -H "Authorization: Bearer $KEY"
{"location":"loc1","call":{"id":"1792742400.1","startedAt":"2026-10-23T08:00:00Z","endedAt":"2026-10-23T08:00:20Z","direction":"inbound","status":"missed","statusGroup":"missed","from":{"number":"+995555123456"},"to":{"extension":"101"},"durationSec":20,"steps":[]}}

Ответ на запрос записи:

{"callId":"1792742400.2","url":"https://example.com/temporary-audio","expiresIn":600,"format":"wav"}

URL здесь — заглушка; используйте ровно выданную ссылку. Она временная — 10 минут, точный срок в expiresIn. Расшифровка:

{"callId":"1792742400.2","recId":"rec-example","language":"ru","text":"Здравствуйте.","segments":[{"speaker":"1","start":0,"end":1.2,"text":"Здравствуйте."}]}

404 transcript_not_ready и 409 transcript_in_progress — разные стадии: расшифровки ещё нет или она идёт прямо сейчас. summary необязателен — не требуйте его для показа текста.

Звонок в клик#

Метод Путь Право Параметры Поля ответа
POST /dial calls:dial location, extension, number commandId,location,extension,number,e164?,state,fast
GET /dial/{commandId} calls:dial location commandId,state,stage,reason?

Сначала звонит телефон сотрудника, после его ответа АТС набирает внешний номер. Разрешения исходящих и ограничения точки продолжают действовать.

curl -sS "$BASE/dial" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"location":"loc1","extension":"101","number":"+995555123456"}'
curl -sS "$BASE/dial/cb-0123456789abcdef0123?location=loc1" -H "Authorization: Bearer $KEY"
{"commandId":"cb-0123456789abcdef0123","location":"loc1","extension":"101","number":"+995555123456","state":"queued","fast":true}

state:"queued" — команда принята, а не разговор состоялся. fast:false предупреждает, что первый звонок может ждать связи с АТС до минуты; следующие несколько минут команды уходят за секунды. Ход звонка — по commandId:

stage Что происходит
queued команда ждёт АТС
sent АТС забрала команду
calling_you звонит телефон сотрудника
dialing_client сотрудник ответил, АТС набирает клиента
connected клиент ответил
ended звонок завершён
failed не получилось; причина — в reason
{"commandId":"cb-0123456789abcdef0123","state":"ringing","stage":"dialing_client"}

Итог разговора сверяйте с журналом или событиями.

Добавочные, переадресация, DND и присутствие#

Метод Путь Право Параметры Поля ответа
GET /extensions extensions:read location location,items[]{extension,kind,name,enabled,forwarding?,dndRequested?,presence?,external?}
PUT /extensions/{ext}/forwarding extensions:write location, forward location,extension,forwarding,supported?
PUT /extensions/{ext}/dnd extensions:write location, on, until? location,extension,dnd
GET /presence presence:read location location,live,items

kind — person, group или room. presence в списке добавочных появляется только при праве presence:read. live:false предупреждает, что сведения не живое состояние АТС (например, она давно не присылала снимок).

curl -sS "$BASE/extensions?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS -X PUT "$BASE/extensions/102/forwarding" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"location":"loc1","forward":{"always":{"kind":"extension","extension":"101"}}}'
curl -sS -X PUT "$BASE/extensions/102/dnd" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"location":"loc1","on":true,"until":"morning"}'
curl -sS "$BASE/presence?location=loc1" -H "Authorization: Bearer $KEY"
{"location":"loc1","items":[{"extension":"101","kind":"person","name":"Анна","enabled":true,"forwarding":null}]}
{"location":"loc1","extension":"102","forwarding":{"always":{"kind":"extension","extension":"101"}},"supported":true}

Пустой список присутствия:

{"location":"loc1","live":true,"items":[]}

В forward допустимы условия always, busy, noAnswer, offline, dnd, offShift и число noAnswerSec. Цель — {kind:"extension",extension}, {kind:"number",number} или {kind:"voicemail"}. forward:null снимает переадресацию. Передавайте весь желаемый объект: это не частичное изменение.

Ответ снятия DND, пока АТС ещё применяет запрос:

{"location":"loc1","extension":"102","dnd":{"on":true,"requested":{"on":false,"at":1792836000},"appliedAt":1792835900,"pending":true,"supported":true,"until":null,"timer":true}}

DND: on:false снимает режим; until при включении — Unix-секунды или "morning" (начало следующей смены, иначе 08:00 местного времени), АТС снимет режим сама. dnd показывает и запрос, и его применение: не сообщайте пользователю «готово», пока pending:true.

Метрики#

Метод Путь Право Параметры Поля ответа
GET /metrics reports:read location, from, to, compare, sections, callbacks location,tz,today,history,range,coverage,sl, выбранные разделы, compare, compareSkipped?, callbacks?

from/to — местные даты точки YYYY-MM-DD включительно; без них — последние 7 дней. compare: prev, year, prev,year или пусто. Разделы: metrics,loss,queues,lines,operators,directions,heatmap,daily; heatmap и daily приходят только по явному запросу. callbacks=true добавляет скорость перезвона (для периодов до 92 дней). Сравнение с прошлым годом — для периодов до 400 дней.

curl -sS "$BASE/metrics?location=loc1&from=2026-09-01&to=2026-09-30&compare=prev,year" -H "Authorization: Bearer $KEY"

Пример периода без данных:

{"location":"loc1","tz":"Asia/Tbilisi","today":"2026-10-24","history":{"since":null,"gaps":[]},"range":{"from":"2026-10-23","to":"2026-10-23"},"coverage":{"periodDays":1,"days":0,"v2Days":0},"sl":{"slaSec":20,"targetPct":80,"shortAbandonSec":5},"queues":[],"compare":{}}

Показатели лежат в поле metrics в корне ответа — те же цифры, что на экране Телефония → Журнал звонков → Аналитика: входящие, принятые и пропущенные, уровень обслуживания, ожидание, брошенные, AHT, FCR, CSAT. Смотрите coverage и history.gaps, чтобы не принять пробел в данных за отсутствие звонков.

Правила звонков#

Пути — относительно /pbx-api/v1; location обязателен, если у ключа несколько точек.

Метод Путь Право Параметры Поля ответа
GET /routes routes:read location location,lines,trunks,inbound,outbound
GET /routes/history routes:read location, section=inbound|outbound location,section,status,items[]{versionId,at,current,summary},truncated?
POST /routes/inbound routes:write location, rule, position? location,rule,order,rev,warnings?
PUT /routes/inbound/{id} routes:write location, rule, position? то же
DELETE /routes/inbound/{id} routes:write location location,order,rev
POST /routes/inbound/{id}/move routes:write location, position location,rule,order,rev,warnings?
POST /routes/outbound/rules routes:write location, rule, position? location,rule,outbound,rev
PUT /routes/outbound/rules/{id} routes:write location, rule, position? то же
DELETE /routes/outbound/rules/{id} routes:write location location,outbound,rev
POST /routes/outbound/rules/{id}/move routes:write location, position location,rule,outbound,rev
PATCH /routes/outbound routes:write location, enabled?, fallback? location,outbound,rev
POST /routes/history/restore routes:write location, section, versionId location,section,versionId,restored

position начинается с 1. Правка правила меняет только переданные поля, null удаляет поле. Поля правила можно передать в корне тела или объектом rule. Срабатывает первое подходящее входящее правило; warnings:["after_catch_all"] значит, что выше уже стоит безусловное правило и новое не сработает.

Входящее rule: name, enabled, match, when, action, extension, botId, forwardNumber, menuId, fallback. match.kind: any, time (days, from, to), callerPrefix, line (lineId), trunk (trunkId). Чтобы направить звонок группе, используйте action:"extension" и добавочный группы; сложное распределение по людям настраивается в админке.

Исходящее rule: name, enabled, pattern, via, scope, stripDigits, prefix, backupVia, backupVias. Шаблон: цифры, X (0–9), N (2–9), Z (1–9), начальный +, завершающая точка. via — {kind:"line",lineId} или {kind:"trunk",trunkId}; scope — {kind:"all"} или {kind:"extensions",extensions:["101"]}. Разрешённые страны и защиту от мошенничества ключ не меняет.

curl -sS "$BASE/routes?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS -X PUT "$BASE/routes/inbound/r-example" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"location":"loc1","rule":{"name":"Дежурный"}}'
curl -sS "$BASE/routes/history?location=loc1&section=inbound" -H "Authorization: Bearer $KEY"
# versionId — из истории, id правила — из /routes.
curl -sS "$BASE/routes/history/restore" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"location":"loc1","section":"inbound","versionId":"version-example"}'

Правила пустой АТС:

{"location":"loc1","lines":[],"trunks":[],"inbound":[],"outbound":null}

Ответ на правку правила:

{"location":"loc1","rule":{"id":"r-example","order":1,"name":"Дежурный","enabled":true,"match":{"kind":"any"},"action":"extension","extension":"101"},"order":["r-example"],"rev":12}

Откат создаёт новую версию: {"location":"loc1","section":"inbound","versionId":"version-example","restored":true}. При конфликте перечитайте правила и историю, прежде чем повторять изменение.

Экспорт и импорт конфигурации#

Метод Путь Право Параметры Поля ответа
GET /config/export config:export location, tables? (через запятую) location,rev,tables
POST /config/import config:import location, tables, sync?, apply?, baseRev? location,applied,changed,rev,tables,errors,newAccounts?,created?,passwords?

Таблицы — тот же формат, что выгрузка в админке: добавочные, группы, линии, входящие, исходящие. Пароли не выгружаются. Импорт принимает tables как массивы строк или объекты {columns,rows} из экспорта.

curl -fsS "$BASE/config/export?location=loc1" -H "Authorization: Bearer $KEY" > export.json
# Отредактируйте export.json. Предпросмотр ничего не применяет.
jq '{location:"loc1",tables:.tables}' export.json > import.json
curl -fsS "$BASE/config/import" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  --data-binary @import.json > preview.json
cat preview.json
# Только после проверки tables и errors:
jq --slurpfile p preview.json '. + {apply:true,baseRev:$p[0].rev}' import.json > apply.json
curl -fsS "$BASE/config/import" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' --data-binary @apply.json

Экспорт tables=extensions пустого списка добавочных:

{"location":"loc1","rev":12,"tables":{"extensions":{"columns":["extension","name","type","external_number","via","staff_email","staff_name","groups","recording","voicemail","voicemail_email","enabled"],"rows":[]}}}

Предпросмотр без изменений:

{"location":"loc1","applied":false,"changed":false,"rev":12,"tables":[{"table":"extensions","added":0,"changed":0,"removed":0,"unchanged":0,"errors":0,"rows":[]}],"errors":0,"newAccounts":0}

Применение без ревизии отклоняется:

{"error":"base_rev_required","hint":"Run import_config without apply first and pass its rev as baseRev."}

В предпросмотре applied:false; errors — число ошибок, подробности — в tables[].rows[]. Строки с ошибками при применении пропускаются: читайте весь отчёт, а не только HTTP-код. После успешного применения applied:true, rev — новая ревизия; если менять нечего, applied остаётся false. sync:{"<таблица>":true} удаляет строки, которых нет в файле, — включайте осознанно. Конфликт ревизии — повод для нового предпросмотра, а не для подстановки свежего номера в старое применение. Пароли новых добавочных доступны только в админке (passwords:"admin_only").

События и вебхуки#

Метод Путь Право Параметры Поля ответа
GET /events calls:read location, since, types, limit location,events,more,next?
GET /webhooks webhooks:write — webhooks,events,limits,delivery
POST /webhooks webhooks:write name, url, events, locations, enabled ok,webhook,secret,created
PATCH /webhooks/{id} webhooks:write изменяемые поля ok,webhook,created
DELETE /webhooks/{id} webhooks:write — ok,id
POST /webhooks/{id}/rotate-secret webhooks:write — ok,webhook,secret
POST /webhooks/{id}/test webhooks:write — ok,id,queued,reason?
GET /webhooks/{id}/deliveries webhooks:write — id,deliveries,health?
curl -sS "$BASE/events?location=loc1&limit=100" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/webhooks/wh_0123456789ab/deliveries" -H "Authorization: Bearer $KEY"
{"location":"loc1","events":[],"more":false}

События идут от старых к новым; следующий запрос передаёт since = next предыдущего ответа, в том числе при more:true. types — список через запятую. Лента хранит до 2000 событий точки за 7 дней. Тела событий, подпись и повторы — в статье Вебхуки событий звонков.

Открыто и закрыто#

Метод Путь Право Параметры Поля ответа
GET /open-closed routes:read location location,codeEnabled,remoteSupported,closed,closedUntil?,closedBy?,request?
PUT /open-closed daynight:write location, closed, until? ok, rev и состояние точки
curl -sS "$BASE/open-closed?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS -X PUT "$BASE/open-closed" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"location":"loc1","closed":true,"until":"2026-10-25T09:00:00+04:00"}'
{"location":"loc1","codeEnabled":true,"remoteSupported":true,"closed":false}

Облако передаёт запрос АТС за секунды: пока request.applied равен false, АТС его ещё не применила. Без until точка закрыта до конца местных суток. Время без смещения читается в часовом поясе точки; срок — не дальше 30 дней, потом точка откроется сама. closed:false открывает точку и снимает until. Нужна включённая настройка «Закрыть кодом *28» в маршрутизации, иначе 409 daynight_code_off. Закрытие — то же, что *28 с телефона: звонки идут правилами «вне часов», сервер не выключается.

Готовые сценарии#

Вчерашний журнал в CSV: Python#

Установите requests. Задайте PBX_KEY, PBX_TZ (часовой пояс точки, например Asia/Tbilisi) и PBX_LOCATION; запустите скрипт с перенаправлением > calls.csv. Он читает все страницы, экранирует CSV и обезвреживает формулы табличных редакторов. Выгружается то, что есть в окне журнала, а не полный архив.

import csv, os, sys
from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo
import requests

base = 'https://api.cenaly.ru/pbx-api/v1'
tz = ZoneInfo(os.environ['PBX_TZ'])
today = datetime.now(tz).date()
params = {'location': os.environ['PBX_LOCATION'], 'limit': 100,
          'since': datetime.combine(today - timedelta(days=1), time.min, tz).isoformat(),
          'until': datetime.combine(today, time.min, tz).isoformat()}
headers = {'Authorization': 'Bearer ' + os.environ['PBX_KEY']}
out = csv.writer(sys.stdout)
out.writerow(['id', 'startedAt', 'from', 'to', 'status', 'durationSec'])
def cell(value):
    s = str(value if value is not None else '')
    return "'" + s if s.startswith(('=', '+', '-', '@', '\t', '\r', '\n')) else s
while True:
    r = requests.get(base + '/calls', params=params, headers=headers, timeout=20)
    r.raise_for_status()
    page = r.json()
    for c in page['items']:
        out.writerow(map(cell, [c['id'], c.get('startedAt'),
            c.get('from', {}).get('number', ''), c.get('to', {}).get('extension', ''),
            c['status'], c.get('durationSec', 0)]))
    if not page.get('nextCursor'):
        break
    params['cursor'] = page['nextCursor']

Звонок в клик из CRM: Node.js#

Код выполняется на сервере CRM (Node.js 18+, файл .mjs), ключ — в PBX_KEY. Привяжите вызов к явному нажатию авторизованного сотрудника и блокируйте двойное нажатие в CRM.

const base = 'https://api.cenaly.ru/pbx-api/v1';
const headers = {'Authorization': `Bearer ${process.env.PBX_KEY}`,
                 'Content-Type': 'application/json'};
const r = await fetch(`${base}/dial`, {method: 'POST', headers,
  body: JSON.stringify({location: 'loc1', extension: '101', number: '+995555123456'}),
  signal: AbortSignal.timeout(20000)});
const result = await r.json();
if (!r.ok) throw new Error(JSON.stringify(result));
// Сохраните commandId в действии CRM; POST автоматически не повторяйте.
console.log({commandId: result.commandId, state: result.state});
const status = await fetch(`${base}/dial/${encodeURIComponent(result.commandId)}?location=loc1`,
  {headers, signal: AbortSignal.timeout(20000)});
if (!status.ok) throw new Error(await status.text());
console.log(await status.json());

Ночной режим по расписанию: curl#

Планировщик запускает команду вечером; UNTIL — время следующего открытия в ISO со смещением, не дальше 30 дней (для ночи с 24 на 25 октября — 2026-10-25T09:00:00+04:00). Вычисляйте дату заново при каждом запуске.

BASE='https://api.cenaly.ru/pbx-api/v1'
: "${PBX_KEY:?Задайте ключ}" "${UNTIL:?Задайте время открытия}"
BODY=$(jq -nc --arg until "$UNTIL" '{location:"loc1",closed:true,until:$until}')
curl -fsS -X PUT "$BASE/open-closed" -H "Authorization: Bearer $PBX_KEY" \
  -H 'Content-Type: application/json' -d "$BODY" | jq -e 'if .error then error(.error) else . end'

Открыть точку раньше — тот же PUT с {"location":"loc1","closed":false}. Проверяйте состояние через GET. Регулярные часы работы без внешнего планировщика настраиваются расписанием маршрутизации в админке.

Смотрите также#

Была ли статья полезной?