# Update Server API Этот документ описывает текущий публичный HTTP API сервиса. Его можно передать клиентскому приложению или агенту, который встраивает автообновление. Примеры ниже используют: ```text Base URL: https://updates.example.com API key: upsk_replace_me Project: desktop-app ``` В реальной интеграции замени `Base URL`, API key и `project_slug` на свои значения. ## Общая модель Update Server хранит проекты и релизы: - `project` - приложение, продукт или канал обновлений; - `release` - загруженный файл обновления внутри проекта; - `api key` - клиентский ключ доступа к API. Администратор работает через web UI: ```text /admin /admin/projects /admin/api-keys ``` Клиентские приложения работают через JSON API: ```text /api/v1 ``` На текущий момент JSON API предназначен для чтения и скачивания обновлений. Создание проектов, загрузка релизов и создание API keys выполняются через admin web UI, а не через публичный JSON API. ## Авторизация Защищенные endpoint'ы требуют bearer token: ```http Authorization: Bearer ``` Пример: ```bash curl -i \ -H "Authorization: Bearer upsk_replace_me" \ https://updates.example.com/api/v1/projects ``` API key должен быть: - активным; - не истекшим по `expires_at`, если срок задан; - с разрешением `can_download`; - со scope, который разрешает доступ к нужному проекту. Поддерживаемые scope modes: ```text all_projects project_allow_list project_deny_list tag_allow_list tag_deny_list ``` Полный API key показывается в admin UI только один раз при создании. Сервер хранит только hash ключа. ## Формат JSON Все JSON-ответы возвращаются с: ```http Content-Type: application/json; charset=utf-8 ``` Поля времени возвращаются строкой в формате RFC3339/RFC3339Nano, например: ```json "created_at": "2026-06-11T10:15:30Z" ``` Относительные URL в ответах, например `download_url`, нужно склеивать с `Base URL` клиента. ## Health Check ### GET /healthz Публичная проверка готовности HTTP-сервера и SQLite. ```bash curl -i https://updates.example.com/healthz ``` Успешный ответ: ```http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 ``` ```json { "database": "ok", "service": "Update Server", "status": "ok", "timestamp": "2026-06-11T10:15:30Z" } ``` Если база недоступна: ```http HTTP/1.1 503 Service Unavailable ``` ```json { "database": "unavailable", "error": "database ping failed", "service": "Update Server", "status": "degraded", "timestamp": "2026-06-11T10:15:30Z" } ``` ## API Index ### GET /api/v1 Публичный JSON-index текущей версии API. Авторизация не нужна. ```bash curl -i https://updates.example.com/api/v1 ``` Ответ: ```json { "auth": { "header": "Authorization: Bearer ", "type": "bearer" }, "routes": [ { "description": "List active projects accessible to the API key.", "method": "GET", "path": "/api/v1/projects" }, { "description": "Get the latest active release metadata for an accessible project.", "method": "GET", "path": "/api/v1/projects/{projectSlug}/releases/latest" }, { "description": "Get release metadata for an accessible release.", "method": "GET", "path": "/api/v1/releases/{releaseID}" }, { "description": "Download the private artifact for an accessible release.", "method": "GET", "path": "/api/v1/releases/{releaseID}/download" } ], "service": "Update Server", "status": "client-api-ready", "version": "v1" } ``` ## List Accessible Projects ### GET /api/v1/projects Возвращает активные проекты, доступные текущему API key. Требует: ```http Authorization: Bearer ``` Пример: ```bash curl -i \ -H "Authorization: Bearer upsk_replace_me" \ https://updates.example.com/api/v1/projects ``` Успешный ответ: ```json { "projects": [ { "id": 1, "name": "Desktop App", "slug": "desktop-app", "description": "Primary desktop updater stream.", "latest_release_url": "/api/v1/projects/desktop-app/releases/latest" } ] } ``` Если доступных проектов нет: ```json { "projects": [] } ``` ## Get Latest Release ### GET /api/v1/projects/{projectSlug}/releases/latest Возвращает latest active release для активного и доступного проекта. Latest release выбирается по: ```text created_at DESC, id DESC ``` Пример: ```bash curl -i \ -H "Authorization: Bearer upsk_replace_me" \ https://updates.example.com/api/v1/projects/desktop-app/releases/latest ``` Успешный ответ: ```json { "project": { "id": 1, "name": "Desktop App", "slug": "desktop-app", "description": "Primary desktop updater stream.", "latest_release_url": "/api/v1/projects/desktop-app/releases/latest" }, "release": { "id": 2, "version": "1.1.0", "build": "build-2", "filename": "desktop-app-1.1.0.zip", "checksum_sha256": "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7c6e64b2f7110b2", "size_bytes": 12345678, "content_type": "application/zip", "release_notes": "Improved desktop rollout.", "created_at": "2026-06-11T10:15:30Z", "metadata_url": "/api/v1/releases/2", "download_url": "/api/v1/releases/2/download" } } ``` Если проект не существует, архивирован или недоступен этому key: ```http HTTP/1.1 404 Not Found ``` ```json { "error": "resource not found" } ``` Если проект доступен, но активных релизов нет: ```http HTTP/1.1 404 Not Found ``` ```json { "error": "release not found" } ``` ## Get Release Metadata ### GET /api/v1/releases/{releaseID} Возвращает metadata релиза по ID. Релиз должен быть активным, его проект должен быть активным, и текущий API key должен иметь доступ к проекту релиза. Пример: ```bash curl -i \ -H "Authorization: Bearer upsk_replace_me" \ https://updates.example.com/api/v1/releases/2 ``` Успешный ответ: ```json { "project": { "id": 1, "name": "Desktop App", "slug": "desktop-app", "description": "Primary desktop updater stream.", "latest_release_url": "/api/v1/projects/desktop-app/releases/latest" }, "release": { "id": 2, "version": "1.1.0", "build": "build-2", "filename": "desktop-app-1.1.0.zip", "checksum_sha256": "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7c6e64b2f7110b2", "size_bytes": 12345678, "content_type": "application/zip", "release_notes": "Improved desktop rollout.", "created_at": "2026-06-11T10:15:30Z", "metadata_url": "/api/v1/releases/2", "download_url": "/api/v1/releases/2/download" } } ``` Если `releaseID` не число: ```http HTTP/1.1 400 Bad Request ``` ```json { "error": "invalid release id" } ``` Если релиз не существует, архивирован или недоступен: ```http HTTP/1.1 404 Not Found ``` ```json { "error": "resource not found" } ``` ## Download Release Artifact ### GET /api/v1/releases/{releaseID}/download Скачивает приватный artifact релиза. Скачивание проходит через приложение, чтобы сервер мог проверить API key, permission и project scope. Пример: ```bash curl -L \ -H "Authorization: Bearer upsk_replace_me" \ -o desktop-app-1.1.0.zip \ https://updates.example.com/api/v1/releases/2/download ``` Успешный ответ: ```http HTTP/1.1 200 OK Content-Disposition: attachment; filename=desktop-app-1.1.0.zip Content-Type: application/zip X-Content-Type-Options: nosniff ``` Body ответа - bytes загруженного файла. Если artifact отсутствует на диске: ```http HTTP/1.1 404 Not Found ``` ```json { "error": "release not found" } ``` Если релиз недоступен: ```http HTTP/1.1 404 Not Found ``` ```json { "error": "resource not found" } ``` ## Project Object ```json { "id": 1, "name": "Desktop App", "slug": "desktop-app", "description": "Primary desktop updater stream.", "latest_release_url": "/api/v1/projects/desktop-app/releases/latest" } ``` Поля: - `id` - numeric project ID. - `name` - display name. - `slug` - stable URL identifier. - `description` - optional description. - `latest_release_url` - relative URL для latest release lookup. ## Release Object ```json { "id": 2, "version": "1.1.0", "build": "build-2", "filename": "desktop-app-1.1.0.zip", "checksum_sha256": "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7c6e64b2f7110b2", "size_bytes": 12345678, "content_type": "application/zip", "release_notes": "Improved desktop rollout.", "created_at": "2026-06-11T10:15:30Z", "metadata_url": "/api/v1/releases/2", "download_url": "/api/v1/releases/2/download" } ``` Поля: - `id` - numeric release ID. - `version` - версия, введенная администратором при загрузке. - `build` - build label или build number, если задан. - `filename` - download filename. - `checksum_sha256` - SHA-256 загруженного artifact. - `size_bytes` - размер artifact в bytes. - `content_type` - MIME type artifact. - `release_notes` - release notes. - `created_at` - время загрузки релиза. - `metadata_url` - relative URL metadata endpoint. - `download_url` - relative URL download endpoint. ## Ошибки JSON API возвращает ошибки в формате: ```json { "error": "message" } ``` Основные статусы: | Status | Когда | | --- | --- | | `400` | Некорректный `releaseID`, например не число. | | `401` | Нет bearer token, token неверный, key выключен или истек. | | `403` | API key валиден, но нет `can_download`. | | `404` | Ресурс не найден, архивирован или недоступен текущему key. | | `429` | Сработал rate limit client API. | | `500` | Внутренняя ошибка сервера. | | `503` | Health check: база недоступна; или API key service недоступен. | ### 401 Unauthorized Missing header: ```http WWW-Authenticate: Bearer realm="update-server" ``` ```json { "error": "missing bearer api key" } ``` Invalid, disabled or expired key: ```json { "error": "invalid api key" } ``` ### 403 Forbidden ```json { "error": "api key permission denied" } ``` ### 429 Too Many Requests ```http Retry-After: 3 ``` ```json { "error": "rate limit exceeded" } ``` По умолчанию client API rate limit настраивается переменными: ```text APP_CLIENT_RATE_LIMIT_PER_MINUTE=120 APP_CLIENT_RATE_LIMIT_BURST=60 ``` ## Headers Для protected API responses сервер добавляет: ```http Cache-Control: no-store, private, max-age=0 Pragma: no-cache Expires: 0 Vary: Authorization X-Robots-Tag: noindex, nofollow ``` Также на все ответы добавляются security headers, включая: ```http X-Content-Type-Options: nosniff X-Frame-Options: DENY Referrer-Policy: no-referrer ``` Если `APP_BASE_URL` использует `https://`, включается: ```http Strict-Transport-Security: max-age=31536000 ``` ## Типовой Client Flow 1. Администратор создает проект в `/admin/projects`. 2. Администратор загружает release artifact в проекте. 3. Администратор создает API key в `/admin/api-keys`. 4. Для ключа включает `can_download`. 5. Для ключа выбирает scope, который разрешает доступ к проекту. 6. Клиент запрашивает latest release: ```bash curl -sS \ -H "Authorization: Bearer upsk_replace_me" \ https://updates.example.com/api/v1/projects/desktop-app/releases/latest ``` 7. Клиент сравнивает `release.version` со своей текущей версией. 8. Если версия новая, клиент скачивает `release.download_url`. 9. Клиент проверяет: - `checksum_sha256`; - `size_bytes`; - при необходимости свою подпись artifact, если она есть в продукте. ## Python Example В репозитории есть минимальный helper: ```text example/autoupdate.py example/update_client.py ``` Приватный файл рядом со скриптом: ```json { "base_url": "https://updates.example.com", "api_key": "upsk_replace_me", "project_slug": "desktop-app" } ``` Вызов: ```python from autoupdate import ensure_updated_from_config ver = "1.0.0" ensure_updated_from_config(current_version=ver) ``` Если `autoupdate.json` отсутствует, helper создаст файл с placeholder values, не пойдет в сеть и выведет короткое сообщение о настройке конфига. ## Чего Сейчас Нет В API На текущий момент client JSON API не предоставляет: - создание/редактирование проектов; - загрузку релизов; - создание/редактирование API keys; - список всех релизов проекта; - release channels вроде `stable` / `beta`; - query parameters вроде `platform`, `arch`, `current_version`; - публичные downloads без API key. Эти действия либо выполняются через admin web UI, либо пока являются будущим расширением API.