openapi: 3.1.0 info: title: EvoBGP Control Plane API version: 0.1.0 description: | REST API управления префиксами, модулями ingest, ревизиями конфигурации BIRD и задачами (async jobs). **Соглашения:** префикс `/v1`; идентификаторы — UUID v7 или ULID (строки); время — ISO 8601 UTC. Ошибки — `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)). Пагинация списков — `cursor` + `limit`; ответ содержит `items`, `next_cursor`, `has_more`. **Роли** (матрица доступа): `viewer`, `editor`, `operator`, `node`. Нода использует отдельные пути и ключ с ролью `node`. Заголовок `X-Tenant-Id` допускается только для супер-ролей (явный tenant); иначе tenant берётся из API-ключа. license: name: Proprietary identifier: LicenseRef-Proprietary servers: - url: https://api.example.com description: Базовый URL инсталляции (замените на свой) tags: - name: System description: Liveness, readiness и метаданные сборки. Обычно без чувствительных данных; доступ может быть шире. - name: Modules description: Экземпляры модулей префиксов (AS, CDN, домены, статические IP-диапазоны) и вложенные записи. Чтение — viewer+; изменение — editor+. - name: DoH profiles description: Профили DNS-over-HTTPS для модулей типа домены. Секрет в ответах не возвращается. - name: Communities description: Справочник BGP community в скоупе tenant. - name: Peers description: BGP-пиры и политики; привязка к спикеру или ко всем спикерам. - name: Speakers description: Регистрация и метаданные экземпляров BIRD (master/replica/canary). - name: Revisions description: История ревизий конфигурации, снимки префиксов, превью и откат (часто асинхронно). - name: Deploy description: Применение ревизий на спикерах и опциональный reload BIRD. Требуется роль operator. - name: Jobs description: Асинхронные задачи; статус и отмена (best-effort). - name: Node description: "API для evobgp-node (бандлы ревизий и enrollment). Отдельный ключ или mTLS, роль node." - name: Settings description: Глобальные настройки и feature flags; изменение — только operator. security: - bearerAuth: [] externalDocs: description: Черновик и контекст API url: ./evobgp-api-sketches.md components: securitySchemes: bearerAuth: type: http scheme: bearer description: API-ключ в формате `Bearer `. На edge может заменяться или дополняться mTLS. parameters: TenantId: name: X-Tenant-Id in: header required: false schema: $ref: "#/components/schemas/ResourceId" description: Явный tenant (только супер-роли). Без заголовка tenant определяется по ключу. IdempotencyKey: name: Idempotency-Key in: header required: false schema: type: string maxLength: 256 description: Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. Cursor: name: cursor in: query schema: type: string description: Непрозрачный курсор из предыдущего ответа. Limit: name: limit in: query schema: type: integer minimum: 1 maximum: 500 default: 50 description: Размер страницы (по умолчанию 50). ModuleId: name: module_id in: path required: true schema: $ref: "#/components/schemas/ResourceId" SpeakerId: name: speaker_id in: path required: true schema: $ref: "#/components/schemas/ResourceId" RevisionId: name: revision_id in: path required: true schema: $ref: "#/components/schemas/ResourceId" JobId: name: job_id in: path required: true schema: $ref: "#/components/schemas/ResourceId" PeerId: name: id in: path required: true schema: $ref: "#/components/schemas/ResourceId" DohProfileId: name: id in: path required: true schema: $ref: "#/components/schemas/ResourceId" CommunityId: name: id in: path required: true schema: $ref: "#/components/schemas/ResourceId" SourceId: name: source_id in: path required: true schema: $ref: "#/components/schemas/ResourceId" EntryId: name: entry_id in: path required: true schema: $ref: "#/components/schemas/ResourceId" RevisionA: name: revision_a in: path required: true schema: $ref: "#/components/schemas/ResourceId" RevisionB: name: revision_b in: path required: true schema: $ref: "#/components/schemas/ResourceId" ModuleTypeFilter: name: type in: query schema: $ref: "#/components/schemas/ModuleType" ModuleEnabledFilter: name: enabled in: query schema: type: boolean SpeakerFilter: name: speaker_id in: query schema: $ref: "#/components/schemas/ResourceId" RevisionModuleFilter: name: module_id in: query schema: $ref: "#/components/schemas/ResourceId" JobStatusFilter: name: status in: query schema: $ref: "#/components/schemas/JobStatus" JobKindFilter: name: kind in: query schema: type: string description: Фильтр по виду задачи; точный перечень расширяем. responses: Unauthorized: description: Нет или невалидный Bearer-токен. content: application/problem+json: schema: $ref: "#/components/schemas/Problem" Forbidden: description: Недостаточно прав для операции. content: application/problem+json: schema: $ref: "#/components/schemas/Problem" NotFound: description: Ресурс не найден в скоупе tenant. content: application/problem+json: schema: $ref: "#/components/schemas/Problem" Conflict: description: Конфликт состояния (например, удаление DoH-профиля, который ещё используется). content: application/problem+json: schema: $ref: "#/components/schemas/Problem" UnprocessableEntity: description: Семантическая ошибка запроса или валидации полей. content: application/problem+json: schema: $ref: "#/components/schemas/Problem" DefaultProblem: description: Ошибка (см. тело Problem). content: application/problem+json: schema: $ref: "#/components/schemas/Problem" schemas: ResourceId: type: string description: UUID v7 или ULID. pattern: "^[0-9A-Za-z_-]{20,36}$" examples: - 01JQXYZABCDEFGHIJKLMNOPQRS Problem: type: object description: Problem Details (RFC 9457). required: - title - status properties: type: type: string format: uri-reference description: URI типа проблемы. title: type: string status: type: integer detail: type: string instance: type: string format: uri-reference errors: type: array items: $ref: "#/components/schemas/FieldError" additionalProperties: true FieldError: type: object properties: field: type: string description: Имя поля (или JSON Pointer). pointer: type: string description: JSON Pointer к значению. message: type: string additionalProperties: true AsyncJobAccepted: type: object required: - job_id - status properties: job_id: $ref: "#/components/schemas/ResourceId" status: type: string enum: [queued] description: Начальный статус задачи в очереди. ModuleType: type: string enum: - AS_PREFIXES - CDN_CIDRS - DOMAINS - IP_RANGES JobStatus: type: string description: Статус задачи; перечень может расширяться. enum: - queued - running - succeeded - failed - cancelled ApplyStrategy: type: string description: Стратегия выбора спикеров для apply. enum: - all_speakers Module: type: object required: - id - type - name - enabled - priority properties: id: $ref: "#/components/schemas/ResourceId" type: $ref: "#/components/schemas/ModuleType" name: type: string enabled: type: boolean priority: type: integer doh_profile_id: type: ["string", "null"] refresh_interval_sec: type: ["integer", "null"] minimum: 0 cron_expr: type: ["string", "null"] default_community_id: type: ["string", "null"] additionalProperties: true ModuleCreate: type: object required: - type - name properties: type: $ref: "#/components/schemas/ModuleType" name: type: string enabled: type: boolean default: true priority: type: integer default: 0 doh_profile_id: type: ["string", "null"] refresh_interval_sec: type: ["integer", "null"] cron_expr: type: ["string", "null"] default_community_id: type: ["string", "null"] ModulePatch: type: object properties: name: type: string enabled: type: boolean priority: type: integer doh_profile_id: type: ["string", "null"] refresh_interval_sec: type: ["integer", "null"] cron_expr: type: ["string", "null"] default_community_id: type: ["string", "null"] CdnSource: type: object required: - id - url - source_kind properties: id: $ref: "#/components/schemas/ResourceId" url: type: string format: uri source_kind: type: string description: Формат скачанного списка / парсер. community_id: type: ["string", "null"] refresh_interval_sec: type: ["integer", "null"] CdnSourceCreate: type: object required: - url - source_kind properties: url: type: string format: uri source_kind: type: string community_id: type: ["string", "null"] CdnSourcePatch: type: object properties: url: type: string format: uri source_kind: type: string community_id: type: ["string", "null"] refresh_interval_sec: type: ["integer", "null"] AsEntry: type: object required: - id properties: id: $ref: "#/components/schemas/ResourceId" asn: type: ["integer", "null"] prefix: type: ["string", "null"] description: CIDR или префикс в зависимости от модели. community_id: type: ["string", "null"] additionalProperties: true AsEntryCreate: type: object properties: asn: type: integer prefix: type: string community_id: type: ["string", "null"] additionalProperties: true DomainEntry: type: object required: - id - fqdn properties: id: $ref: "#/components/schemas/ResourceId" fqdn: type: string community_id: type: ["string", "null"] DomainEntryCreate: type: object required: - fqdn properties: fqdn: type: string community_id: type: ["string", "null"] IpRangeEntry: type: object required: - id - prefix - community_id properties: id: $ref: "#/components/schemas/ResourceId" prefix: type: string description: IPv4/IPv6 CIDR. example: 203.0.113.0/24 community_id: $ref: "#/components/schemas/ResourceId" IpRangeEntryCreate: type: object required: - prefix - community_id properties: prefix: type: string community_id: $ref: "#/components/schemas/ResourceId" DohProfile: type: object required: - id properties: id: $ref: "#/components/schemas/ResourceId" url: type: string format: uri timeout_ms: type: integer vault_secret_ref: type: ["string", "null"] description: Ссылка на секрет в vault; сырое значение не отдаётся в GET. additionalProperties: true DohProfileCreate: type: object required: - url properties: url: type: string format: uri timeout_ms: type: integer vault_secret_ref: type: ["string", "null"] BgpCommunity: type: object required: - id properties: id: $ref: "#/components/schemas/ResourceId" name: type: string kind: type: string additionalProperties: true BgpPeer: type: object required: - id properties: id: $ref: "#/components/schemas/ResourceId" neighbor: type: string description: IP соседа. remote_asn: type: integer bgp_speaker_id: type: ["string", "null"] description: "`null` — политика для всех спикеров." additionalProperties: true BgpSpeaker: type: object required: - id properties: id: $ref: "#/components/schemas/ResourceId" role: type: string description: master, replica, canary и т.д. endpoint: type: string last_applied_revision_id: type: ["string", "null"] additionalProperties: true ConfigRevision: type: object required: - id properties: id: $ref: "#/components/schemas/ResourceId" parent_revision_id: type: ["string", "null"] content_hash: type: string created_at: type: string format: date-time additionalProperties: true PrefixSnapshotItem: type: object description: Элемент материализованного снимка префиксов (детали — по реализации). additionalProperties: true Job: type: object required: - job_id - kind - status - created_at properties: job_id: $ref: "#/components/schemas/ResourceId" kind: type: string status: $ref: "#/components/schemas/JobStatus" idempotency_key: type: ["string", "null"] created_at: type: string format: date-time started_at: type: ["string", "null"] description: ISO 8601 UTC, если задача уже стартовала. finished_at: type: ["string", "null"] description: ISO 8601 UTC по завершении. error: type: ["string", "null"] meta: type: object additionalProperties: true ApplyRequest: type: object properties: revision_id: $ref: "#/components/schemas/ResourceId" strategy: $ref: "#/components/schemas/ApplyStrategy" dry_run: type: boolean default: false VersionInfo: type: object properties: api_version: type: string git_sha: type: string build_time: type: string format: date-time additionalProperties: true HealthStatus: type: object properties: status: type: string example: ok additionalProperties: true ReadyStatus: type: object properties: status: type: string example: ready checks: type: object additionalProperties: true additionalProperties: true Settings: type: object description: KV настройки tenant/глобальные лимиты и feature flags. additionalProperties: true RevisionDiff: type: object description: | Сравнение двух ревизий. Конкретный формат (JSON Patch, табличный diff и т.д.) задаётся реализацией — контракт может уточняться. additionalProperties: true NodeEnrollRequest: type: object description: Тело регистрации ноды; протокол обмена ключами уточняется отдельно. properties: public_key: type: string speaker_id: $ref: "#/components/schemas/ResourceId" additionalProperties: true NodeEnrollResponse: type: object description: Ответ enrollment (плейсхолдер). additionalProperties: true DohProfilePatch: type: object properties: url: type: string format: uri timeout_ms: type: integer vault_secret_ref: type: ["string", "null"] BgpCommunityCreate: type: object required: [name] properties: name: type: string kind: type: string additionalProperties: true BgpCommunityPatch: type: object properties: name: type: string kind: type: string additionalProperties: true BgpPeerCreate: type: object required: [neighbor, remote_asn] properties: neighbor: type: string remote_asn: type: integer bgp_speaker_id: type: ["string", "null"] additionalProperties: true BgpPeerPatch: type: object properties: neighbor: type: string remote_asn: type: integer bgp_speaker_id: type: ["string", "null"] additionalProperties: true BgpSpeakerCreate: type: object required: [endpoint] properties: role: type: string endpoint: type: string additionalProperties: true BgpSpeakerPatch: type: object properties: role: type: string endpoint: type: string additionalProperties: true LatestRevisionPointer: type: object required: - revision_id properties: revision_id: $ref: "#/components/schemas/ResourceId" published_at: type: string format: date-time paths: /v1/health: get: tags: [System] summary: Liveness description: Проверка, что процесс API жив. Не обязана проверять БД или брокер. operationId: getHealth responses: "200": description: Процесс отвечает. content: application/json: schema: $ref: "#/components/schemas/HealthStatus" default: $ref: "#/components/responses/DefaultProblem" /v1/ready: get: tags: [System] summary: Readiness description: Готовность к трафику (БД, брокер сообщений при reference-архитектуре и т.д.). operationId: getReady responses: "200": description: Сервис готов принимать запросы. content: application/json: schema: $ref: "#/components/schemas/ReadyStatus" "503": description: Не готов (зависимости недоступны). content: application/problem+json: schema: $ref: "#/components/schemas/Problem" default: $ref: "#/components/responses/DefaultProblem" /v1/version: get: tags: [System] summary: Версия сборки description: Версия API и control-plane (`git_sha`, `build_time` и др.). operationId: getVersion responses: "200": description: Метаданные сборки. content: application/json: schema: $ref: "#/components/schemas/VersionInfo" default: $ref: "#/components/responses/DefaultProblem" /v1/modules: get: tags: [Modules] summary: Список модулей description: Модули tenant с опциональными фильтрами по типу и флагу `enabled`. operationId: listModules parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/ModuleTypeFilter" - $ref: "#/components/parameters/ModuleEnabledFilter" responses: "200": description: Страница модулей. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/Module" next_cursor: type: ["string", "null"] has_more: type: boolean "401": $ref: "#/components/responses/Unauthorized" default: $ref: "#/components/responses/DefaultProblem" post: tags: [Modules] summary: Создать модуль description: | Создаёт экземпляр модуля. Поле `type` задаёт вид (`AS_PREFIXES`, `CDN_CIDRS`, `DOMAINS`, `IP_RANGES`). `module_id` в других путях — идентификатор экземпляра, не имя типа. operationId: createModule parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ModuleCreate" responses: "201": description: Модуль создан. content: application/json: schema: $ref: "#/components/schemas/Module" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "422": $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" get: tags: [Modules] summary: Получить модуль operationId: getModule responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/Module" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" patch: tags: [Modules] summary: Обновить модуль description: Частичное обновление (расписание, DoH, приоритет, `enabled` и т.д.). operationId: patchModule parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ModulePatch" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/Module" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "422": $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Modules] summary: Удалить модуль description: Мягкое удаление или перевод в `enabled=false` — конкретное поведение задаётся реализацией. operationId: deleteModule parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено или деактивировано. "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/cdn-sources: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" get: tags: [Modules] summary: Список CDN-источников operationId: listCdnSources parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/CdnSource" next_cursor: type: ["string", "null"] has_more: type: boolean "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" post: tags: [Modules] summary: Добавить CDN-источник description: URL, `source_kind`, опционально `community_id`. operationId: createCdnSource parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CdnSourceCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/CdnSource" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "422": $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/cdn-sources/{source_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" - $ref: "#/components/parameters/SourceId" patch: tags: [Modules] summary: Обновить CDN-источник operationId: patchCdnSource parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CdnSourcePatch" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/CdnSource" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Modules] summary: Удалить CDN-источник operationId: deleteCdnSource parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено. "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/as-entries: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" get: tags: [Modules] summary: Список AS-записей operationId: listAsEntries parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/AsEntry" next_cursor: type: ["string", "null"] has_more: type: boolean "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" post: tags: [Modules] summary: Добавить AS-запись operationId: createAsEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AsEntryCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/AsEntry" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/as-entries/{entry_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" - $ref: "#/components/parameters/EntryId" patch: tags: [Modules] summary: Обновить AS-запись operationId: patchAsEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AsEntryCreate" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/AsEntry" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Modules] summary: Удалить AS-запись operationId: deleteAsEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено. "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/domain-entries: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" get: tags: [Modules] summary: Список доменных записей description: FQDN и привязка к community. operationId: listDomainEntries parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/DomainEntry" next_cursor: type: ["string", "null"] has_more: type: boolean "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" post: tags: [Modules] summary: Добавить доменную запись operationId: createDomainEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DomainEntryCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/DomainEntry" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/domain-entries/{entry_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" - $ref: "#/components/parameters/EntryId" patch: tags: [Modules] summary: Обновить доменную запись operationId: patchDomainEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DomainEntryCreate" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/DomainEntry" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Modules] summary: Удалить доменную запись operationId: deleteDomainEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено. "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/ip-range-entries: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" get: tags: [Modules] summary: Список IP-диапазонов description: "Только для модулей с типом IP_RANGES (статические CIDR и community_id)." operationId: listIpRangeEntries parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/IpRangeEntry" next_cursor: type: ["string", "null"] has_more: type: boolean "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" post: tags: [Modules] summary: Добавить IP-диапазон operationId: createIpRangeEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/IpRangeEntryCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/IpRangeEntry" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/ip-range-entries/{entry_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" - $ref: "#/components/parameters/EntryId" patch: tags: [Modules] summary: Обновить IP-диапазон operationId: patchIpRangeEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/IpRangeEntryCreate" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/IpRangeEntry" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Modules] summary: Удалить IP-диапазон operationId: deleteIpRangeEntry parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено. "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/modules/{module_id}/refresh: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/ModuleId" post: tags: [Modules] summary: Запустить ingest description: | Запуск обновления данных модуля (CDN / DoH / AS в зависимости от типа). Для `IP_RANGES` данные обычно только в БД: сервер может вернуть **204** (no-op) или **400**, если refresh не поддерживается — поведение фиксируется в реализации. Рекомендуется передавать `Idempotency-Key`. operationId: postModuleRefresh parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "202": description: Задача поставлена в очередь. headers: Location: description: URL задачи `/v1/jobs/{job_id}` schema: type: string content: application/json: schema: $ref: "#/components/schemas/AsyncJobAccepted" "204": description: Нет операции (например модуль `IP_RANGES` и no-op). "400": description: Тип модуля не поддерживает refresh. content: application/problem+json: schema: $ref: "#/components/schemas/Problem" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/doh-profiles: get: tags: [DoH profiles] summary: Список DoH-профилей operationId: listDohProfiles parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/DohProfile" next_cursor: type: ["string", "null"] has_more: type: boolean default: $ref: "#/components/responses/DefaultProblem" post: tags: [DoH profiles] summary: Создать DoH-профиль description: URL и таймауты; секрет — через vault id или отдельный вызов установки секрета. operationId: createDohProfile parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DohProfileCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/DohProfile" "422": $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/DefaultProblem" /v1/doh-profiles/{id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/DohProfileId" get: tags: [DoH profiles] summary: Получить DoH-профиль description: Секрет в ответе не возвращается. operationId: getDohProfile responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/DohProfile" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" patch: tags: [DoH profiles] summary: Обновить DoH-профиль operationId: patchDohProfile parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DohProfilePatch" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/DohProfile" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [DoH profiles] summary: Удалить DoH-профиль description: Допустимо только если профиль не используется модулями. operationId: deleteDohProfile parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено. "409": $ref: "#/components/responses/Conflict" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/communities: get: tags: [Communities] summary: Список BGP community operationId: listCommunities parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/BgpCommunity" next_cursor: type: ["string", "null"] has_more: type: boolean default: $ref: "#/components/responses/DefaultProblem" post: tags: [Communities] summary: Создать community operationId: createCommunity parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BgpCommunityCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/BgpCommunity" default: $ref: "#/components/responses/DefaultProblem" /v1/communities/{id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/CommunityId" get: tags: [Communities] summary: Получить community operationId: getCommunity responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/BgpCommunity" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" patch: tags: [Communities] summary: Обновить community operationId: patchCommunity parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BgpCommunityPatch" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/BgpCommunity" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Communities] summary: Удалить community description: Только при отсутствии ссылок из других сущностей. operationId: deleteCommunity parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено. "409": $ref: "#/components/responses/Conflict" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/peers: get: tags: [Peers] summary: Список пиров operationId: listPeers parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/SpeakerFilter" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/BgpPeer" next_cursor: type: ["string", "null"] has_more: type: boolean default: $ref: "#/components/responses/DefaultProblem" post: tags: [Peers] summary: Создать пира operationId: createPeer parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BgpPeerCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/BgpPeer" default: $ref: "#/components/responses/DefaultProblem" /v1/peers/{id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/PeerId" get: tags: [Peers] summary: Получить пира operationId: getPeer responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/BgpPeer" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" patch: tags: [Peers] summary: Обновить пира description: Политики, neighbor, ASN, привязка к `bgp_speaker_id` или `null` для всех спикеров. operationId: patchPeer parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BgpPeerPatch" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/BgpPeer" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" delete: tags: [Peers] summary: Удалить или отключить пира operationId: deletePeer parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "204": description: Удалено / отключено. "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/speakers: get: tags: [Speakers] summary: Список спикеров description: Master / replica, endpoint и связанные поля. operationId: listSpeakers parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/BgpSpeaker" next_cursor: type: ["string", "null"] has_more: type: boolean default: $ref: "#/components/responses/DefaultProblem" post: tags: [Speakers] summary: Зарегистрировать спикер description: Реплика, canary и т.д. operationId: createSpeaker parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BgpSpeakerCreate" responses: "201": description: Ресурс создан. content: application/json: schema: $ref: "#/components/schemas/BgpSpeaker" default: $ref: "#/components/responses/DefaultProblem" /v1/speakers/{id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/PeerId" get: tags: [Speakers] summary: Получить спикер description: В т.ч. `last_applied_revision_id`. operationId: getSpeaker responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/BgpSpeaker" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" patch: tags: [Speakers] summary: Обновить метаданные спикера operationId: patchSpeaker parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BgpSpeakerPatch" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/BgpSpeaker" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/revisions: get: tags: [Revisions] summary: История ревизий operationId: listRevisions parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/RevisionModuleFilter" responses: "200": description: Список ревизий с cursor-пагинацией; опциональный фильтр `module_id`. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/ConfigRevision" next_cursor: type: ["string", "null"] has_more: type: boolean default: $ref: "#/components/responses/DefaultProblem" /v1/revisions/{revision_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/RevisionId" get: tags: [Revisions] summary: Метаданные ревизии description: Хэш, родитель, время, артефакты. operationId: getRevision responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/ConfigRevision" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/revisions/{revision_id}/prefixes: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/RevisionId" get: tags: [Revisions] summary: Снимок префиксов ревизии operationId: getRevisionPrefixes parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/PrefixSnapshotItem" next_cursor: type: ["string", "null"] has_more: type: boolean "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/revisions/{revision_id}/preview: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/RevisionId" get: tags: [Revisions] summary: Превью фрагментов BIRD description: Только чтение, без apply. operationId: getRevisionPreview responses: "200": description: Текст или структурированное представление — формат задаётся реализацией. content: application/json: schema: type: object additionalProperties: true text/plain: schema: type: string "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/revisions/{revision_id}/rollback: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/RevisionId" post: tags: [Revisions] summary: Откат через новую ревизию description: | Создаёт **новую** ревизию с содержимым отката от указанной. Часто возвращает **202** с `Location` на задачу. Рекомендуется `Idempotency-Key`. operationId: postRevisionRollback parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "202": description: Откат поставлен в очередь. headers: Location: schema: type: string content: application/json: schema: $ref: "#/components/schemas/AsyncJobAccepted" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/revisions/{revision_a}/diff/{revision_b}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/RevisionA" - $ref: "#/components/parameters/RevisionB" get: tags: [Revisions] summary: Сравнить две ревизии description: Формат diff (JSON Patch, табличный и т.д.) определяется реализацией. operationId: getRevisionDiff responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/RevisionDiff" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/apply: post: tags: [Deploy] summary: Применить конфигурацию description: | Применить целевую ревизию на спикерах (политика по умолчанию или из тела). Ответ **202** с `Location` на `/v1/jobs/{job_id}`. **Рекомендуется или обязателен** `Idempotency-Key` по политике продукта. operationId: postApply parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: false content: application/json: schema: $ref: "#/components/schemas/ApplyRequest" responses: "202": description: Запрос принят, задача поставлена в очередь. headers: Location: schema: type: string content: application/json: schema: $ref: "#/components/schemas/AsyncJobAccepted" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" default: $ref: "#/components/responses/DefaultProblem" /v1/speakers/{id}/apply: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/PeerId" post: tags: [Deploy] summary: Применить на одном спикере description: Canary / точечный deploy. **202** + `Location` на job. operationId: postSpeakerApply parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: false content: application/json: schema: $ref: "#/components/schemas/ApplyRequest" responses: "202": description: Запрос принят, задача поставлена в очередь. headers: Location: schema: type: string content: application/json: schema: $ref: "#/components/schemas/AsyncJobAccepted" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/bird/reload: post: tags: [Deploy] summary: Мягкий reload BIRD description: Опциональный явный reload политики, если отделён от apply; иначе может отсутствовать или быть частью apply. operationId: postBirdReload parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" responses: "202": description: Задача reload поставлена (если реализовано как async). headers: Location: schema: type: string content: application/json: schema: $ref: "#/components/schemas/AsyncJobAccepted" "204": description: Reload выполнен синхронно (если так реализовано). "501": description: Эндпоинт не используется в данной сборке. content: application/problem+json: schema: $ref: "#/components/schemas/Problem" default: $ref: "#/components/responses/DefaultProblem" /v1/jobs: get: tags: [Jobs] summary: Список задач operationId: listJobs parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/JobStatusFilter" - $ref: "#/components/parameters/JobKindFilter" responses: "200": description: Успешно. content: application/json: schema: type: object required: [items, has_more] properties: items: type: array items: $ref: "#/components/schemas/Job" next_cursor: type: ["string", "null"] has_more: type: boolean default: $ref: "#/components/responses/DefaultProblem" /v1/jobs/{job_id}: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/JobId" get: tags: [Jobs] summary: Статус задачи description: Прогресс, ошибка, связанные сущности в `meta`. operationId: getJob responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/Job" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/jobs/{job_id}/cancel: parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/JobId" post: tags: [Jobs] summary: Запросить отмену задачи description: Best-effort; гарантии зависят от вида задачи. operationId: postJobCancel parameters: - $ref: "#/components/parameters/IdempotencyKey" responses: "202": description: Запрос отмены принят. content: application/json: schema: $ref: "#/components/schemas/Job" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/speakers/{speaker_id}/revisions/latest: parameters: - $ref: "#/components/parameters/SpeakerId" get: tags: [Node] summary: Последняя опубликованная ревизия для ноды description: Вызывается **evobgp-node** с ключом роли `node`. operationId: getSpeakerLatestRevision security: - bearerAuth: [] responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/LatestRevisionPointer" "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/speakers/{speaker_id}/bundle/{revision_id}: parameters: - $ref: "#/components/parameters/SpeakerId" - $ref: "#/components/parameters/RevisionId" get: tags: [Node] summary: Скачать подписанный бандл description: | Архив с `manifest.json` и подписью (например Ed25519). Целостность по SHA-256 в манифесте. `Content-Type` — `application/octet-stream` или multipart; детали — в реализации. operationId: getSpeakerBundle security: - bearerAuth: [] responses: "200": description: Бинарное тело бандла. headers: Content-Disposition: schema: type: string content: application/octet-stream: schema: type: string format: binary "404": $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/DefaultProblem" /v1/nodes/enroll: post: tags: [Node] summary: Регистрация ноды description: Обмен ключами и привязка к `speaker_id`; детали протокола уточняются отдельно. operationId: postNodeEnroll security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/NodeEnrollRequest" responses: "200": description: Успешная регистрация (плейсхолдер). content: application/json: schema: $ref: "#/components/schemas/NodeEnrollResponse" "422": $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/DefaultProblem" /v1/settings: get: tags: [Settings] summary: Получить настройки description: KV (лимиты CDN, feature flags и т.д.). operationId: getSettings parameters: - $ref: "#/components/parameters/TenantId" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/Settings" default: $ref: "#/components/responses/DefaultProblem" patch: tags: [Settings] summary: Частично обновить настройки description: Только роль **operator**. operationId: patchSettings parameters: - $ref: "#/components/parameters/TenantId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/Settings" responses: "200": description: Успешно. content: application/json: schema: $ref: "#/components/schemas/Settings" "403": $ref: "#/components/responses/Forbidden" default: $ref: "#/components/responses/DefaultProblem"