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
- Агент открывает WSS-соединение с заголовком
Authorization: Bearer <agent_key>.
- Сервер валидирует key (argon2-хэш в БД), достаёт agent_id.
- Первое сообщение от агента —
Register, сервер отвечает RegisterAck с config snapshot и assigned session_id.
- Все последующие сообщения содержат monotonic
seq для дедупликации.
3. Envelope
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.
4. Сообщения Agent → Server
Register
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
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
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
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
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
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
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
message RestoreUpdate {
string restore_id = 1;
JobStatus status = 2;
uint32 progress_percent = 3;
string current_step = 4;
string error_message = 5;
}
Ack
message Ack {
string correlation_id = 1;
bool accepted = 2;
string reason = 3; // если accepted=false
}
5. Сообщения Server → Agent
RegisterAck
message RegisterAck {
string session_id = 1;
AgentConfig config = 2;
uint32 heartbeat_interval_sec = 3;
string server_time_iso = 4;
}
AgentConfig (используется в RegisterAck и ConfigUpdate)
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
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
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
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. Модель ошибок
{
"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).
- Query params:
?limit=50&cursor=<opaque>.
- Response:
{
"items": [...],
"next_cursor": "...",
"has_more": true
}
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.