SvitloPulse

API

Відкрите JSON API: ті самі дані, що на карті. Без ключа та реєстрації.

Загальне

  • База: той самий домен, шлях /api/v1. JSON, UTF-8.
  • Час у форматі RFC 3339: у відповідях за Києвом, в історії в UTC.
  • Читання віддається з незмінного знімка ревізії: сильний ETag і gzip, Cache-Control: public, max-age=15..30. Надсилайте If-None-Match, щоб отримати 304.
  • Помилки: {"error": "…", "code": "machine_code"} зі статусами 400, 403, 404, 422, 429, 503.
  • Ліміти: 600 читань на хвилину з IP (сплеск 150), 30 записів на хвилину. Записи потребують того самого походження (заголовок Origin) і ставлять анонімний HttpOnly-cookie sp_dev.

Коди та стани

oblast iso
ISO 3166-2, наприклад UA-46. Кожна область має й slug.
group
Черга оператора, ключ <operator_id>:<group>, наприклад lvivoblenergo:3.2.
group.state
on | off | maybe | unknown. «unknown» ніколи не слід малювати як «on».
group.class
planned | emergency | local | unknown | on
oblast.state
on | off | partial | emergency | unknown. off_share — частка черг без світла (0..1); stale — дані оператора старші за 45 хвилин.
restore_at
ПЛАНОВИЙ час повернення (кінець поточного інтервалу вимкнення). next_at / next_state — наступна планова зміна.

Час без даних повертається як unknown_min і не входить у on_share та avg_off_share.

Поточні дані

МетодШляхЩо повертає
GET/summaryЗведення: кількість черг і областей із відключеннями, свіжість операторів. Поле demo: true означає синтетичні дані.
GET/oblastsУсі 27 одиниць (24 області, Крим, Київ, Севастополь): стан, частка черг без світла, ознака «застаріло».
GET/oblasts/{slug}Одна область: оператори, черги, режими, 12 найбільших населених пунктів.
GET/operatorsОператори: назви, кількість черг, скільки вимкнено, свіжість.
GET/groups/{operator}/{group}Черга: стан, клас, ймовірність, впевненість, покриття, плановий час повернення, графіки на сьогодні й завтра.
GET/ics/{operator}/{group}Планові відключення черги як iCalendar-стрічка (сьогодні, завтра та все, що вже відомо). Підпишіться в Google або Apple Calendar. Лише плани, без сигналів.
GET/places?oblast=UA-46&limit=437 населених пунктів зі станом їхніх черг.
GET/places/{slug}Населений пункт: черги та найближчі пункти.
GET/search?q=Пошук населених пунктів та відомих адрес. Не кешується.
GET/streamSSE: подія snapshot із номером ревізії після кожного перерахунку (~30 с), коментар-«пульс» кожні 25 с. Після події перечитайте /summary та /oblasts.

Історія

МетодШляхЩо повертає
GET/history/map?at=Стани областей на момент T (RFC 3339 або unix), крок 5 хвилин, до 400 днів назад.
GET/history/oblast/{iso}?from&toСегменти станів області та доступність (on/off/maybe/unknown, частки). Вікно до 31 дня, за замовчуванням остання доба.
GET/history/group/{operator}/{group}?from&toТе саме для однієї черги.
GET/history/daily/{iso}?days=14Підсумки по кожній добі за Києвом (для календаря-теплокарти).
GET/history/plan/{operator}/{group}?asof=&date=Графік у тому вигляді, як його опублікував оператор на момент asof.

Обговорення, звіти, підписки

МетодШляхЩо повертає
GET/posts?scope=&before=&limit=Обговорення. scope: global, oblast:UA-46, place:<slug>, group:<оператор>:<черга>.
GET/posts/{id}/repliesВідповіді на допис.
POST/postsСтворити допис {scope, body, parent_id?}. 201 {id}. 422 з кодом restricted_topic, links_not_allowed, phone_not_allowed, coordinates_not_allowed, abuse, too_short, too_long, no_text. 429 після 5 дописів за 10 хвилин.
POST/posts/{id}/likeПеремикач лайка. Відповідь {likes}.
POST/posts/{id}/reportСкарга {reason}. Після 3 скарг допис приховується.
POST/reportsЗвіт {operator_id, group, state: "on"|"off"}. 202. Сигнал публічний лише після кворуму і затримки, а не окремо.
POST/subscribe-linkОдноразове посилання на бота {operator_id, group, place_slug?, label?, address_text?, lead_minutes?[5..180]}. Відповідь {url, token, expires_in_s, bot_enabled}. Дійсне 30 хвилин.
GET/bot{username, url, enabled}

Приклади

curl

curl -s https://svitlopulse.andrewboyko.name/api/v1/summary
curl -s https://svitlopulse.andrewboyko.name/api/v1/oblasts | jq '.oblasts[] | {slug, state, off_share}'
curl -s "https://svitlopulse.andrewboyko.name/api/v1/history/map?at=2026-11-04T10:00:00Z"

JavaScript

const r = await fetch('/api/v1/oblasts');
const { oblasts } = await r.json();
const off = oblasts.filter((o) => o.state === 'off' || o.state === 'emergency');

// live updates: refetch when the server recomputes (~30 s)
const es = new EventSource('/api/v1/stream');
es.addEventListener('snapshot', () => refresh());

Фрагмент відповіді /summary

{
  "demo": false,
  "rev": 1280,
  "at": "2026-11-04T22:11:39+02:00",
  "fact_enabled": true,
  "fact_delay_min": 30,
  "summary": { "groups": 324, "groups_off": 52, "oblasts_affected": 20, "emergency": false },
  "operators_total": 25, "operators_fresh": 23, "oblasts_total": 27
}

Числа вище — ілюстрація формату, а не реальні дані.

Правила приватності

  • Публічні дані агреговані до рівня черги й області.
  • Індивідуальні сигнали з’являються лише від 5 різних джерел і з затримкою не менше 30 хвилин (поле fact_delay_min).
  • Немає шарів підстанцій чи ліній електропередач.
  • Обговорення відхиляють повідомлення про обстріли та координати.

Чесні застереження

  • Це не офіційне джерело і не гарантія подачі світла. Первинні дані належать операторам.
  • Формат API v1 може розширюватися новими полями. Не покладайтеся на відсутність невідомих полів.
  • Не робіть із нього систему автоматичних рішень там, де від цього залежить безпека людей.
  • Поважайте ліміти. Для частих оновлень використовуйте /stream і ETag, а не опитування.
  • Потрібні вищі квоти чи вебхуки? Є ключі (заголовок X-API-Key або Authorization: Bearer): Для розробників. Бейджі, картку для iframe й конфіг Home Assistant генерує сторінка «Віджети і бейджі».