Обзор эндпоинтов

Полное машинно-сгенерированное описание — в интерактивном справочнике. Здесь — карта API с пояснениями, которых в схеме нет.

Наблюдения и задания

/api/observations/

Список и карточки наблюдений; сюда же станция загружает аудио и водопад.

Запись доступна владельцу станции (StationOwnerPermission). При загрузке артефакта, который уже загружен, возвращается 403 с телом:

{"detail": "<артефакт> has already been uploaded", "code": "already_uploaded"}

Важно

Формулировка has already been uploaded — часть контракта, а не текст для человека: клиент станции распознаёт успешный повтор именно по этой подстроке. Менять её нельзя, пока весь парк клиентов не перейдёт на чтение поля code.

DELETE отменяет наблюдение, которое ещё не началось. Право спрашивается тем же delete_perms, по которому в интерфейсе появляется кнопка удаления: автор, владелец станции, модератор, суперпользователь. Начавшееся наблюдение не удаляет никто — 403 с code: cannot_delete. Успех — 204 без тела.

Фильтры по времени: start (не раньше) и end (не позже) выбирают наблюдения целиком внутри окна; start__lt и end__gt — вторые половины тех же границ, ими выбираются наблюдения, которые в заданный момент шли.

Чтение списка ограничено по частоте — OBSERVATIONS_LIST_ANON_THROTTLE_RATE (60/час) и OBSERVATIONS_LIST_AUTH_THROTTLE_RATE (240/час). Ограничение висит только на списке: карточка наблюдения, планирование и загрузка артефактов не ограничены, поэтому станция его не замечает.

/api/jobs/

Задания для станции — будущие наблюдения с параметрами приёма. Только чтение. Новый клиент запрашивает его при старте контейнера и пока нет связи с брокером; дальше тот же список приходит retained-сообщением stations/<id>/jobs при каждом изменении расписания (см. «Расписание станции» ниже). Старый клиент опрашивает его раз в минуту. Анонимный доступ управляется JOBS_ALLOW_ANONYMOUS (по умолчанию включён).

Одиннадцать полей ответа неизменны с 2021 года. Всё, что добавлено позже, выдаётся только по явному объявлению клиента — ?capabilities=norad_cat_id,transmitter_parameters,max_altitude: клиент, не объявивший ничего, получает прежний ответ байт в байт. transmitter_parameters берётся из снимка наблюдения, то есть описывает передатчик таким, каким он был на момент планирования.

/api/demoddata/

Демодулированные кадры. Чтение — только с аутентификацией, отправка кадра по протоколу SIDS открыта и анонимам (SafeMethodsWithPermission); привязать кадр к станции или наблюдению может только владелец. Отправка ограничена по частоте — DEMODDATA_CREATE_THROTTLE_RATE (3600/час на пользователя или IP), чтение не ограничено.

Список постраничный, с курсором, от новых кадров к старым (-timestamp, -id). По умолчанию на странице 25 кадров, ?page_size= поднимает размер до 1000. Кадр и payload_json берутся из копии в базе (DemodDataContent), поэтому страница стоит один SQL-запрос и ни одного обращения к S3. Кадры, принятые до появления копии, читаются из файла, пока их не скопирует copy_demoddata_content.

Как выкачивать архив: page_size=1000, переходить по next и распараллеливать по спутникам или по окнам start/end. Новые кадры по мере приёма удобнее получать по MQTT (см. Спутники и передатчики), а REST использовать для догонки.

Имя файла разбирается по контракту <префикс>_<id>_<временная метка>.<расширение>, метка допускается с разделителем времени - или :. Имя, не соответствующее контракту, отбрасывается штатно, а не приводит к ошибке 500.

Разобранная метка должна попадать в окно наблюдения с допуском DEMODDATA_TIME_TOLERANCE_MINUTES (по умолчанию 5 минут) с обеих сторон — допуск на уход часов станции, а не на кадр из другого пролёта. Кадр вне окна отклоняется 400 с code: frame_outside_window. На SiDS-отправки (POST /api/demoddata/) проверка не распространяется: там метку объявляет клиент по своему протоколу.

Станции

Эндпоинт

Метод

Назначение

/api/stations/

GET

Список станций и их состояний

/api/station/register

POST

Регистрация новой станции

/api/stations/check

GET

Проверка состояния станции

Список станций одинаков для всех, поэтому отдаётся из кэша на CACHE_TTL (5 минут) — не на час, как у апстрима: в ответе есть last_seen и status, и час означал бы устаревшие сведения о том, кто сейчас в эфире. Анонимное чтение списка ограничено STATIONS_LIST_ANON_THROTTLE_RATE (256/час).

Спутники и передатчики

Эндпоинт

Назначение

/api/satellites/

Каталог спутников

/api/transmitters/

Передатчики; params — параметры режима для станции (у LoRa — JSON со страницы спутника TinyGS, проверяется при сохранении; пустой — передатчик не настроен и не планируется)

/api/modes/

Справочник режимов модуляции

Везде, где фильтр или параметр принимает номер NORAD (satellite__norad_cat_id, norad_cat_id, satellite, satellite_norad, noradID в SIDS), номера от 100000 можно передавать и в записи Alpha-5 (A0469 = 100469), как их публикует Space-Track.

Вспомогательные представления:

  • /api/satellitestationobs — наблюдения спутника по станциям;

  • /api/future_satellite_obs — будущие наблюдения спутника;

  • /api/topdata — сводка по наиболее активным объектам.

Орбитальные данные

/api/tles/

Чтение — публичное, но спутник назвать обязательно: нужен один из фильтров satellite_norad, satellite_sat_id, satellite_name. Без него — 400 с results: null. Раньше эндпоинт отдавал весь архив портала на любой запрос.

Постраничная выдача курсорная (-updated, -id), поэтому в теле нет count: архив растёт с головы, и смещение и уплывало бы, и замедлялось с глубиной.

POST — публикация набора внешним сервисом определения орбиты. Требует аутентификации (включая Bearer через OIDC), точечного права на конкретный спутник и проходит полную валидацию TLE. Сохраняется с источником Manual, после чего сразу пересчитывается LatestTleSet. Подробности и ограничения — в Орбитальные данные (TLE).

/api/latesttles/

Актуальный набор на спутник. Доступен анонимно, ограничен LATEST_TLES_THROTTLE_RATE (60/мин), поддерживает ?FORMAT=tle и ?FORMAT=json. Отдаёт только аппараты на орбите — отбор по возрасту TLE (LATEST_TLES_MAX_AGE_DAYS), а не по полю status.

Аналитика

Эндпоинт

Назначение

/api/analytics/network/

Суточные метрики по сети

/api/analytics/station/

Суточные метрики по станциям

/api/analytics/dynamic-demod/

Динамика поступления демодулированных данных

/api/analytics/needs-attention/

Станции, требующие внимания

Подробнее о том, что и как считается, — в Аналитика.

/api/v2/ — определения спутников

Первый эндпоинт новой версии API. Всё новое живёт здесь и ничего старого не двигает: станция, которая никогда не обновится, обращается только к /api/ и появления /api/v2/ заметить не может.

Эндпоинт

Метод

Кто

Назначение

/api/v2/bundles/<версия>/

GET

станция, анонимно

Метаданные sat-data bundle: version, sha256, size и адрес архива. Вместо версии можно указать latest

/api/v2/bundles/

POST

CI, staff-токен

Публикация нового bundle: поле version и файл archive

Что такое bundle. Архив tar.gz с определениями спутников (satyaml/) и sat.cfg, собираемый CI репозитория soniks-satyaml. Станция скачивает его при старте вместо того, чтобы клонировать определения из git: в закрытых сетях, где открыт только sonik.space, клон падал и станция работала с устаревшими определениями, ничего об этом не сообщая.

Чтение анонимно намеренно. Определения спутников — публичные данные, а токен между станцией в закрытой сети и её единственным каналом обновления был бы лишней движущейся частью.

Версия неизменяема. Хеш считает портал по принятому телу, как и у артефактов наблюдений. Повторная публикация тех же байт под той же версией — 200 без новой записи (перезапущенный пайплайн), других байт — 409 с кодом version_exists: станции, уже применившие эту версию, иначе остались бы с содержимым, которого больше нигде нет.

/api/v2/ — станция и портал

Эндпоинт

Метод

Кто

Назначение

/api/v2/stations/<id>/status/

POST

владелец станции, Authorization: Token

Станция сообщает о себе: modes — режимы демодуляции, satellites — NORAD спутников, которые она декодирует при любом режиме, client_version — версия клиента, config — итог применения конфигурации с портала, sdr — найденные приёмники, calibration — итог калибровки усиления. Ограничен STATION_STATUS_THROTTLE_RATE (1200/час на владельца)

/api/v2/stations/<id>/status/

GET

владелец

Сохранённый документ, время его приёма и текущее поколение конфигурации — для страницы настроек

/api/v2/stations/<id>/state/

GET

владелец

Документ состояния: конфигурация, координаты, канал релизов, команды станции. ETag, If-None-Match → 304

/api/v2/mqtt/auth/, /api/v2/mqtt/acl/

POST

брокер MQTT, внутри сети compose

Проверка логина и прав на топики для mosquitto-go-auth. Снаружи закрыты nginx, в схему не входят

Статус станции

Тело POST — JSON: modes — необязательный непустой список строк до 25 символов (как Mode.name; отсутствует — прежний список остаётся, пустой — 400), satellites — необязательный список положительных целых NORAD (спутники, для которых у станции есть satyaml gr-satellites: их передатчики планируются при любом режиме; пустой список допустим и значит «исключений нет», хранится отсортированным без повторов), client_version — необязательная строка до 45 символов (обновляет и колонку Station.client_version, которую раньше писала только выгрузка наблюдения), config — необязательный объект {generation, ok, error, applied_at, actual}, где actual — фактическая конфигурация станции в той же форме, что и state.config (ключи вне STATION_CONFIG_FIELDS — 400). sdr — необязательный объект {scanned_at, devices, error}: приёмники, которые станция нашла через SoapySDR.Device.enumerate(), у каждого args (строка для OBSERVATION__SOAPY_RX_DEVICE), driver, label, serial, antennas, gains: {overall: [min, max], stages: {имя: [min, max]}}, sample_rates, frequency_range, dc_offset_mode, либо error, если не открылся. calibration — необязательный объект {calibrated_at, results, recommended, lift_db, error}, где results — по одному на частоту: device, frequency, points (пары [усиление_дБ, мощность_дБ]), recommended. scheduling — необязательный объект {lead_seconds}, 10…900: за сколько секунд до начала станции нужно получить задание; пока станция на связи по MQTT (connection: online), портал планирует ей по этому порогу вместо OBSERVATION_DATE_MIN_START (Station.scheduling_lead()), а начало, попавшее внутрь порога, сдвигает вперёд и сообщает об этом планирующему. Остальные ключи отбрасываются.

Документ сливается с сохранённым по ключам верхнего уровня: станция заменяет свои ключи (modes, satellites, client_version, config, sdr, calibration, scheduling) и не трогает чужие (connection пишет мост MQTT, agent — будущий агент). Лежит в Station.reported_status, время приёма — в reported_status_at, last_seen сдвигается. Ответ — 200 со слитым документом, режимы в нём отсортированы и без повторов. Аноним — 401, чужая станция — 403, нет станции — 404, неверное тело — 400.

Зачем. Портал планирует станции только режимы из её списка: иначе клиент получал режим, которого нет в его таблице, и молча принимал его как FM. Станция, которая ничего не заявила, планируется на все режимы, как раньше, — поэтому эндпоинт ничего не меняет для парка, который никогда не обновится. config.actual предзаполняет форму настроек: станция, настроенная через .env, переезжает на портал без перепечатывания.

Состояние станции

GET /api/v2/stations/<id>/state/ отдаёт то, что владелец сохранил в форме «Настройки станции», плюс то, что станции нужно знать о себе:

{
  "generation": 3,
  "config": {"OBSERVATION__SOAPY_RX_DEVICE": "driver=rtlsdr", "FLOWGRAPH__RF_GAIN": 25.0},
  "location": {"lat": 55.75, "lng": 37.61, "alt": 150},
  "release": {"channel": "stable", "mode": "notify", "client_image": "sonik.space/sonikspace/soniks-client@sha256:…"},
  "commands": {"rescan_sdr": {"at": "2026-09-11T10:00:00+00:00"}, "calibrate_gain": {"at": "…", "frequencies": [137500000, 435000000]}}
}

generation — номер сохранения формы, 0 — владелец ничего не сохранял и станция работает по своему .env. config — плоский словарь, ключи — имена переменных окружения клиента (единый словарь имён — network/base/station_config.py). ETag считается по всему телу: координаты и канал меняются без нового поколения. commands — разовые просьбы к станции (кнопки «Пересканировать» и «Откалибровать» в форме настроек, POST stations/<id>/command/ со страницы): станция выполняет команду один раз на отметку at, между проходами, и отвечает ключами sdr / calibration статуса. Повторное нажатие — новая отметка, а не новый ключ. last_seen запрос не двигает — станция ходит за расписанием в /api/jobs/ тем же циклом.

Тот же документ портал публикует в MQTT (stations/<id>/state, retained) при сохранении формы, так что станция на связи применяет его сразу, а не на следующей минуте. Как устроен брокер — CI/CD и деплой, раздел «MQTT-брокер».

Расписание станции

Тот же список, что GET /api/jobs/?ground_station=<id>&capabilities=norad_cat_id,transmitter_parameters,max_altitude — будущие наблюдения станции со всеми opt-in полями, — портал публикует в stations/<id>/jobs (retained, QoS 1) при каждом изменении: планирование, отмена, пересчёт TLE, обновление снимка передатчика. Снимок, а не поток событий: у брокера всегда лежит актуальное расписание, станция после (пере)подключения получает его без реплея и применяет тем же сравнением, что и ответ REST; отменённое наблюдение — то, которого в списке больше нет. Публикацию ставят сигналы post_save/post_delete наблюдения с будущим start (одна задача на станцию за 2 с) и два пути мимо сигналов — refresh_future_observation_snapshots и update_future_observations_with_new_tle_sets; раз в 30 минут publish_all_station_jobs переиздаёт снимки станций на связи — страховка от сообщения, потерянного при недоступном брокере. Без MQTT_BROKER_HOST ничего не публикуется.

Подписка на данные спутника

Кроме станций, в брокер входит подписчик. Логин — subscriber-<id пользователя>, пароль — User.subscription_key. Ключ выпускается при первой подписке, а в профиле его можно перевыпустить (POST /users/update/subscription-key/). API-токен в брокер под этим логином не пускает. Имя пользователя Django на права не влияет: логин строится из id.

ACL разрешает подписчику чтение и подписку (acc 1 и 4) ровно на satellites/<sat_id>/frames, если соблюдены два условия:

  • у пользователя есть строка SatelliteSubscription на этот спутник;

  • хотя бы одна его станция сейчас в сети (Station.objects.connected()).

Всё остальное — запись, шаблоны, чужие топики — 403. Решения брокер кеширует на 300 с, поэтому доступ закрывается и открывается с такой задержкой.

Публикует портал из задачи decode_current_frame: через неё проходит каждый новый кадр станции и SiDS. Кадр публикуется после попытки декодирования, от имени служебного пользователя, с QoS 1, без retain, и только если на спутник есть хоть одна подписка. Суточный импорт SatNOGS ставит задачу с publish=False.

Тело — DemodDataViewSerializer, то есть элемент GET /api/demoddata/. Ключи payload_json переводятся через trans_decode на русский. Флаг «возможных данных» в поток не входит, пока его нет в модели.

Где что лежит в коде

Файл

Содержимое

network/api/urls.py

Маршруты и регистрация ViewSet’ов в роутере

network/api/urls_v2.py

Маршруты /api/v2/

network/base/station_config.py

Поля конфигурации станции: единый словарь для формы, сериализатора и state/

network/api/views.py

ViewSet’ы и функциональные представления

network/api/serializers.py

Сериализаторы

network/api/filters.py

Фильтры

network/api/pagination.py

Классы постраничной выдачи

network/api/renderers.py

Браузерный рендерер без форм

network/api/tests.py

Тесты API