Files
nexus-sync/docs/api.md
T
2026-06-25 02:39:25 +03:00

6.8 KiB

API contract

Transport

  • Protocol: HTTPS.
  • Request and response body format: JSON.
  • API prefix: /api/v1.
  • Time format: RFC 3339 / ISO 8601, e.g. 2026-05-24T13:20:30Z.
  • Client authorization: bearer token in the Authorization header.

Example headers:

Content-Type: application/json
Authorization: Bearer <client-token>

Client heartbeat

POST /api/v1/client/heartbeat

The client calls this endpoint on every polling tick. The same endpoint serves three scenarios:

  • an "I'm alive" message;
  • a regular state update;
  • reporting the result of the last finished command.

Request

{
  "client_id": "macbook-pro-01",
  "observed_at": "2026-05-24T13:20:30Z",
  "client": {
    "hostname": "macbook-pro.local",
    "platform": "darwin",
    "version": "0.1.0"
  },
  "state": {
    "local_time": "2026-05-24T16:20:30+03:00",
    "uptime_seconds": 1200
  },
  "last_command_result": null
}

Fields:

  • client_id - a stable identifier set during client setup. It must not change on every restart.
  • observed_at - the moment the client prepared the payload.
  • client.hostname - the machine's current hostname.
  • client.platform - the client platform. Prefer names close to Python/platform: linux, darwin, windows.
  • client.version - the nexus-sync client version.
  • state - an intentionally small object. IP, disk usage, memory, battery status and other metrics may be added here later.
  • last_command_result - null if the client has nothing new to report about command execution.

Request with command result

{
  "client_id": "macbook-pro-01",
  "observed_at": "2026-05-24T13:21:05Z",
  "client": {
    "hostname": "macbook-pro.local",
    "platform": "darwin",
    "version": "0.1.0"
  },
  "state": {
    "local_time": "2026-05-24T16:21:05+03:00",
    "uptime_seconds": 1235
  },
  "last_command_result": {
    "command_id": "cmd_01JY3H8V8W8P3FXDR3S2BM7M6B",
    "status": "succeeded",
    "started_at": "2026-05-24T13:20:35Z",
    "finished_at": "2026-05-24T13:20:36Z",
    "return_code": 0,
    "stdout": "lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384\n",
    "stderr": ""
  }
}

Result fields:

  • command_id must match the id of the command the server previously returned to the client.
  • status takes one of: succeeded, failed, timed_out, rejected.
  • return_code - the process exit code, if the process was started. For timed_out or rejected the field may be null when there is no final exit code.
  • stdout and stderr must be size-limited by the client before sending. This contract does not yet fix a specific byte limit.

Response without command

{
  "status": "ok",
  "server_time": "2026-05-24T13:20:30Z",
  "next_poll_after_seconds": 60,
  "command": null
}

Response with command

{
  "status": "ok",
  "server_time": "2026-05-24T13:20:30Z",
  "next_poll_after_seconds": 10,
  "command": {
    "id": "cmd_01JY3H8V8W8P3FXDR3S2BM7M6B",
    "kind": "exec",
    "name": "network_interfaces",
    "args": {},
    "timeout_seconds": 30
  }
}

Command fields:

  • id is generated by the server and must be unique.
  • kind describes the executor type. Only exec is defined so far.
  • name - the command preset name.
  • args holds arguments specific to a particular preset.
  • timeout_seconds - the maximum execution time the server allows for this command.

The client must run only commands it knows and locally allows. Unknown or forbidden commands must be returned as rejected.

Server-side API

These endpoints are for the server/admin side, to see clients and queue commands for them. Clients directly use only the heartbeat.

GET /api/v1/server/clients

Returns the list of known clients with their latest heartbeat data and available_commands.

GET /api/v1/server/clients/{client_id}

Returns a single client, or 404 if the server has not seen this client_id yet.

POST /api/v1/server/clients/{client_id}/commands

Creates a pending command for the client. The client receives it on its next heartbeat. This endpoint accepts the command name directly.

Request:

{
  "name": "hostname",
  "args": {},
  "timeout_seconds": 30
}

Response:

{
  "id": "cmd_...",
  "client_id": "macbook-pro-01",
  "kind": "exec",
  "name": "hostname",
  "args": {},
  "status": "pending",
  "timeout_seconds": 30,
  "attempts": 0,
  "max_attempts": 1,
  "created_at": "2026-05-24T13:20:30Z",
  "delivered_at": null,
  "finished_at": null
}

GET /api/v1/server/commands/{command_id}

Returns the command and, if the client has already reported, the result field.

Command lifecycle

Command lifecycle:

  1. pending: the command was created on the server and not yet delivered to the client.
  2. delivered: the command was returned to the client in a heartbeat response.
  3. succeeded: the client reported successful execution.
  4. failed: the client reported an execution error.
  5. timed_out: the client reported a timeout.
  6. rejected: the client refused to run the command.

Terminal statuses: succeeded, failed, timed_out, rejected.

HTTP statuses

The heartbeat endpoint should use these HTTP statuses:

  • 200 OK: heartbeat accepted; the response body matches the contract above.
  • 400 Bad Request: malformed JSON or invalid field values.
  • 401 Unauthorized: the bearer token is missing or invalid.
  • 403 Forbidden: the token is valid but not allowed to act as this client_id.
  • 409 Conflict: the result references an unknown, already terminal, or incompatible command.
  • 429 Too Many Requests: the client polls the server too often.
  • 500 Internal Server Error: an unexpected server error.

Command execution state is expressed via JSON fields, not HTTP errors. For example, a command with exit code 1 is still delivered over a successful heartbeat request.

Polling rules

The server response contains next_poll_after_seconds.

Behavior:

  • normal polling interval without a command: 60 seconds;
  • polling interval after receiving a command: 10 seconds;
  • client minimum interval: 5 seconds;
  • client maximum interval: 300 seconds.

The client should treat the server value as a recommendation and clamp it to its local min/max bounds.

Security constraints

The following constraints must be respected:

  • do not run arbitrary shell strings received from the server;
  • run only locally known command presets;
  • require a bearer token for client endpoints;
  • include command_id in every result to avoid ambiguous matching;
  • store command output as logs, not as trusted control data;
  • limit the size of command output before sending it to the server;
  • run every command with a timeout.

These constraints are intentionally part of the contract because the project deals with remote machine management. It is cheaper to build the first implementation around them than to add them after the fact.