mirror of
https://github.com/TronoSfera/backupy-agent.git
synced 2026-05-18 18:13:30 +03:00
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/ — Go agent (WSS client, persistent queue, Docker
discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/ — protobuf wire format (generated .pb.go committed
so the repo builds without protoc)
- docs/ — agent spec + wire-protocol contract
Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
635 lines
21 KiB
Markdown
635 lines
21 KiB
Markdown
# 07. API-контракты
|
||
|
||
## 1. Транспорт Agent ↔ Server
|
||
|
||
- **Протокол**: WebSocket Secure (WSS) на :443, бинарные фреймы с Protobuf-payload'ами.
|
||
- **Альтернатива** для Phase 3+: gRPC bidirectional stream.
|
||
- Длинноживущее соединение, инициируемое агентом.
|
||
- Heartbeat: PING каждые 30 секунд, RTO 90 секунд.
|
||
- Reconnect: exponential backoff 1s → 2s → 4s → … → 60s cap, jitter ±20%.
|
||
|
||
## 2. Аутентификация WSS
|
||
|
||
1. Агент открывает WSS-соединение с заголовком `Authorization: Bearer <agent_key>`.
|
||
2. Сервер валидирует key (argon2-хэш в БД), достаёт agent_id.
|
||
3. Первое сообщение от агента — `Register`, сервер отвечает `RegisterAck` с config snapshot и assigned `session_id`.
|
||
4. Все последующие сообщения содержат monotonic `seq` для дедупликации.
|
||
|
||
## 3. Envelope
|
||
|
||
```protobuf
|
||
syntax = "proto3";
|
||
package backup.v1;
|
||
|
||
message Envelope {
|
||
uint64 seq = 1; // monotonic, per-direction
|
||
uint64 ts_ms = 2; // unix ms, отправителя
|
||
string correlation_id = 3; // для request/response пар
|
||
oneof payload {
|
||
// ----- agent → server -----
|
||
Register register = 10;
|
||
Heartbeat heartbeat = 11;
|
||
DiscoveryReport discovery = 12;
|
||
JobUpdate job_update = 13;
|
||
BackupCompleted backup_completed = 14;
|
||
HealthCheckResult health_result = 15;
|
||
LogEvent log = 16;
|
||
RestoreUpdate restore_update = 17;
|
||
Ack ack = 18;
|
||
|
||
// ----- server → agent -----
|
||
RegisterAck register_ack = 50;
|
||
ConfigUpdate config_update = 51;
|
||
RunBackup run_backup = 52;
|
||
CancelJob cancel_job = 53;
|
||
RunHealthCheck run_health_check = 54;
|
||
SelfUpdate self_update = 56;
|
||
Ping ping = 57;
|
||
}
|
||
}
|
||
```
|
||
|
||
> **Изменение относительно v1:** `RunRestore` удалён из MVP. В Phase 1 нет restore-в-БД, только download через REST endpoint (`GET /runs/:id/download-url` + локальная расшифровка CLI-утилитой). См. [04-server-spec.md → Download](./04-server-spec.md).
|
||
|
||
## 4. Сообщения Agent → Server
|
||
|
||
### Register
|
||
|
||
```protobuf
|
||
message Register {
|
||
string agent_version = 1;
|
||
string hostname = 2;
|
||
string os = 3; // "linux"
|
||
string arch = 4; // "amd64"
|
||
string docker_version = 5;
|
||
repeated string capabilities = 6; // ["pg_dump", "mysqldump", "docker_discovery"]
|
||
uint64 last_known_config_version = 7;
|
||
}
|
||
```
|
||
|
||
### Heartbeat
|
||
|
||
```protobuf
|
||
message Heartbeat {
|
||
uint64 config_version = 1;
|
||
AgentMetrics metrics = 2;
|
||
repeated string active_job_ids = 3;
|
||
}
|
||
message AgentMetrics {
|
||
float cpu_percent = 1;
|
||
uint64 mem_used_bytes = 2;
|
||
uint64 mem_total_bytes = 3;
|
||
uint64 disk_used_bytes = 4;
|
||
uint64 disk_total_bytes = 5;
|
||
uint32 queue_depth = 6;
|
||
}
|
||
```
|
||
|
||
Частота: каждые 30 секунд.
|
||
|
||
### DiscoveryReport
|
||
|
||
```protobuf
|
||
message DiscoveryReport {
|
||
repeated DiscoveredContainer containers = 1;
|
||
}
|
||
message DiscoveredContainer {
|
||
string container_id = 1;
|
||
string name = 2;
|
||
string image = 3; // "postgres:16"
|
||
string detected_db_type = 4; // "postgresql"
|
||
repeated string networks = 5;
|
||
map<string,string> env_hints = 6; // отфильтрованные env без секретов
|
||
repeated PortBinding ports = 7;
|
||
}
|
||
message PortBinding {
|
||
uint32 container_port = 1;
|
||
uint32 host_port = 2;
|
||
string protocol = 3;
|
||
}
|
||
```
|
||
|
||
Отправляется при старте и при изменениях docker events.
|
||
|
||
### JobUpdate
|
||
|
||
```protobuf
|
||
message JobUpdate {
|
||
string job_id = 1;
|
||
JobStatus status = 2;
|
||
uint32 progress_percent = 3;
|
||
string current_step = 4; // "dumping", "compressing", "uploading"
|
||
string error_message = 5; // если status=FAILED
|
||
}
|
||
enum JobStatus {
|
||
JOB_STATUS_UNSPECIFIED = 0;
|
||
QUEUED = 1;
|
||
RUNNING = 2;
|
||
SUCCESS = 3;
|
||
FAILED = 4;
|
||
CANCELLED = 5;
|
||
}
|
||
```
|
||
|
||
### BackupCompleted
|
||
|
||
```protobuf
|
||
message BackupCompleted {
|
||
string job_id = 1;
|
||
string run_id = 2;
|
||
string s3_key = 3;
|
||
uint64 size_bytes = 4;
|
||
string sha256 = 5;
|
||
uint64 duration_ms = 6;
|
||
string dek_kms_id = 7;
|
||
bytes encrypted_dek = 8;
|
||
string compression = 9; // "zstd"
|
||
string db_engine_version = 10; // "PostgreSQL 16.2"
|
||
}
|
||
```
|
||
|
||
### HealthCheckResult
|
||
|
||
```protobuf
|
||
message HealthCheckResult {
|
||
string check_id = 1;
|
||
uint64 ts_ms = 2;
|
||
bool ok = 3;
|
||
uint32 latency_ms = 4;
|
||
string error = 5;
|
||
uint32 status_code = 6; // для HTTP
|
||
}
|
||
```
|
||
|
||
### LogEvent
|
||
|
||
```protobuf
|
||
message LogEvent {
|
||
uint64 ts_ms = 1;
|
||
LogLevel level = 2;
|
||
string job_id = 3; // опционально
|
||
string message = 4;
|
||
map<string,string> fields = 5;
|
||
}
|
||
enum LogLevel { TRACE=0; DEBUG=1; INFO=2; WARN=3; ERROR=4; }
|
||
```
|
||
|
||
### RestoreUpdate
|
||
|
||
```protobuf
|
||
message RestoreUpdate {
|
||
string restore_id = 1;
|
||
JobStatus status = 2;
|
||
uint32 progress_percent = 3;
|
||
string current_step = 4;
|
||
string error_message = 5;
|
||
}
|
||
```
|
||
|
||
### Ack
|
||
|
||
```protobuf
|
||
message Ack {
|
||
string correlation_id = 1;
|
||
bool accepted = 2;
|
||
string reason = 3; // если accepted=false
|
||
}
|
||
```
|
||
|
||
## 5. Сообщения Server → Agent
|
||
|
||
### RegisterAck
|
||
|
||
```protobuf
|
||
message RegisterAck {
|
||
string session_id = 1;
|
||
AgentConfig config = 2;
|
||
uint32 heartbeat_interval_sec = 3;
|
||
string server_time_iso = 4;
|
||
}
|
||
```
|
||
|
||
### AgentConfig (используется в RegisterAck и ConfigUpdate)
|
||
|
||
```protobuf
|
||
message AgentConfig {
|
||
uint64 version = 1;
|
||
repeated Target targets = 2;
|
||
repeated BackupJobSpec jobs = 3;
|
||
repeated HealthCheckSpec health_checks = 4;
|
||
MaintenanceWindow maintenance = 5;
|
||
AdvancedSettings advanced = 6;
|
||
}
|
||
|
||
message Target {
|
||
string id = 1;
|
||
DbType type = 2;
|
||
string display_name = 3;
|
||
ConnectionConfig connection = 4;
|
||
}
|
||
|
||
enum DbType {
|
||
DB_UNSPECIFIED = 0;
|
||
POSTGRESQL = 1;
|
||
MYSQL = 2;
|
||
MARIADB = 3;
|
||
MONGODB = 4;
|
||
REDIS = 5;
|
||
SQLITE = 6;
|
||
}
|
||
|
||
message ConnectionConfig {
|
||
string host = 1;
|
||
uint32 port = 2;
|
||
string database = 3;
|
||
string username = 4;
|
||
string password_secret_ref = 5; // ссылка на зашифрованный секрет
|
||
string container_id = 6; // для docker exec strategy
|
||
ConnectionStrategy strategy = 7;
|
||
}
|
||
enum ConnectionStrategy { TCP = 0; DOCKER_EXEC = 1; UNIX_SOCKET = 2; }
|
||
|
||
message BackupJobSpec {
|
||
string id = 1;
|
||
string target_id = 2;
|
||
string cron = 3; // UTC
|
||
uint32 retention_days = 4;
|
||
string compression = 5; // "zstd" | "gzip" | "none"
|
||
bool encryption_enabled = 6;
|
||
repeated string pre_hooks = 7;
|
||
repeated string post_hooks = 8;
|
||
RetryPolicy retry = 9;
|
||
uint32 timeout_sec = 10;
|
||
}
|
||
|
||
message RetryPolicy {
|
||
uint32 max_attempts = 1;
|
||
repeated uint32 backoff_seconds = 2; // [60, 300, 1800]
|
||
}
|
||
|
||
message HealthCheckSpec {
|
||
string id = 1;
|
||
string name = 2;
|
||
CheckType type = 3;
|
||
string target = 4; // URL или host:port
|
||
uint32 interval_sec = 5;
|
||
uint32 timeout_sec = 6;
|
||
uint32 expected_status = 7; // для HTTP
|
||
}
|
||
enum CheckType { HTTP = 0; HTTPS = 1; TCP = 2; }
|
||
|
||
message MaintenanceWindow {
|
||
string start_utc = 1; // "02:00"
|
||
string end_utc = 2; // "05:00"
|
||
}
|
||
|
||
message AdvancedSettings {
|
||
uint32 max_parallel_jobs = 1;
|
||
string log_level = 2;
|
||
bool auto_update_enabled = 3;
|
||
}
|
||
```
|
||
|
||
### RunBackup
|
||
|
||
```protobuf
|
||
message RunBackup {
|
||
string job_id = 1;
|
||
string run_id = 2;
|
||
bool manual_trigger = 3;
|
||
S3UploadCreds upload_creds = 4;
|
||
bytes encrypted_dek = 5; // DEK для шифрования, обёрнутый KMS
|
||
}
|
||
message S3UploadCreds {
|
||
string presigned_put_url = 1;
|
||
uint64 expires_at_ms = 2;
|
||
string final_s3_key = 3;
|
||
}
|
||
```
|
||
|
||
### CancelJob, RunHealthCheck
|
||
|
||
```protobuf
|
||
message CancelJob { string job_id = 1; }
|
||
message RunHealthCheck { string check_id = 1; }
|
||
```
|
||
|
||
### ~~RunRestore~~ — удалено в MVP
|
||
|
||
В Phase 1 функция «получить данные обратно» работает через REST API (см. ниже эндпоинты `/runs/:id/download-url` и `/runs/:id/decryption-token`) + локальную CLI-утилиту `backupy-decrypt`. Агент в этом потоке не участвует.
|
||
|
||
Restore-в-БД через агент (RESTORE_MODE: NEW_DATABASE / ANOTHER_AGENT) — Phase 3. К тому моменту в proto будет добавлен новый message `RunRestore` (proto v2).
|
||
|
||
### SelfUpdate
|
||
|
||
```protobuf
|
||
message SelfUpdate {
|
||
string target_version = 1;
|
||
string binary_url = 2;
|
||
string sha256 = 3;
|
||
bytes cosign_signature = 4;
|
||
bool force = 5;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. REST API
|
||
|
||
База: `https://api.backupy.ru/v1`.
|
||
Auth: `Authorization: Bearer <token>` (session-token из UI или API-key).
|
||
|
||
### Auth & Account
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| POST | `/auth/signup` | email+password регистрация |
|
||
| POST | `/auth/login` | email+password |
|
||
| POST | `/auth/magic-link/request` | запрос magic link |
|
||
| GET | `/auth/magic-link/verify?token=` | подтверждение |
|
||
| GET | `/auth/oauth/github` | OAuth redirect |
|
||
| GET | `/auth/oauth/github/callback` | OAuth callback |
|
||
| GET | `/auth/oauth/google` | OAuth redirect |
|
||
| GET | `/auth/oauth/google/callback` | OAuth callback |
|
||
| POST | `/auth/logout` | |
|
||
| POST | `/auth/password-reset/request` | |
|
||
| POST | `/auth/password-reset/confirm` | |
|
||
| GET | `/me` | текущий пользователь |
|
||
| PATCH | `/me` | обновить профиль |
|
||
| DELETE | `/me` | account deletion (start grace period) |
|
||
| GET | `/me/export` | data export JSON |
|
||
| POST | `/me/2fa/enable` | включить TOTP |
|
||
| POST | `/me/2fa/disable` | |
|
||
|
||
### Agents
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/agents` | список агентов |
|
||
| POST | `/agents` | создать (возвращает раз secret key) |
|
||
| GET | `/agents/:id` | детали |
|
||
| PATCH | `/agents/:id` | изменить имя/настройки |
|
||
| DELETE | `/agents/:id` | |
|
||
| POST | `/agents/:id/rotate-key` | новый ключ |
|
||
| GET | `/agents/:id/config` | текущий config (видимый пользователю) |
|
||
| PATCH | `/agents/:id/config` | изменить (создаёт новую version) |
|
||
| GET | `/agents/:id/discovered` | то, что обнаружил Docker socket |
|
||
| GET | `/agents/:id/metrics` | последние метрики |
|
||
|
||
### Targets & Jobs
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/agents/:id/targets` | список target'ов |
|
||
| POST | `/agents/:id/targets` | создать target |
|
||
| PATCH | `/targets/:id` | |
|
||
| DELETE | `/targets/:id` | |
|
||
| POST | `/targets/:id/test-connection` | проверка коннекта |
|
||
| GET | `/agents/:id/jobs` | jobs |
|
||
| POST | `/agents/:id/jobs` | новый job |
|
||
| GET | `/jobs/:id` | детали |
|
||
| PATCH | `/jobs/:id` | редактирование |
|
||
| DELETE | `/jobs/:id` | |
|
||
| POST | `/jobs/:id/run` | manual run, `Idempotency-Key` поддерживается |
|
||
|
||
### Backup Runs
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/runs` | каталог с фильтрами `?agent_id=&target_id=&from=&to=&status=` |
|
||
| GET | `/runs/:id` | детали |
|
||
| GET | `/runs/:id/download-url` | presigned GET URL для скачивания зашифрованного объекта (TTL 15 min) |
|
||
| POST | `/runs/:id/decryption-token` | временный JWT с plaintext DEK для CLI-утилиты (TTL 15 min) |
|
||
| DELETE | `/runs/:id` | удалить бэкап вручную |
|
||
|
||
> Restore-в-БД (`POST /runs/:id/restore` + `/restores/:id`) — **Phase 3**, не реализуется в MVP.
|
||
|
||
### Health Checks
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/health-checks` | |
|
||
| POST | `/health-checks` | |
|
||
| PATCH | `/health-checks/:id` | |
|
||
| DELETE | `/health-checks/:id` | |
|
||
| GET | `/health-checks/:id/results` | history с временным фильтром |
|
||
| GET | `/health-checks/:id/uptime?period=24h\|7d\|30d\|90d` | aggregated |
|
||
|
||
### Storage Profiles
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/storage-profiles` | managed + BYO |
|
||
| POST | `/storage-profiles` | добавить BYO-S3 |
|
||
| PATCH | `/storage-profiles/:id` | |
|
||
| DELETE | `/storage-profiles/:id` | |
|
||
| POST | `/storage-profiles/:id/test` | тестовое PUT/GET |
|
||
| GET | `/storage-profiles/usage` | сколько занято |
|
||
|
||
### Status Page
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/status-page` | конфиг страницы |
|
||
| PATCH | `/status-page` | |
|
||
| POST | `/status-page/verify-dns` | проверка CNAME |
|
||
| POST | `/status-page/incidents` | создать incident |
|
||
| PATCH | `/incidents/:id` | обновить status (Investigating → Resolved) |
|
||
| GET | `/incidents` | список |
|
||
| GET | `/status-page/subscribers` | |
|
||
|
||
### Public (без auth)
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/public/status/:subdomain` | публичная страница |
|
||
| GET | `/public/status/:subdomain/rss` | RSS feed |
|
||
| POST | `/public/status/:subdomain/subscribe` | подписка |
|
||
| GET | `/public/status/:subdomain/unsubscribe?token=` | |
|
||
|
||
### Notifications
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/notification-channels` | |
|
||
| POST | `/notification-channels` | email/telegram/webhook |
|
||
| PATCH | `/notification-channels/:id` | |
|
||
| DELETE | `/notification-channels/:id` | |
|
||
| POST | `/notification-channels/:id/test` | отправить тестовое |
|
||
| GET | `/notification-preferences` | |
|
||
| PATCH | `/notification-preferences` | |
|
||
|
||
### Audit Log & API Tokens
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/audit-log` | с фильтрами |
|
||
| GET | `/audit-log/export` | CSV |
|
||
| GET | `/api-tokens` | список (без plaintext) |
|
||
| POST | `/api-tokens` | создать |
|
||
| DELETE | `/api-tokens/:id` | |
|
||
|
||
---
|
||
|
||
## 6a. Admin REST API (`/admin/v1/`)
|
||
|
||
Базовый путь: `https://api.backupy.ru/admin/v1`.
|
||
Auth: тот же Bearer token, но проверяется `role=admin`. Не-admin запрос → `403 PERMISSION_DENIED`.
|
||
|
||
### Companies (admin only)
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/companies` | список с фильтрами `?plan=&status=&country=&search=` | 1 |
|
||
| POST | `/companies` | создать вручную (для enterprise-онбординга) | 1 |
|
||
| GET | `/companies/:id` | детальная карточка (users, agents count, payment history placeholder, internal notes) | 1 |
|
||
| PATCH | `/companies/:id` | обновить (имя, контактный email, страна, plan, status) | 1 |
|
||
| POST | `/companies/:id/suspend` | приостановить — все агенты компании перестают получать команды | 1 |
|
||
| POST | `/companies/:id/reactivate` | вернуть в active | 1 |
|
||
| POST | `/companies/:id/impersonate` | создать временную user-session под owner-юзером компании | 1 |
|
||
| GET | `/companies/:id/notes` | internal notes (admin-only) | 1 |
|
||
| POST | `/companies/:id/notes` | добавить заметку | 1 |
|
||
| POST | `/companies/:id/change-plan` | сменить план (без биллинга — биллинг Phase 3) | 2 |
|
||
|
||
### Users (admin only)
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/users` | все юзеры с фильтрами `?company_id=&role=&status=&search=` | 1 |
|
||
| POST | `/users` | создать юзера в конкретной компании (или с auto-создаваемой) | 1 |
|
||
| GET | `/users/:id` | детали юзера | 1 |
|
||
| POST | `/users/:id/suspend` | заблокировать sign-in и API | 1 |
|
||
| POST | `/users/:id/reactivate` | | 1 |
|
||
| POST | `/users/:id/impersonate` | временная user-session | 1 |
|
||
| POST | `/users/:id/reset-password` | отправить reset-email | 1 |
|
||
| DELETE | `/users/:id` | начать GDPR-удаление (полный flow — Phase 3) | 1 (start) / 3 (full) |
|
||
|
||
### Plans (admin only)
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/plans` | все тарифы (active + inactive) | 1 |
|
||
| POST | `/plans` | создать новый | 2 |
|
||
| PATCH | `/plans/:id` | обновить (цена, лимиты, фичи, active) | 2 |
|
||
| DELETE | `/plans/:id` | удалить (если customers=0; иначе ошибка) | 2 |
|
||
| GET | `/plans/distribution` | сколько customers на каждом плане + MRR | 2 |
|
||
|
||
### Infrastructure (admin only, read-only)
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/infra/s3` | список наших S3 bucket'ов: размер, объекты, стоимость | 1 |
|
||
| GET | `/infra/kms` | KMS keys active, operations today, cost | 1 |
|
||
| GET | `/infra/cluster` | K8s cluster CPU/memory | 1 |
|
||
| GET | `/infra/db` | PostgreSQL size, replication lag, connections | 1 |
|
||
| GET | `/infra/redis` | Redis memory usage | 1 |
|
||
| GET | `/infra/cost-breakdown` | детализация месячных расходов + gross margin расчёт | 2 |
|
||
|
||
### Admin audit log
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/admin-audit` | список admin-actions с фильтрами | 1 |
|
||
| GET | `/admin-audit/export` | CSV | 2 |
|
||
|
||
### System settings (admin only)
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/system-settings` | branding, signup config, feature flags | 2 |
|
||
| PATCH | `/system-settings` | обновить | 2 |
|
||
| GET | `/system-settings/feature-flags` | список фич с per-company overrides | 2 |
|
||
| POST | `/system-settings/feature-flags/:flag/enable` | включить глобально | 2 |
|
||
| POST | `/system-settings/feature-flags/:flag/disable` | выключить глобально | 2 |
|
||
|
||
### Admin team
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/admin-team` | список админов с ролями | 2 |
|
||
| POST | `/admin-team/invite` | пригласить нового админа | 2 |
|
||
| PATCH | `/admin-team/:id` | сменить роль (super_admin / support / billing) | 2 |
|
||
| DELETE | `/admin-team/:id` | удалить админа | 2 |
|
||
|
||
### Payments & Revenue (Phase 3, заглушки в Phase 2)
|
||
|
||
| Метод | Путь | Назначение | Phase |
|
||
|---|---|---|---|
|
||
| GET | `/payments` | все транзакции с фильтрами | 3 |
|
||
| POST | `/payments/:id/refund` | refund-action | 3 |
|
||
| POST | `/payments/:id/retry` | повторить failed charge | 3 |
|
||
| GET | `/analytics/revenue` | MRR, ARR, churn, conversion, cohorts | 3 |
|
||
|
||
---
|
||
|
||
## 7. Модель ошибок
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "RESOURCE_NOT_FOUND",
|
||
"message": "Agent not found",
|
||
"details": { "agent_id": "agt_..." },
|
||
"trace_id": "01HM..."
|
||
}
|
||
}
|
||
```
|
||
|
||
Коды:
|
||
- `AUTH_FAILED`
|
||
- `PERMISSION_DENIED`
|
||
- `RESOURCE_NOT_FOUND`
|
||
- `VALIDATION_ERROR`
|
||
- `RATE_LIMITED`
|
||
- `CONFLICT`
|
||
- `PRECONDITION_FAILED`
|
||
- `INTERNAL`
|
||
- `SERVICE_UNAVAILABLE`
|
||
|
||
## 8. Идемпотентность
|
||
|
||
- `POST /jobs/:id/run` и `POST /runs/:id/restore` принимают `Idempotency-Key` header.
|
||
- Сервер кеширует ответ по ключу на 24 часа.
|
||
- `BackupCompleted` от агента дедуплицируется по `run_id` на сервере (upsert).
|
||
|
||
## 9. Pagination
|
||
|
||
- Query params: `?limit=50&cursor=<opaque>`.
|
||
- Response:
|
||
```json
|
||
{
|
||
"items": [...],
|
||
"next_cursor": "...",
|
||
"has_more": true
|
||
}
|
||
```
|
||
- Limit max: 100.
|
||
|
||
## 10. Real-time (для UI)
|
||
|
||
- SSE endpoint: `GET /events?topics=jobs,agents,restores` (auth required).
|
||
- События:
|
||
- `agent.connected`, `agent.disconnected`, `agent.config.applied`
|
||
- `job.started`, `job.progress`, `job.completed`, `job.failed`
|
||
- `restore.started`, `restore.progress`, `restore.completed`, `restore.failed`
|
||
- `incident.opened`, `incident.resolved`
|
||
|
||
## 11. Версионирование
|
||
|
||
- API: `/v1/...`. Breaking changes → `/v2/`.
|
||
- Proto: семантическое версионирование пакета (`backup.v1`, `backup.v2`).
|
||
- Агент шлёт `agent_version`, сервер договаривается о минимальной поддерживаемой версии.
|
||
- Минимум 6 месяцев backward compatibility между proto-версиями.
|
||
|
||
## 12. Rate limits (defaults)
|
||
|
||
| Endpoint | Limit |
|
||
|---|---|
|
||
| `/auth/login` | 5/min/IP |
|
||
| `/auth/signup` | 3/hour/IP |
|
||
| `/auth/magic-link/request` | 3/hour/email |
|
||
| `/auth/password-reset/request` | 3/hour/email |
|
||
| API (auth required) | 60/min/token |
|
||
| `/public/status/*` | 60/min/IP |
|
||
|
||
Превышение → 429 с `Retry-After`.
|