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.
Хотите увидеть эту функцию на своих данных?Покажем ТГИД на демонстрации и ответим на вопросы инженеров.
Запросить демо