Kontinuum Node — API Contracts
Formal API definitions для четырёх interface surfaces: admin REST, S3-gateway, billing webhooks, Tier 0 RPC. P1 prerequisite — без этого нельзя координировать app team и billing team.
Audience: node developers · app developers (kontinuum-app integration) · billing team · Tier 0 operators.
Связанные документы:
protocols.md— wire types (inter-node); этот документ — HTTP/JSONwire-types.md— CBOR encoding (inter-node); этот документ использует JSONbootstrap.md— cert lifecycle ops, которые admin REST вызываетdb-schemas.md—billing_eventstable принимает webhooks
Обзор четырёх surfaces
| Surface | Process | Direction | Auth |
|---|---|---|---|
| Admin REST API | kontinuum-node-admin | Inbound (operator) | Org operator session token + IP whitelist |
| S3-gateway HTTP API | kontinuum-node-server | Inbound (client app) | Ed25519 challenge-response per request |
| Billing webhooks | kontinuum-node-admin | Inbound (from Billing) | HMAC-SHA256 signature |
| Tier 0 RPC | kontinuum-node-admin | Outbound (to Tier 0) | Mutual TLS + client cert |
Все surfaces — HTTP/1.1 + JSON (Content-Type: application/json; charset=utf-8). Inter-node communication через CBOR (wire-types.md) — это отдельный protocol, не описан в этом документе.
1. Admin REST API
Для org operators (kontinuum.org employees) + partner operators. Slot для day-to-day операций над сетью.
Base URL
- Production:
https://admin.kontinuum.network/api/v1 - Staging:
https://admin.staging.kontinuum.network/api/v1 - Dev:
http://localhost:8080/api/v1
Authentication
Session-based через operator identity. Login flow:
POST /api/v1/auth/login
Content-Type: application/json
{
"operator_id": "alice@kontinuum.org",
"challenge_response": "<Ed25519 signature over server-issued nonce>"
}
200 OK
{
"session_token": "<opaque base64>",
"expires_at": "2026-05-19T10:30:00Z",
"permissions": ["cert.issue", "cert.revoke", "node.drain", "audit.read"]
}Subsequent requests:
GET /api/v1/nodes
Authorization: Bearer <session_token>Permission model
| Role | Permissions |
|---|---|
ops-admin | All operations |
ops-engineer | node.drain, node.metrics.read, node.list, audit.read |
support | node.list, node.metrics.read, audit.read |
partner-operator | Только над собственными partner nodes: node.metrics.read, node.drain |
auditor | Read-only: audit.read, node.list |
Endpoints
POST /nodes/provision
Запросить provisioning новой ноды (org-operated). Triggers cert request flow.
POST /api/v1/nodes/provision
Authorization: Bearer <token>
Content-Type: application/json
{
"node_pubkey": "a3f9...", // hex Ed25519 pubkey (64 hex chars)
"tier": 1,
"tenancy": "multi",
"operator": "org",
"served_identity": null, // required для single-tenant
"capacity_gb": 50,
"geo_zone": "eu-west",
"capabilities": ["dht_routing", "relay", "storage", "mailbox", "s3_gateway", "anti_entropy"]
}
201 Created
{
"node_id": "1f8b...", // hex blake3(node_pubkey)
"cert": "<base64 CBOR Tier0SignedCert>",
"issued_at": "2026-05-19T10:30:00Z",
"valid_until": "2027-05-19T10:30:00Z"
}
400 Bad Request — invalid pubkey / tier mismatch with tenancy
403 Forbidden — insufficient permissions
409 Conflict — node_pubkey already provisioned
503 Service Unavailable — Tier 0 multi-sig coordination failed (retry-after header set)GET /nodes
List all nodes (filtered по permission scope).
GET /api/v1/nodes?tier=1&status=active&page=1&per_page=50
Authorization: Bearer <token>
200 OK
{
"nodes": [
{
"node_id": "1f8b...",
"tier": 1,
"tenancy": "single",
"operator": "org",
"served_identity": "b7c2...",
"geo_zone": "eu-west",
"status": "active", // 'active' | 'drain' | 'read_only' | 'cold_archive' | 'tombstone'
"last_seen": "2026-05-19T10:29:45Z",
"capabilities": ["dht_routing", "relay", "storage", "mailbox"],
"storage_used_bytes": 12345678901,
"storage_capacity_bytes": 50000000000
},
...
],
"pagination": {
"page": 1,
"per_page": 50,
"total": 1247
}
}GET /nodes/
Detail для одной ноды.
GET /api/v1/nodes/1f8b...
Authorization: Bearer <token>
200 OK
{
"node_id": "1f8b...",
"node_pubkey": "a3f9...",
"cert": {
"issued_at": "2026-05-19T10:30:00Z",
"valid_until": "2027-05-19T10:30:00Z",
"state": "valid"
},
"tier": 1,
"tenancy": "single",
"operator": "org",
"served_identity": "b7c2...",
"geo_zone": "eu-west",
"status": "active",
"current_load_pct": 23,
"storage_used_pct": 24,
"dht_records_count": 1500,
"active_connections": 42,
"last_status_update": "2026-05-19T10:29:45Z",
"last_challenge_pass_rate": 0.98
}
404 Not Found — node не существуетPOST /nodes/{node_id}/drain
Поставить ноду в drain mode (отказывает новые writes, существующие connections finish gracefully).
POST /api/v1/nodes/1f8b.../drain
Authorization: Bearer <token>
Content-Type: application/json
{
"reason": "scheduled-maintenance",
"estimated_duration_minutes": 30
}
202 Accepted
{
"node_id": "1f8b...",
"drain_started_at": "2026-05-19T10:30:00Z",
"drain_complete_estimated_at": "2026-05-19T11:00:00Z"
}POST /nodes/{node_id}/cert/revoke
Revoke cert ноды (срабатывает CRL gossip propagation).
POST /api/v1/nodes/1f8b.../cert/revoke
Authorization: Bearer <token>
Content-Type: application/json
{
"reason": "policy-violation",
"details": "Persistent challenge failures, score below threshold"
}
202 Accepted
{
"node_id": "1f8b...",
"revocation_id": "rev_abc123",
"tier0_signatures_required": 3, // v1.0 multi-sig
"tier0_signatures_collected": 1,
"status": "pending"
}
GET /api/v1/cert-revocations/rev_abc123 // poll до collected = required
200 OK
{
"revocation_id": "rev_abc123",
"status": "complete",
"published_to_dht_at": "2026-05-19T10:35:12Z"
}POST /nodes/{node_id}/cert/renew
Renew cert (продление validity period).
POST /api/v1/nodes/1f8b.../cert/renew
Authorization: Bearer <token>
201 Created
{
"node_id": "1f8b...",
"new_cert": "<base64 CBOR Tier0SignedCert>",
"issued_at": "2026-05-19T10:30:00Z",
"valid_until": "2028-05-19T10:30:00Z"
}POST /spaces/{space_id}/rebalance
Force rebalance replication factor для Space (re-evaluate placement, push к недостающим replicas).
POST /api/v1/spaces/1f8b.../rebalance
Authorization: Bearer <token>
Content-Type: application/json
{
"target_replication_factor": 3
}
202 Accepted
{
"rebalance_id": "rb_xyz789",
"blobs_to_replicate": 1247,
"estimated_completion": "2026-05-19T11:15:00Z"
}GET /audit
Audit log query.
GET /api/v1/audit?action=cert.revoke&from=2026-05-01&to=2026-05-19&page=1
Authorization: Bearer <token>
200 OK
{
"events": [
{
"audit_id": 4521,
"admin_identity": "alice@kontinuum.org",
"action": "cert.revoke",
"target_resource": "node:1f8b...",
"payload": { "reason": "policy-violation" },
"ip_address": "10.0.0.5",
"timestamp": "2026-05-19T10:30:00Z"
},
...
]
}Error response format
Все 4xx/5xx responses в едином формате:
{
"error": {
"code": "INVALID_PUBKEY",
"message": "node_pubkey must be 64 hex chars (32 bytes Ed25519 pubkey)",
"details": {
"received_length": 32,
"expected_length": 64
},
"request_id": "req_abc123" // для cross-reference с logs
}
}Common error codes
| HTTP | Code | Reason |
|---|---|---|
| 400 | INVALID_PUBKEY | Malformed Ed25519 pubkey |
| 400 | INVALID_TENANCY_OPERATOR | Cross-field validation failed |
| 401 | UNAUTHENTICATED | Missing / invalid session token |
| 403 | INSUFFICIENT_PERMISSIONS | Token doesn't grant requested operation |
| 404 | NOT_FOUND | Resource не существует |
| 409 | ALREADY_EXISTS | Conflict (e.g., node_pubkey re-provisioning) |
| 429 | RATE_LIMITED | Превышение rate limit, retry-after header set |
| 500 | INTERNAL_ERROR | Server-side bug — request_id used для логов |
| 503 | TIER0_COORDINATION_FAILED | Multi-sig collection timeout / cancelled |
Rate limits
- Per-operator: 100 req/min общий.
- Per-endpoint: 10 req/min для destructive operations (provision, revoke, rebalance).
- 429 returns с
Retry-After: <seconds>header.
2. S3-gateway HTTP API (server-side)
Inbound от kontinuum-app для blob storage operations.
Base URL
Per-node, locally accessible (e.g., https://relay.alice.kontinuum.network).
Authentication — Ed25519 challenge-response
AWS SigV4 не используется. Custom auth:
POST /presign
Content-Type: application/json
{
"operation": "PUT", // 'PUT' | 'GET' | 'DELETE' | 'HEAD'
"space_id": "b7c2...",
"blob_hash": "f3e5...", // только для GET / DELETE / HEAD
"size_hint": 1048576, // только для PUT
"ts": 1715946600, // unix epoch, anti-replay
"nonce": "abc123...", // 16 hex chars, anti-replay
"requester_identity": "alice...",
"signature": "<Ed25519 over canonical encoding above>"
}
200 OK
{
"presigned_url": "https://rustfs.alice.kontinuum.network/cnt-alicec/b7c2/f3e5?X-Amz-Expires=60...",
"expires_at": "2026-05-19T10:31:00Z",
"method": "PUT"
}
401 Unauthorized
{
"error": {
"code": "INVALID_SIGNATURE",
"message": "Signature verification failed"
}
}
403 Forbidden
{
"error": {
"code": "NOT_SPACE_MEMBER",
"message": "Requester is not a current member of space",
"details": { "space_id": "b7c2..." }
}
}
429 Too Many RequestsПосле получения presigned_url — клиент использует его для прямого HTTP request к rustfs (PUT/GET/DELETE/HEAD).
Server-side validation
Auth shim проверяет:
- Signature над canonical CBOR(или JSON-canonical) encoding запроса.
tswithin ±60 seconds от server clock.nonceне использован в последние 5 минут (replay-cache).requester_identityв текущей membership соответствующего Space (через DHT lookup или local cache).- Per-identity quotas not exceeded (
tenant_quotasесли family-mode).
Other endpoints
GET /healthz — health check (no auth)
GET /metrics — Prometheus metrics (auth по IP whitelist или token)
GET /node-info — public info (node_id, tier, capabilities, geo_zone)Rate limits
Per-identity (auth-shim level):
- 1000 req/min для read operations (GET/HEAD)
- 100 req/min для write operations (PUT/DELETE)
- В family-mode multi-tenant — per-tenant отдельно.
Превышение → 429 с Retry-After.
3. Billing webhooks (inbound от Kontinuum Billing)
kontinuum-node-admin принимает webhook events от Kontinuum Billing system.
Endpoint
POST /webhook/billing
Content-Type: application/json
X-Kontinuum-Billing-Signature: <hmac-sha256 hex>
X-Kontinuum-Billing-Event-Id: evt_abc123 // idempotency keyAuthentication — HMAC
hmac-sha256(payload_bytes, billing_webhook_secret) == X-Kontinuum-Billing-SignatureSecret стораджится в admin.toml (billing_integration.webhook_secret_file, см. configuration.md).
Idempotency
Same X-Kontinuum-Billing-Event-Id → return cached previous response. Prevents double-processing на network retries.
Event types
subscription.created
Triggered после успешной оплаты.
{
"event_id": "evt_abc123",
"event_type": "subscription.created",
"timestamp": "2026-05-19T10:30:00Z",
"subscription_id": "sub_xyz789",
"customer_id": "cus_def456",
"identity_id": "b7c2...",
"plan": "relay-node-medium",
"details": {
"node_pubkey_hint": null, // если nullable — admin генерит pubkey сам
"geo_zone_preference": "eu-west",
"valid_until": "2026-06-19T10:30:00Z"
}
}
200 OK
{
"status": "accepted",
"action_taken": "node_provisioning_initiated",
"node_id": "1f8b..."
}
202 Accepted
{
"status": "queued",
"action_taken": "node_provisioning_will_start_shortly"
}Admin process triggers provisioning flow (см. bootstrap.md Tier 1 Standard buyer).
subscription.renewed
Cert renewal.
{
"event_id": "evt_abc456",
"event_type": "subscription.renewed",
"subscription_id": "sub_xyz789",
"new_valid_until": "2026-07-19T10:30:00Z"
}
200 OK
{ "status": "accepted", "action_taken": "cert_renewal_scheduled" }subscription.cancelled
Cancellation acknowledged. Initiates non-payment timeline (см. operations.md §12).
{
"event_id": "evt_abc789",
"event_type": "subscription.cancelled",
"subscription_id": "sub_xyz789",
"effective_at": "2026-06-19T10:30:00Z",
"reason": "customer_request"
}
200 OK
{ "status": "accepted", "action_taken": "lapse_timeline_scheduled" }subscription.lapsed
Payment failed после grace period. Triggers cert revocation.
{
"event_id": "evt_abc012",
"event_type": "subscription.lapsed",
"subscription_id": "sub_xyz789",
"lapsed_at": "2026-05-19T10:30:00Z",
"lapse_stage": "tombstone" // 'warning' | 'read_only' | 'cold_archive' | 'tombstone' | 'hard_delete'
}
200 OK
{ "status": "accepted", "action_taken": "stage_applied" }Error responses
401 Unauthorized — HMAC verification failed
400 Bad Request — malformed payload, unknown event type
500 Internal Error — processing failed (Billing should retry с exponential backoff)Billing должен retry на 5xx с exponential backoff (1s, 2s, 4s, ..., max 1h, max 24 attempts).
4. Tier 0 RPC (outbound от admin к Tier 0 anchors)
kontinuum-node-admin инициирует cert issuance / CRL flows. Outbound вызовы к Tier 0 anchor cluster.
Authentication — mutual TLS
- Admin presents client cert signed by Tier 0 root (отдельная PKI для admin clients).
- Tier 0 anchor verifies admin's cert.
- TLS 1.3 с restricted cipher suites.
Base URL
- Tier 0 endpoint указан в
admin.toml(tier0.tier0_endpoint). - В v1.0 — multi-anchor: admin вызывает каждого anchor отдельно для multi-sig collection.
Endpoints
POST /tier0/cert/sign
Request signing на cert body. v0.1: один anchor returns full cert. v1.0: anchor returns его одна signature, admin aggregates.
POST /tier0/cert/sign
Content-Type: application/cbor
mTLS client cert: admin@kontinuum.org
<CBOR-encoded CertBody>
200 OK
Content-Type: application/cbor
;; v0.1 (single-key)
SingleSigResponse = {
0: signature, ;; bytes(64)
1: tier0_pubkey, ;; bytes(32) — какой ключ подписал
2: signed_at, ;; uint
}
;; v1.0 (one of multi-sig)
PartialSigResponse = {
0: signature, ;; bytes(64) — этого anchor'a
1: tier0_pubkey, ;; bytes(32)
2: signed_at, ;; uint
3: anchor_id, ;; uint — index в multi-sig group
}Admin v1.0 flow:
- Send request к каждому из 5 anchors параллельно.
- Wait для ≥3 partial signatures.
- Aggregate в
Tier0SignedCert.signaturesarray (CBOR отwire-types.md). - Publish в global DHT.
POST /tier0/crl/publish
Same pattern для CRL updates.
POST /tier0/crl/publish
Content-Type: application/cbor
mTLS client cert
<CBOR-encoded CrlEntry>
200 OK
Content-Type: application/cbor
PartialSigResponse { ... }GET /tier0/health
Liveness check для admin process (мониторит availability anchors).
GET /tier0/health
200 OK
Content-Type: application/json
{
"anchor_id": 2,
"tier0_pubkey": "...",
"version": "1.0.3",
"last_sign_at": "2026-05-19T09:45:00Z",
"pending_sign_requests": 0
}Anchor-side authorization
Anchor проверяет:
- mTLS client cert — issued by Tier 0 root, not revoked.
- Request signature от authorized admin operator (отдельная PKI слоя — operator certs signed by Tier 0 management).
cert_request.tiernot 0 (no self-signing).- Payment confirmation (для cert issuance — included в request metadata, verified против Billing API).
App ↔ Node integration contract
Это summary что app expects от server's S3-gateway API (§2 выше) для kontinuum-app::clouds::S3 integration.
Endpoint discovery
Client получает endpoint URL ноды через:
- Standard user — после purchase relay-node, app stores
node_endpointвkontinuum-app::clouds::Cloudtable (см.docs/managers/clouds.md). - PRO user — manually configured в Settings → My Infrastructure → Add VPS.
- Family-mode tenant — endpoint discovery через rendezvous + invitation token.
Version negotiation
Client включает User-Agent header с client version:
User-Agent: kontinuum-app/1.0.5 (linux; x86_64)Server returns X-Kontinuum-Node-Version: 1.0.3 в response. Client может детектировать mismatch и предложить update.
Error handling protocol
| Server response | App action |
|---|---|
| 200 OK | Continue |
| 401 INVALID_SIGNATURE | Re-derive signing key, retry once; если повторяется — flag error |
| 403 NOT_SPACE_MEMBER | Refresh Space membership from DHT, retry once |
| 429 RATE_LIMITED | Wait Retry-After seconds, retry |
| 503 (нода в drain mode) | Wait, поинтересоваться status через /node-info |
| 5xx прочее | Exponential backoff retry; eventually fail to user |
Connection lifecycle
- App holds persistent connection к одной primary node (своя relay-node или PRO VPS).
- Secondary nodes — connected on-demand (для shared space queries, friend's mailbox lookup).
- Keep-alive 5 min idle timeout.
- Reconnect с exponential backoff при disconnects.
Implementation checklist
- [ ] Сгенерировать OpenAPI 3.0 spec из этого документа —
docs/node/openapi/admin.yaml,docs/node/openapi/s3-gateway.yaml. - [ ]
admin/src/rest_api.rs— axum router, handlers per endpoint. - [ ]
server/src/storage/auth_shim.rs— Ed25519 challenge-response logic. - [ ]
admin/src/billing_integration.rs— webhook receiver + HMAC verification + идемпотентность. - [ ]
admin/src/tier0_client.rs— outbound mTLS client к anchors. - [ ] Property tests: signature verification, HMAC, replay-cache.
- [ ] Integration tests: full admin REST flow (login → list nodes → revoke).
- [ ] Документация generated client SDK для
kontinuum-app(kontinuum-app/backend/src/clients/node_api/).
Open implementation questions
- OpenAPI vs hand-written. OpenAPI 3.0 generates types + clients автоматически, но learning curve. Hand-written проще для small surface. Решение: OpenAPI для admin REST (large surface), hand-written для S3-gateway (~3 endpoints).
- Session token vs JWT. Session tokens (opaque, server-stored) — простая revocation. JWT — stateless, scaling-friendly. Для admin REST с малой нагрузкой — session sufficient.
- gRPC vs REST для Tier 0 RPC. gRPC — typed, streaming-friendly, но добавляет dependency. REST + CBOR-body sufficient. Решение: REST для v0.1; gRPC рассмотреть post-v1.0 если перформанс mattering.
- WebSocket для admin live updates. Currently spec — polling. Live event-stream от admin (node status changes, billing events) — добавить в post-v1.0.
- Rate limiting backend. In-memory token-bucket (single admin process) или Redis-backed (multi-instance admin)? v0.1 — in-memory; multi-instance — после growth.