API v2 для внешних систем
> Кратко. /api/v2 — стабильные адреса для ERP, CRM, 1С и шлюзов SCADA: лента событий, наряды, отключения, дефекты, создание заявки и приём показаний датчиков. Вход — ключ API (scope: api), который пользователь выдаёт себе в личном кабинете. Поля v2 только добавляются; удаление и переименование — только в v3.
Ключ
Выдать. Личный кабинет → «Ключ API v2 (внешние системы)» → «Выдать ключ» (POST /api/v1/api-key
с токеном входа). Ответ: token, expires_at, days, api_url, openapi_url. Ключ показывается один раз;
в окне рядом — адрес API, описание OpenAPI и пример запроса ленты.
Как действует:
- заголовок
Authorization: Bearer <ключ>; действует только на/api/v2/…— остальные адреса API отвечают 401; - срок —
API_TOKEN_DAYSдней (по умолчанию 365, не больше 730); - права — того, кто выдал ключ: чтение — любой вошедший, создание заявки и приём показаний — право «Журналы»;
персональные данные маскируются, если у него нет права «Персональные данные». Маскирование узнаёт поля
по имени (телефон, e-mail, ИИН, лицевой счёт, владелец). В свободном тексте (
title,description,note,comment) маскируются ФИО с отчеством, телефоны +7/8 и e-mail, если у владельца ключа нет прав «Журналы» и «Справочники»;addressдефекта отдаётся как есть; - выдача пишется в журнал действий (
API_KEY), действия по ключу — от имени пользователя.
Отозвать отдельный ключ нельзя: у ключей нет списка на сервере (как у ключей ArcGIS и токенов MCP). Ключ закрывается сменой пароля пользователя (у пользователей UsersDB ключ несёт отпечаток пароля) или блокировкой учётной записи. Поэтому для интеграции заводите отдельного пользователя с нужными правами: его пароль и блокировка — ваш рычаг отзыва.
Токен входа веба на /api/v2 тоже принимается — так удобно пробовать адреса из /docs.
Адреса
| Адрес | Что |
|---|---|
GET /api/v2 |
версия, город, ссылка на описание, типы событий |
GET /api/v2/openapi.json |
описание только v2 (openapi-generator, NSwag) |
GET /api/v2/events?after=&types=&limit= |
лента событий после номера seq: {data, next_after, has_more} |
GET /api/v2/work-orders?status=&updated_since=&after_id=&limit= |
наряды и заявки |
GET /api/v2/work-orders/{id} |
наряд с историей статусов |
POST /api/v2/work-orders |
заявка: title, description, kind, priority, место (line_id, node_id или lon + lat), planned_finish → 201 |
GET /api/v2/outages?status=&updated_since=&after_id=&limit= |
отключения |
GET /api/v2/outages/{id} |
отключение с зоной (GeoJSON, WGS 84) и историей |
GET /api/v2/defects?after_id=&limit= |
журнал дефектов |
GET /api/v2/defects/{id} |
дефект |
POST /api/v2/telemetry/readings |
показания датчиков пачкой |
Списки — по возрастанию id, limit до 500 (по умолчанию 100): {"data": [...], "next_after_id": N}.
Следующая страница — after_id=N; null — страниц больше нет. updated_since — изменённые с этого времени.
Время — ISO 8601 с часовым поясом.
Заявка без дубля. POST /api/v2/work-orders с заголовком Idempotency-Key (до 100 символов, например номер
обращения в CRM): повтор с тем же ключом от того же пользователя возвращает тот же наряд — 200 и заголовок
Idempotent-Replayed: true. Тот же ключ с другим названием — 422. Без места — 422. Запись выключена на сервере
(MUTATIONS_ENABLED=false) — 503.
Показания датчиков (шлюз SCADA, ОДПУ): {"readings": [{"sensor": "<код датчика>", "ts": "…", "value": 4.2}]},
до 10 000 за запрос. Время без пояса — местное время сервера; повтор того же времени заменяет значение.
Неизвестные и выключенные коды и время больше чем на сутки вперёд пропускаются и перечисляются в ответе:
{"accepted", "skipped", "unknown_codes", "future"}. Датчики заводят в окне «Телеметрия: факт против модели» (право «Справочники»).
Ошибки — {"detail": "…"}: 401 — ключ (нет, истёк, «Пароль сменён — выдайте новый ключ API»), 403 — права,
404, 409, 422. Язык — по Accept-Language.
Примеры
Копия Алматы, стенд без входа (на рабочем сервере добавьте -H "Authorization: Bearer $KEY").
curl -s "$API/api/v2"
{"version":"2.0.0","city":"almaty","openapi":"http://127.0.0.1:8090/api/v2/openapi.json",
"event_types":{"defect.created":"Новый дефект (авария, нарушение) — из веба, с обхода или из десктопа", "…": "…"}}
Лента событий — первые два события копии (датчики учебного пилота):
curl -s "$API/api/v2/events?limit=2"
{"data":[{"id":"evt_almaty_56","seq":56,"type":"sensor.silent","occurred_at":"2026-10-30T14:24:35Z","city":"almaty",
"data":{"sensor_id":24,"code":"ALM-N710-P2","kind":"pressure","pipe":"return","status":"silent","prev_status":"normal",
"value":3.5966,"model":3.6713,"unit":"kgf_cm2","node_id":534627,"alert_id":4,"…":"…"}},
{"id":"evt_almaty_57","seq":57,"…":"…"}],
"next_after":57,"has_more":true}
Следующий запрос — ?after=57. Номер seq храните у себя: события старше 30 дней удаляются.
Показания на несуществующий код — ничего не записывается:
curl -s -X POST "$API/api/v2/telemetry/readings" -H "Content-Type: application/json" \
-d '{"readings":[{"sensor":"SYNTH-E2E-NO-SUCH","ts":"2026-11-06T10:00:00+05:00","value":4.2}]}'
# {"accepted":0,"skipped":1,"unknown_codes":["SYNTH-E2E-NO-SUCH"],"future":0}
Синхронизация без вебхуков: раз в минуту GET /api/v2/events?after=<последний seq>, по событиям дочитывать
объекты (/work-orders/{id}, /outages/{id}). Сразу узнавать о событиях — вебхуки.
Для разработчика
- API:
routers/api_v2.py(модели ответа — pydantic, по ним строится описание),routers/telemetry.py(показания),database/api_events.py(лента). Новый маршрут v2 обязан иметь зависимостьUser(api_user): шлюзAUTH_REQUIRED_GETадреса/api/v2пропускает. - Веб:
components/ApiKeyPanel.vueвAccountDialog. - Тесты: API —
tests/test_webhooks.py(ключ только для v2, смена пароля,Idempotency-Key); e2e —e2e/functions/developer-api.spec.ts.
