Документация для интеграции (satya-dharma и другие клиенты). UI календаря — только QA-предпросмотр.
Base URL этого стенда:
OpenAPI 3.1 (JSON) — машиночитаемый контракт календарного ядра API и профиля Satya Dharma; текущая версия HTTP-контракта — 1.2.2.
Большинство JSON-эндпоинтов:
// успех
{ "success": true, "data": { ... }, "message": "..."? }
// ошибка (часто HTTP 400)
{ "success": false, "error": "...", "error_code": "VALIDATION_ERROR", "error_details": ...? }
GET /health — исключение: { "status": "ok", "version": "..." } без envelope; поле version содержит build SHA. Версия всего приложения — 2.5.12 (SemVer), она отдельно показана в шапке QA-календаря.
Кэш API: заголовок Cache-Control: private, no-cache.
Ответы гzip-сжаты, если клиент шлёт Accept-Encoding: gzip (браузеры и большинство HTTP-клиентов делают это сами) — Content-Encoding: gzip в ответе, тело меньше в разы (~6× на /api/calendar-data). Ответы короче ~500 байт (напр. ошибки валидации) не сжимаются — оверхед gzip-заголовка не окупается.
Для /api/calendar-data, /api/calendar-data/year, /api/calendar-data/next-event, /api/day-detail, /api/day-detail/astronomy и /api/day-detail/fasting-logic — два способа:
geonames_id — любая запись каталога city_catalog (~170k городов + несколько кастомных записей вроде Kosshy/Mayapur, см. ниже; результат /api/locations/catalog);lat + lon + timezone_offset вместе (рекомендуется внешним сайтам со своим списком городов).Если передано и то, и другое — побеждает молча geonames_id. Невалидный/несуществующий geonames_id → VALIDATION_ERROR, не тихая подстановка другого города. Часть координатной тройки → VALIDATION_ERROR. Ничего не передано → geonames_id по умолчанию (Алматы, 1526384).
Все четыре эндпоинта календаря (calendar-data, calendar-data/year, calendar-data/next-event, day-detail) и day-detail/fasting-logic дополнительно принимают caturmasya_system=purnima|pratipat|ekadasi — какая из трёх традиций расчёта границ месяцев Чатурмасьи используется. По умолчанию — purnima — дефолт самой gaurabda из коробки, и он же совпадает с vaisnavacalendar.info (эталон, с которым сверяется satya-dharma). Неизвестное значение → VALIDATION_ERROR. Значение, которое реально применилось, всегда есть в ответе как поле caturmasya_system.
pratipat — единственной проверкой тогда был Будапешт, где он побитово совпал с эталоном. На выборке из 202 городов × 2026 год это не подтвердилось: purnima совпадает с эталоном в 174/202 городов, pratipat — только в 11/202 (Будапешт — один из этих 11, не типичный случай). Дефолт вернули на purnima.
Те же эндпоинты принимают и fasting_note_style=classic|modern — формулировка заметок о посте при праздниках святых (GCDisplaySettings[42] в gaurabda): classic — «(Fast till noon for X, with feast tomorrow)», modern — «(Fast today for X)». По умолчанию — classic: на полной выборке из 202 городов × 2026 год против vaisnavacalendar.info под classic текст заметок совпадает с эталоном на 75.8% дат (3316/4375), под modern (дефолт самой gaurabda) — на 12.9% (561/4356). В отличие от caturmasya_system проверка на большой выборке подтвердила исходный результат — дефолт не менялся. Тот же контракт: неизвестное значение → VALIDATION_ERROR, применённое значение — в ответе рядом с caturmasya_system.
Диапазоны: год в year и параметрах-датах — 2..9998 (крайние годы исключены, поскольку расчёт использует соседние дни), month=1..12, lat -90..90, lon -180..180, timezone_offset=-14..14 часов от UTC (Алматы: 5). Нечисловые и неканонические значения не подменяются дефолтами, а возвращают VALIDATION_ERROR.
/api/locations/catalog, ничего никуда не пишется) либо координаты + TZ, если свои координаты городов уже есть на стороне сайта.
location в ответеКалендарные эндпоинты (calendar-data, calendar-data/year, calendar-data/next-event, day-detail, day-detail/astronomy, day-detail/fasting-logic) возвращают в data.location резолвленную локацию — не то, что было передано в запросе, а то, что реально использовалось для расчёта:
| Поле | Описание |
|---|---|
geonames_id | При адресации по координатам — null. Отрицательный (<0) — синтетическая запись вне GeoNames (сейчас только Mayapur, см. schema.CUSTOM_CITIES): своей страницы на geonames.org у неё нет, ссылку по https://www.geonames.org/<geonames_id> строить нельзя. |
coordinate_source | Источник фактически использованных координат: geonames, custom_catalog (курируемая точка вне GeoNames) или request (явные lat/lon из запроса). |
lat, lon, alt | Координаты и высота (м), по которым фактически считался календарь. |
timezone_offset | «Стандартный» (не-DST) числовой offset от UTC — тот же смысл, что и в параметре запроса. |
tzname | IANA-зона (напр. America/New_York) — при адресации по координатам null. По ней же (не по timezone_offset) в gaurabda DST-осведомлённо считаются восход/закат/титхи для городов с переходом на летнее время. |
name, country, region | При адресации по координатам — null. name — русское имя, если оно есть в каталоге, иначе исходное (та же логика, что и в /api/locations/catalog). |
docs/research/vaisnavacalendar-coordinate-audit.md). Собственный UI gcal показывает под выбором города строку вида «Ноксвилл, Теннесси, US» / «35.96064, -83.92074 · America/New_York · Источник: GeoNames 4634946», где GeoNames ID — ссылка на geonames.org; для курируемой точки вне GeoNames явно указывается «Источник: каталог gcal».
gaurabda, восход — верхний край диска с номинальной рефракцией 50′, aruṇodaya — 96 минут до восхода. Исследовательские оси (альтернативные эфемериды, горизонт, aruṇodaya) доступны только с research=1 в query string и не входят в OpenAPI; см. docs/research/internal-research-api.md.
GET /api/calendar-dataСетка месяца + события.
| Параметр | Описание |
|---|---|
year, month | Год 2–9998, месяц 1–12. По умолчанию — текущие. |
| локация | см. выше |
compact | 1 — только location/year/month/events[]/payload_version, caturmasya_system, fasting_note_style, event_categories (без days{}/ekadashi_events[]/caturmasya[]/gaurabda_year — нужны только собственному UI gcal, не считаются вовсе при compact). Без параметра — полный ответ, как раньше. |
caturmasya_system | см. выше. По умолчанию purnima. |
fasting_note_style | см. выше. По умолчанию classic. |
event_categories | см. «События events[]» ниже. Список чисел 0..6 через запятую — фильтр по «важности»/типу праздника. Не передан — фильтр не применяется. |
data (без compact): location, year, month, gaurabda_year, days{}, ekadashi_events[], events[], caturmasya[], payload_version, caturmasya_system, fasting_note_style, event_categories.
GET /api/calendar-data?geonames_id=1526384&year=2026&month=9
GET /api/calendar-data?geonames_id=1526384&year=2026&month=9&compact=1 # без grid-only days/ekadashi_events/caturmasya
compact=1 — используется только events[] (Экадаши/Махадвадаши/Чатурмасья видны как записи event_kind внутри него), в разы меньше трафика. См. «Формат ответа» про gzip — работают вместе (~11× меньше суммарно на замере: 11800 → ~1050 байт).payload_version (сейчас v17.2) — тот же идентификатор, что gcal использует в ключе собственного кэша года (_YEAR_PAYLOAD_VERSION + EVENT_CLASSIFIER_VERSION); меняется только при изменении логики расчёта/схемы событий, не на каждый деплой. v17 отделяет кэши по точке диска, рефракции и определению aruṇodaya; суффикс .2 — версия классификатора событий.
GET /api/calendar-data/yearevents[] за весь год одним запросом — без сетки (days{}) и без месячного фильтра. Отдельного расчёта не требует: год и так считается и кэшируется целиком (gcal_cache.get_year), этот эндпоинт просто не фильтрует его по месяцу.
| Параметр | Описание |
|---|---|
year | По умолчанию — текущий. |
| локация | см. выше |
caturmasya_system | см. выше. По умолчанию purnima. |
fasting_note_style | см. выше. По умолчанию classic. |
event_categories | см. «События events[]» ниже. |
data: location, year, events[], payload_version, caturmasya_system, fasting_note_style, event_categories — форма ответа всегда компактная, отдельного compact-параметра нет.
GET /api/calendar-data/year?geonames_id=1526384&year=2026 # events[] за весь 2026 год, caturmasya_system=purnima + fasting_note_style=classic (дефолты)
GET /api/calendar-data/year?geonames_id=1526384&year=2026&caturmasya_system=pratipat
GET /api/calendar-data/year?geonames_id=1526384&year=2026&fasting_note_style=modern
GET /api/calendar-data/year?geonames_id=1526384&year=2026&event_categories=4 # только ISKCON's historical events среди festival
GET /api/calendar-data/next-eventПервое подходящее событие с датой date >= from. Если его нет в оставшейся части года, автоматически проверяется следующий календарный год; дальше поиск не продолжается. Использует тот же годовой кэш, но не строит и не сериализует полный events[].
| Параметр | Описание |
|---|---|
from | YYYY-MM-DD, включительно. По умолчанию — текущая дата в часовом поясе выбранной локации. |
event_kinds | Обязательный непустой список через запятую: ekadashi, mahadvadashi, parana, festival, caturmasya, sankranti, other. |
| локация | см. выше |
caturmasya_system | см. выше. По умолчанию purnima. |
fasting_note_style | см. выше. По умолчанию classic. |
data: location, from, event (обычное публичное событие с добавленным date либо null), payload_version, caturmasya_system, fasting_note_style, event_categories.
GET /api/calendar-data/next-event?geonames_id=1526384&from=2026-07-31&event_kinds=ekadashi,mahadvadashi
{
"success": true,
"data": {
"location": {
"geonames_id": 1526384,
"coordinate_source": "geonames",
"lat": 43.25249,
"lon": 76.9115,
"alt": 783.0,
"timezone_offset": 5.0,
"name": "Алматы",
"country": "KZ",
"region": "Алматы",
"tzname": "Asia/Almaty"
},
"from": "2026-07-31",
"event": {
"date": "2026-08-09",
"event_code": "EKADASHI",
"event_kind": "ekadashi",
"text": "Fasting for Kamika Ekadasi"
},
"payload_version": "v17.2",
"caturmasya_system": "purnima",
"fasting_note_style": "classic",
}
}
1.2.2 (info.version в OpenAPI). payload_version версионирует содержимое годового расчётного payload и ключ его кэша, а не набор доступных маршрутов; текущее значение — v17.2.
GET /api/day-detailДетали одного дня (времена, сегменты титхи, накшатра, йога, раши Солнца и Луны, экадаши/парана, sankranti).
| Параметр | Описание |
|---|---|
date | Обязателен, YYYY-MM-DD |
| локация | см. выше |
caturmasya_system | см. выше. По умолчанию purnima. |
fasting_note_style | см. выше. По умолчанию classic. |
event_categories | см. «События events[]» ниже. |
Расширенный панчанга: nakshatra_elapsed_percent (0..<100), nakshatra_pada (1..4), yoga/yoga_name (0..26), sun_rasi/sun_rasi_name и moon_rasi/moon_rasi_name (0..11). Значения относятся к восходу дня. Эти поля не добавляются в compact-ответы и /api/calendar-data/year.
date_timezone_offset — фактическое дневное UTC-смещение на выбранную дату с учётом DST (режим в местный полдень, используемый суточным расчётом Gaurabda). Это не замена IANA-зоне для каждого момента: в ночь перехода смещение в 00:00 может отличаться, поэтому готовые JD форматируются по tzname. В ekadashi{} явно отдаются ekadashi_date и fast_day; у постового дня parana{} содержит date, time1, time2. Поэтому клиенту не нужно выводить относительное «завтра» или самостоятельно вычислять дату перенесённого поста/паранама.
Структурные признаки: is_ksaya, ksaya_tithi (1..30 или null), vriddhi_day (1, 2 или null), masa_start/masa_end. Они формируются из соседних дней, уже рассчитанных GCal, без включения английских display-заметок. is_ksaya стоит на дне после пропущенной титхи; vriddhi_day различает первый и второй восход повторённой титхи. Те же поля есть у дня в полном days{}; в compact и годовой endpoint не входят.
moonrise_time/moonset_time — местное HH:MM либо null, если события нет внутри этих гражданских суток. Расчёт точечный, топоцентрический, для центра диска Луны на горизонте 0° без рефракции; дорогие годовые ключи GCDisplaySettings[4]/[5] не включаются. Нативное ядро включается только после self-check с Python-путём, а результат дня лениво кэшируется в ограниченной памяти worker'а. На независимой сверке 1825 городо-дней со Swiss Ephemeris совпадение наличия событий — 100%, медиана времени около 2.8 сек, максимум 18.7 сек.
arunodaya_offset_minutes показывает фактический интервал до восхода; night_muhurta_minutes — одну пятнадцатую предыдущей фактической ночи. При fixed-варианте первое поле равно 96 минутам, а второе остаётся сезонным справочным значением; при temporal-варианте первое равно удвоенному второму.
GET /api/day-detail?geonames_id=1526384&date=2026-09-04
GET /api/day-detail/astronomyЛенивая астрономическая часть объединённой модалки дня. Принимает обязательные date=YYYY-MM-DD и локацию. Расширенный набор (карана, pāda, мухурты, транзиты) — только с research=1&extended_panchanga=1; см. docs/research/internal-research-api.md. Имеет отдельный SQLite/LRU-кэш и не меняет годовой payload_version.
civil_day_hours — реальная длина гражданской даты (24, на переходе DST 23/25 часов); day_length_hours/night_length_hours, тропические долготы Солнца/Луны и tithi_angle_deg в Arunodaya;ayanamsha_arunodaya_deg — поворот кольца сидерических раши; *_longitude_day_start_deg/*_longitude_day_end_deg — положения в местные 00:00 и следующие 00:00 для внутренних дуг движения (23/25-часовые DST-сутки учитываются);tithi_segments/nakshatra_segments — сводка гражданских суток; массивы *_chart_segments для титхи, накшатр, точных pāda, каран, йог и раши покрывают общую ось графика -3…+27 часов;muhurta_segments — 45 отрезков: предыдущая ночь, день и следующая ночь по 15; altitude_curve — высоты Солнца/Луны с шагом 15 минут, с локальными date/time каждой точки для корректной пропущенной/повторённой DST-часовой метки;sun_transit_*/moon_transit_* — уточнённые верхние кульминации.Соглашения расчёта: титхи/карана используют геоцентрическую разность долгот центров Луны и Солнца; накшатра, pāda и rāśi Луны — геоцентрическую долготу центра Луны с GCal Lahiri ayanāṃśa; нитья-йога — сумму долгот центров с двойной ayanāṃśa. Край диска, горизонт и рефракция для этих величин не участвуют. В default-режиме центр Солнца находится на −0.833° потому, что выбран верхний край (+16′) и фиксированная рефракция (+34′); альтернативы задаются независимыми ключами выше. Moonrise/moonset — топоцентрический центр Луны на геометрическом горизонте 0° без рефракции. Высоты на графике также геометрические, без рефракции.
На UI участки кривых выше горизонта сплошные, ниже — пунктирные. События Солнца подписаны сверху, события Луны/титхи/смены раши — снизу.
GET /api/day-detail/astronomy?geonames_id=1526384&date=2026-09-04
GET /api/day-detail/fasting-logicПояснение выбора дня поста экадаши для QA-UI (кнопка «Почему этот день?» в модалке дня). Читает уже кэшированный год, астрономию не пересчитывает, payload_version не меняет. 404, если дата не из окна календарной экадаши / дня поста / паранам.
В steps[] только сработавшие проверки GCal (MahadvadasiCalc → EkadasiCalc → окно паранам), без перечня отпавших соседних типов. sources[] — цитаты Hari-bhakti-vilāsa по сработавшему типу.
| Параметр | Описание |
|---|---|
date | Обязателен, YYYY-MM-DD. |
| локация | см. выше. |
caturmasya_system | см. выше. По умолчанию purnima. |
fasting_note_style | см. выше. По умолчанию classic. |
GET /api/day-detail/fasting-logic?geonames_id=1526384&date=2026-08-24
GET /api/panchanga-timeline (internal)Внутренний/отладочный эндпоинт (x-internal в OpenAPI). QA UI и satya-dharma его не вызывают; та же логика используется внутри day-detail/astronomy. Точные по модели GCal моменты начала титхи, санкранти, накшатры и йоги в полуоткрытом интервале местных гражданских дат [start, end). Расчёт ленивый и не изменяет годовой/compact payload.
| Параметр | Описание |
|---|---|
start | Обязателен, YYYY-MM-DD, включительно. |
end | Обязателен, YYYY-MM-DD, не включается; интервал от 1 до 32 дней. |
| локация | см. выше; нужна для локального времени и границ гражданских дат. |
types | tithi,sankranti (по умолчанию); также допустимы nakshatra, yoga, conjunction, rahu_kalam, yama_ghanti, guli_kalam, abhijit. |
research | Только для QA/research: 1 включает исследовательские оси расчёта и metadata в ответе; без ключа — GCal default, override молча игнорируется. См. docs/research/internal-research-api.md. |
data: location, start, end, types, events[]. С research=1 дополнительно metadata расчёта (см. internal-research-api). Общие поля события: event_kind, at_utc, local_date, local_time. У tithi_start: tithi_number (1..30), tithi_name; у sankranti и conjunction: rasi (0..11), rasi_name; у nakshatra_start: nakshatra/nakshatra_name (0..26); у yoga_start: yoga/yoga_name (0..26).
payload_version.GET /api/panchanga-timeline?geonames_id=1526384&start=2026-04-01&end=2026-05-01
GET /api/panchanga-timeline?geonames_id=1526384&start=2026-04-01&end=2026-05-01&types=sankranti
GET /api/panchanga-timeline?geonames_id=1526384&start=2026-04-01&end=2026-05-01&types=nakshatra,yoga
GET /api/panchanga-timeline?geonames_id=1526384&start=2026-04-01&end=2026-05-01&types=conjunction
GET /api/research/arunodaya-scales (internal)Внутренний/исследовательский эндпоинт (x-internal в OpenAPI). Только с research=1 (иначе 404). QA-графики трёх шкал aruṇodaya за год; satya-dharma не вызывает. Не меняет year-payload. Подробности — docs/research/internal-research-api.md.
| Параметр | Описание |
|---|---|
year | Год; без ключа — текущий гражданский год. |
| локация | см. выше. |
research | Обязателен: 1, true или yes. |
data: location, year, days[] с часами от местной полуночи (sunrise_hour, arunodaya_96_hour, arunodaya_215_hour, arunodaya_1212_hour, solar6_hour; null в полярные сутки). Плюс metadata расчёта. Выбор arunodaya_definition на состав рядов не влияет — всегда все три шкалы.
GET /api/research/arunodaya-scales?geonames_id=1526384&year=2026&research=1
events[]Каждый элемент: { date?, event_code, event_kind, text, event_category?, event_category_name?, fasttype?, fastsubject? }.
event_kind: ekadashi | mahadvadashi | parana | festival | caturmasya | sankranti | otherevent_code: стабильный id в рамках версии gaurabda (SPEC_*, EVT_<index>, …)event_category/event_category_name — только у event_kind == "festival" (у остальных kind'ов поле отсутствует, не null — у них этой классификации в gaurabda просто нет). event_category — число 0..6:
| 0 | Appearance days of the Lord |
|---|---|
| 1 | Events in the pastimes of the Lord |
| 2 | Appearance/disappearance of recent acaryas |
| 3 | Appearance/disappearance of Mahaprabhu's associates and other acaryas |
| 4 | ISKCON's historical events |
| 5 | Bengal-specific holidays |
| 6 | Personal events |
event_categories=0,4 (все три эндпоинта календаря) — список чисел 0..6 через запятую, оставляет в events[] только festival-события этих категорий. Остальные event_kind (экадаши, парана, Чатурмасья, санкранти...) фильтр не трогает — у них категории нет, они приходят всегда. Параметр не передан — фильтр не применяется. Неизвестное число → VALIDATION_ERROR. Применённый фильтр отражён в ответе как event_categories (null, если не передавали).
is_fast — не поле events[] (элементы события его не несут, это раньше было ошибочно указано здесь). Это отдельное булево поле на уровне дня — внутри days{} (полный, не-compact ответ calendar-data) и на верхнем уровне ответа /api/day-detail. Означает только пост экадаши/махадвадаши (есть запись в ekadashi_events на этот день), не пост при явлении святого — тот определяется по fasttype/fastsubject самого festival-события в events[]. satya-dharma, работающий только с compact=1/events[], это поле не видит и не использует.
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api/locations | Динамический список городов UI gcal — те, для кого уже посчитан кэш года, не статичный справочник: limit? 1–200, sort? recent (по умолчанию, недавность обращения) или frequent (суммарный access_count); каждая запись отдаёт use_count. Ответ — private, no-cache; UI запрашивает его заново при загрузке/F5, но не при переходе стрелками между месяцами. |
| GET | /api/locations/catalog | Поиск в каталоге GeoNames: search?, limit? 1–200, compact? 1 — только geonames_id/name/name_ru/country/region/population/timezone_offset |
| GET | /api/locations/nearest | Ближайшие из city_catalog (весь GeoNames, ~170k): обязательные lat=-90..90, lon=-180..180, limit? 1–20. Отдаёт geonames_id, не id — использовать с /api/calendar-data?geonames_id=... |
Единственный справочник локаций — city_catalog (~170k городов, сид из data/cities_geonames.csv при первом старте БД, плюс несколько записей вне GeoNames — Kosshy/Mayapur, см. schema.CUSTOM_CITIES). Отдельного куррированного справочника с внутренним id больше нет — всё адресуется по geonames_id.
Mayapur возвращает две географически разные точки. Собственный UI ставит первой «Маяпур — ISKCON, рекомендуемая точка» (geonames_id=-1, 23.423413, 88.388079) и отдельно подписывает GeoNames 1263253 как «другая деревня» — она находится примерно в 116 км и для календаря ISKCON-Маяпуры не подходит.
Типичный поток для UI / сайта:
GET /api/locations/catalog?search=Алматы — найти город (поля: id каталога — служебный, не для адресации, name, name_ru, lat, lon, timezone_offset, country, geonames_id, region, population, tzname — IANA-зона, напр. Asia/Almaty; результаты отсортированы по населению, крупные города — в начале списка); timezone_offset в этом ответе — «стандартный» (не-DST) числовой offset, как и раньше — но сам расчёт календаря (events[]/days{} из /api/calendar-data) DST-осведомлён: для городов с переходом на летнее время восход/закат/титхи/накшатра/санкранти считаются по фактическому смещению на каждую дату (по tzname из каталога), не по одному фиксированному числу на весь год;geonames_id из найденной записи — ничего никуда не пишется: GET /api/calendar-data?geonames_id=<geonames_id>&...;lat/lon/timezone_offset без обращения к каталогу вовсе./api/locations/catalog.
geonames_id, остальное — для city picker'а) рекомендуется compact=1: geonames_id/name/name_ru/country/region/population/timezone_offset — в разы меньше трафика, чем полный ответ, но всё ещё различает одноимённые города (в каталоге реально встречаются разные города с одинаковым name_ru в одной стране — напр. три разных "Aktau"/"Актау" в Казахстане; без region/population в подсказках city picker'а они были бы неразличимы). Собственный поиск города в UI gcal тоже ходит с compact=1 — timezone_offset в компакте хватает для подписи в <select>.
# поиск (компактно, для автокомплита)
curl -sS 'BASE/api/locations/catalog?search=Almaty&limit=5&compact=1'
# сразу расчёт по geonames_id из результата поиска, компактный ответ
curl -sS 'BASE/api/calendar-data?geonames_id=1526384&year=2026&month=9&compact=1'
# ближайшая Экадаши/Махадвадаши для hero
curl -sS 'BASE/api/calendar-data/next-event?geonames_id=1526384&from=2026-07-31&event_kinds=ekadashi,mahadvadashi'
APP_VERSION → 2.5.12 (SemVer, показан в шапке QA-календаря)
GET /health → { "status": "ok", "version": "..." } # build GIT_SHA
GET /openapi.json → OpenAPI 3.1, JSON
GET / → HTML QA-календарь
GET /docs → эта страница