Control-plane HTTP API v1
The Hub API is a desired-state API. A successful HTTP mutation means that an Intent was durably accepted; it does not mean that a node has executed the command or that the Stream has converged.
Resource model
A Stream has three independent views:
desired operator target: state, generation, config_version
observed latest node report: state, generation, config_version
convergence comparison: unknown, pending, applying, in_sync, degraded, blocked
Operation records additionally expose intent_id, attempt_id, generation,
retry metadata, failure classification, and the latest observed result.
Canonical lifecycle mutation
PUT /api/v1/nodes/{node_id}/streams/{stream_id}/desired-state
Authorization: Bearer <operator-token>
Content-Type: application/json
If-Match: "generation-3"
Idempotency-Key: orders-desired-4
X-Correlation-ID: request-123
{"state":"running","config_version":"cfg-17"}
The Hub returns 202 Accepted with Location pointing to the operation and
an ETag for the new generation:
{
"operation_id": "intent-4-17",
"intent_id": "intent-4-17",
"node_id": "node-a",
"stream_id": "orders",
"generation": 4,
"desired_state": "running",
"config_version": "cfg-17",
"convergence": "pending"
}
If-Match is a compare-and-swap guard. An old generation returns 412 with
generation_conflict. Idempotency-Key deduplicates retries for the same
principal, resource, and request body. Reusing a key with another body returns
409 with idempotency_key_reused.
Read and list resources
GET /api/v1/nodes/{node_id}/streams/{stream_id} is the authoritative point-in-
time resource view. Collection endpoints use:
{"items": [], "page": 1, "page_size": 50, "total": 0}
page_size is bounded to 100. Operation listing supports node_id,
resource_id, operation, state, and correlation_id. Clients should use
intent_id and generation as stable reconciliation references instead of
assuming that a command ID represents final success.
Restart and compatibility routes
Restart is a one-shot action:
POST /api/v1/nodes/{node_id}/streams/{stream_id}/actions/restart
It converges only after the Agent reports the matching action_id and the
Stream is observed running. The legacy POST .../restart, start/stop, and
configuration apply/rollback routes remain adapters that create the same
durable Intent pipeline. A node being offline does not reject a desired-state
write.
Cancellation
DELETE /api/v1/operations/{intent_id} cancels an Intent, not an already
executed side effect. Before dispatch, pending work is suppressed. After
dispatch, the Attempt outcome remains visible and the API does not claim that
a restart or other non-idempotent action was undone.
Problem envelope
Errors use a stable code, human-readable message, echoed
correlation_id, and optional machine-readable details:
{
"code": "generation_conflict",
"message": "Expected generation 3, current generation 4",
"correlation_id": "request-123",
"details": {
"expected_generation": 3,
"current_generation": 4,
"resource": {"node_id":"node-a", "stream_id":"orders"}
}
}
The Agent contract is separate from the operator contract. Agent reports are
authenticated with node session credentials and carry boot_id and monotonic
report_seq; commands carry generation, Attempt ID, configuration version,
and expiry. Command acknowledgement is transport state only.