Skip to content

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/JSON
  • wire-types.md — CBOR encoding (inter-node); этот документ использует JSON
  • bootstrap.md — cert lifecycle ops, которые admin REST вызывает
  • db-schemas.mdbilling_events table принимает webhooks

Обзор четырёх surfaces

SurfaceProcessDirectionAuth
Admin REST APIkontinuum-node-adminInbound (operator)Org operator session token + IP whitelist
S3-gateway HTTP APIkontinuum-node-serverInbound (client app)Ed25519 challenge-response per request
Billing webhookskontinuum-node-adminInbound (from Billing)HMAC-SHA256 signature
Tier 0 RPCkontinuum-node-adminOutbound (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

RolePermissions
ops-adminAll operations
ops-engineernode.drain, node.metrics.read, node.list, audit.read
supportnode.list, node.metrics.read, audit.read
partner-operatorТолько над собственными partner nodes: node.metrics.read, node.drain
auditorRead-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 в едином формате:

json
{
    "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

HTTPCodeReason
400INVALID_PUBKEYMalformed Ed25519 pubkey
400INVALID_TENANCY_OPERATORCross-field validation failed
401UNAUTHENTICATEDMissing / invalid session token
403INSUFFICIENT_PERMISSIONSToken doesn't grant requested operation
404NOT_FOUNDResource не существует
409ALREADY_EXISTSConflict (e.g., node_pubkey re-provisioning)
429RATE_LIMITEDПревышение rate limit, retry-after header set
500INTERNAL_ERRORServer-side bug — request_id used для логов
503TIER0_COORDINATION_FAILEDMulti-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 проверяет:

  1. Signature над canonical CBOR(или JSON-canonical) encoding запроса.
  2. ts within ±60 seconds от server clock.
  3. nonce не использован в последние 5 минут (replay-cache).
  4. requester_identity в текущей membership соответствующего Space (через DHT lookup или local cache).
  5. 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 key

Authentication — HMAC

hmac-sha256(payload_bytes, billing_webhook_secret) == X-Kontinuum-Billing-Signature

Secret стораджится в 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 после успешной оплаты.

json
{
    "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.

json
{
    "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).

json
{
    "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.

json
{
    "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:

  1. Send request к каждому из 5 anchors параллельно.
  2. Wait для ≥3 partial signatures.
  3. Aggregate в Tier0SignedCert.signatures array (CBOR от wire-types.md).
  4. 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 проверяет:

  1. mTLS client cert — issued by Tier 0 root, not revoked.
  2. Request signature от authorized admin operator (отдельная PKI слоя — operator certs signed by Tier 0 management).
  3. cert_request.tier not 0 (no self-signing).
  4. 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 ноды через:

  1. Standard user — после purchase relay-node, app stores node_endpoint в kontinuum-app::clouds::Cloud table (см. docs/managers/clouds.md).
  2. PRO user — manually configured в Settings → My Infrastructure → Add VPS.
  3. 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 responseApp action
200 OKContinue
401 INVALID_SIGNATURERe-derive signing key, retry once; если повторяется — flag error
403 NOT_SPACE_MEMBERRefresh Space membership from DHT, retry once
429 RATE_LIMITEDWait 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

  1. 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).
  2. Session token vs JWT. Session tokens (opaque, server-stored) — простая revocation. JWT — stateless, scaling-friendly. Для admin REST с малой нагрузкой — session sufficient.
  3. gRPC vs REST для Tier 0 RPC. gRPC — typed, streaming-friendly, но добавляет dependency. REST + CBOR-body sufficient. Решение: REST для v0.1; gRPC рассмотреть post-v1.0 если перформанс mattering.
  4. WebSocket для admin live updates. Currently spec — polling. Live event-stream от admin (node status changes, billing events) — добавить в post-v1.0.
  5. Rate limiting backend. In-memory token-bucket (single admin process) или Redis-backed (multi-instance admin)? v0.1 — in-memory; multi-instance — после growth.