Skip to content

13. Cluster Management API ​

The cluster API manages nodes, syncs data and supports node discovery for the decentralized multi-active cluster. Two auth methods are used:

  • Cluster Secret for inter-node traffic; header format X-Cluster-Secret: <node secret> (the target node's secret, looked up from the local DB).
  • Root admin for the admin UI; uses Authorization: Bearer <Root Access Token> or cookie session.

13.1 Node Discovery and Heartbeat (internal) ​

Endpoint: POST /api/cluster/ping

Auth: Cluster secret

Description: Two-way ping between nodes for cluster discovery and liveness checks. The responder returns its full node list so the requester can do transitive discovery.

Request body:

json
{
  "node_id": 1,
  "node_name": "node-cn",
  "address": "https://cn.example.com",
  "secret_key": "node-1-secret"
}

Request fields:

FieldTypeRequiredDescription
node_idintyesID of the node sending the ping (matches CLUSTER_NODE_ID)
node_namestringyesName of the node sending the ping
addressstringyesPublic address of the node sending the ping (including the scheme prefix)
secret_keystringyesSecret of the node sending the ping

Response (includes the full known node list):

json
{
  "success": true,
  "data": {
    "nodes": [
      {
        "id": 1,
        "node_id": 1,
        "node_name": "node-cn",
        "address": "https://cn.example.com",
        "status": 1,
        "last_heartbeat": 1718000000,
        "ping_failures": 0,
        "disabled": false,
        "created_at": 1718000000,
        "updated_at": 1718000000
      }
    ]
  }
}

13.2 Data Sync (internal) ​

Endpoint: POST /api/cluster/sync

Auth: Cluster secret

Description: Receives data-change events pushed from other nodes. The receiver skips events whose event.NodeId equals its own node ID to avoid loops.

Request body:

json
{
  "source_node_id": 1,
  "events": [
    {
      "id": 1001,
      "table_name": "channels",
      "operation": "UPDATE",
      "primary_key": 5,
      "data": {
        "id": 5,
        "name": "OpenAI",
        "status": 2
      },
      "event_time": 1718000000
    }
  ]
}

Request fields:

FieldTypeRequiredDescription
source_node_idintyesID of the node that emitted the events
eventsarrayyesEvent list, max 50 per batch by default

Event fields:

FieldTypeDescription
idint64Unique event ID
table_namestringTable name (users / tokens / channels / abilities / options / plans / user_plans / redemptions, etc.)
operationstringOperation type: INSERT / UPDATE / DELETE
primary_keyuintPrimary key value
dataobjectChange payload (empty on DELETE)
event_timeint64Event timestamp (seconds)

Response:

json
{
  "success": true,
  "data": {
    "applied": 1,
    "skipped": 0
  }
}

13.3 List All Nodes ​

Endpoint: GET /api/cluster_node/

Auth: Root admin

Query parameters:

ParameterTypeDescription
pintPage number, default 0

Response:

json
{
  "success": true,
  "message": "",
  "data": [
    {
      "id": 1,
      "node_id": 1,
      "node_name": "node-cn",
      "address": "https://cn.example.com",
      "status": 1,
      "last_heartbeat": 1718000000,
      "ping_failures": 0,
      "disabled": false,
      "secret_key": "node-1-secret",
      "created_at": 1718000000,
      "updated_at": 1718000000
    }
  ]
}

Response fields:

FieldTypeDescription
iduintRecord ID
node_idintNode number (matches CLUSTER_NODE_ID)
node_namestringNode name
addressstringNode public address
statusintStatus: 1=alive, 2=failed
last_heartbeatint64Last heartbeat time (Unix timestamp)
ping_failuresintConsecutive ping failures
disabledboolWhether the admin has disabled this node
secret_keystringThe node's access secret

13.4 Get a Single Node ​

Endpoint: GET /api/cluster_node/:id

Auth: Root admin

Path parameters:

ParameterTypeDescription
idintNode record ID (not node_id)

13.5 Add a Node ​

Endpoint: POST /api/cluster_node/

Auth: Root admin

Request body:

json
{
  "node_id": 2,
  "node_name": "node-us",
  "address": "https://us.example.com",
  "secret_key": "node-2-secret"
}

Request fields:

FieldTypeRequiredDescription
node_idintyesNode number (1-49), unique within the cluster
node_namestringyesNode name
addressstringyesNode public address (must include the scheme prefix, e.g. https://)
secret_keystringyesInitial secret; must match the target node's CLUSTER_SECRET

13.6 Update a Node ​

Endpoint: PUT /api/cluster_node/

Auth: Root admin

Request body:

json
{
  "id": 2,
  "node_id": 2,
  "node_name": "node-us",
  "address": "https://us.example.com",
  "secret_key": "new-secret-value",
  "disabled": false
}

Request fields:

FieldTypeRequiredDescription
iduintyesNode record ID
node_idintnoNode number
node_namestringnoNode name
addressstringnoNode public address
secret_keystringnoUpdate the node's secret. After the update, other nodes must use the new value when contacting this node.
disabledboolnoDisabled state

Secret rotation: A node's secret is carried by the X-Cluster-Secret header during ping and verified by the target against its own secret. When the admin updates a node's secret, other nodes learn the new value automatically on the next ping.

13.7 Soft Delete a Node ​

Endpoint: DELETE /api/cluster_node/:id

Auth: Root admin

Description: Does not hard-delete the record; sets disabled = true instead. Disabled nodes still respond to pings (so peers know they are online), but no other node will push events to them. Hard delete requires running SQL manually: DELETE FROM cluster_nodes WHERE node_id = ?.

13.8 Re-enable a Disabled Node ​

Endpoint: POST /api/cluster_node/:id/enable

Auth: Root admin

Description: Resets disabled to false, restoring the node's participation in cluster communication.

13.9 Manually Ping a Node ​

Endpoint: GET /api/cluster_node/ping/:id

Auth: Root admin

Description: Triggers a single ping, typically used to diagnose connectivity.