Stellar ControlMission control · by Stellar Systems v0.1.0

API Guide

WebSocket Streams

Follow telecommands, runs, current values, alarms and gateway throughput over WebSocket.

Nine routes of the API follow something as it happens. They are GET requests that upgrade to a WebSocket; each text message the server sends is one JSON document. The client sends nothing (anything it sends is ignored); closing the socket stops the follow-up. Identity is not required to follow.

RouteMessagesReplays the pastEnds
GET /v1/tc/{target}/{id}/eventsTcEventyes, every event of the telecommandon a final state
GET /v1/tc/watch?target=…TcWatched: {target, event}, the TcEvent with its targetno, events from nownever
GET /v1/runs/{id}/eventsRunEventyes, the whole log of the runafter finished
GET /v1/targets/{target}/values/watch?measures=…CurrentValuethe current values firstnever
GET /v1/alarms/watch?target=…AlarmEventno, transitions from nownever
GET /v1/instances/gateway/{instance}/throughputThroughputViewno, reports from nownever
GET /v1/passes/watch?target=…PassViewevery pass firstnever
GET /v1/schedules/watch?target=…Scheduleevery schedule firstnever
GET /v1/transfers/watch?target=…Transferevery transfer firstnever
GET /v1/reconciler/events/watch?target=…journal Entryno, changes from nownever

When the stream ends, the server sends a WebSocket close frame. Errors found before the upgrade (an invalid target or identifier, the bus unavailable) answer a plain HTTP error with the error body: 404 api::invalid-target, 404 api::invalid-id, 400 api::invalid-measure, 503 api::unavailable.

Telecommand events#

GET /v1/tc/{target}/{id}/events replays the events of the telecommand from the TC_EVENTS stream, follows the new ones, and closes after a final state: REJECTED, ENCODE_FAILED, SEND_FAILED, VERIFIED, VERIFY_FAILED, VERIFY_TIMEOUT or COMPLETE. Connecting after the end still gives the whole chain.

JSON
{"tc": "01JA2Y8S5V5E6F7G8H9J0K1M2N", "state": "PENDING", "at": "2026-10-02T10:15:01.120Z",
 "telecommand": {"component": "tcu", "instance": "TCU1", "telecommand": "set_mode"}}
{"tc": "01JA2Y8S5V5E6F7G8H9J0K1M2N", "state": "ENCODED", "at": "2026-10-02T10:15:01.131Z"}
{"tc": "01JA2Y8S5V5E6F7G8H9J0K1M2N", "state": "SENT", "at": "2026-10-02T10:15:01.140Z"}
{"tc": "01JA2Y8S5V5E6F7G8H9J0K1M2N", "state": "ECHO", "at": "2026-10-02T10:15:01.402Z", "conforming": true}
{"tc": "01JA2Y8S5V5E6F7G8H9J0K1M2N", "state": "VERIFIED", "at": "2026-10-02T10:15:03.010Z"}

detail explains a failure or a rejection; ECHO is not a state and carries conforming. See Telecommand Lifecycle.

Run events#

GET /v1/runs/{id}/events replays the log of the run from the RUNS stream, follows it, and closes after its finished event. Each message has run, seq (position in the log, from 1), at, and event with the fields of that event:

eventFields
startedprocedure, library, environment, targets, ir, by, inputs
procedure_started, procedure_finishedpath; ok when finished
step_startedpath, attempt, inputs
telecommand_sentpath, tc, target, telecommand, changes_state, retry_of
telecommand_finishedpath, tc, state, detail, acks (the chain of events)
checkedpath, statement (expect, check, wait_until), condition, ok, reason, samples
loggedpath, message, samples
askedpath, prompt, role (for a hazardous confirmation)
answeredpath, accepted, by, value, role
answer_refusedpath, by, reason
decision_requiredpath, reason
suspended, continuedpath, by; choice when continued
taken_overby (executor instance)
step_finishedpath, attempt, ok, reason
finishedok, reason
JSON
{"run": "01JA2Z…", "seq": 1, "at": "…", "event": "started", "procedure": "Hot standby test",
 "library": "platform-v3-steps@1.4.2", "environment": "AIT",
 "targets": {"sat": "sim-1", "psu": "psu-sim-1"}, "ir": "9f3c…",
 "inputs": {"tcu": "TCU2", "bus_voltage": 28.0}}
{"run": "01JA2Z…", "seq": 2, "at": "…", "event": "procedure_started", "path": "Hot standby test"}
{"run": "01JA2Z…", "seq": 3, "at": "…", "event": "step_started",
 "path": "Hot standby test / Power the PPU", "attempt": 1}

A path locates a procedure or a step: names separated by /, #<n> for an unnamed inline step. When an asked or decision_required event arrives, answer with POST /v1/runs/{id}/answers and its path. See Evidence and Reports.

Current values#

GET /v1/targets/{target}/values/watch sends the current value of each measure of the target, then every update of the current value table. measures (comma-separated) selects measures:

SelectorSelects
tcu[TCU1].respondingone instance
tcu.respondingevery instance of the component
respondingthe measure in any component

Without measures, every measure of the target. Each message is a CurrentValue:

JSON
{"component": "tcu", "instance": "TCU1", "measure": "anode_voltage",
 "sample": {"value": 42.5, "raw": 9284, "time": "…", "ground_time": "…",
            "link": "nominal", "delivery": "realtime"}}

instance is absent for a single-instance component. See Measures and Current Values.

Alarm transitions#

GET /v1/alarms/watch sends every alarm transition from now on, in order; target keeps those of one target. Each message is an AlarmEvent:

JSON
{"target": "sat1-fm", "component": "tcu", "instance": "TCU1", "alarm": "anode_voltage",
 "previous": "NORMAL", "state": "ACTIVE_UNACK", "severity": "warning", "value": 285.2,
 "at": "…", "seq": 7, "cause": "condition"}

Read the current states with GET /v1/alarms first, then follow: the transitions carry seq, increasing per alarm. See Alarm Handling.

Passes, schedules and transfers#

GET /v1/passes/watch, GET /v1/schedules/watch and GET /v1/transfers/watch send every pass (with the check of its gateway), schedule or transfer first, then each one again when it changes: a pass refined by a feeder or whose gateway check changes, a schedule at risk, fired or missed, a transfer that progresses. target keeps those of one target (for schedules, those on its passes). Each message is the whole object, as GET /v1/passes, GET /v1/schedules/{id} and GET /v1/transfers/{target}/{file_id}/{generation} return it: keep the last one of each identifier.

Journal of the reconciler#

GET /v1/reconciler/events/watch sends each change the reconciler makes from now on: a link bound, unbound or rebound, a target ready or not ready, a new revision of its configuration. GET /v1/reconciler/events?since=…&limit=… reads the past ones, newest first:

JSON
{"at": "…", "target": "sim-1", "link": "nominal", "change": "rebound",
 "driver": "platform-v3-sim-2", "gateway": "sim-gw-1", "reasons": []}

Gateway throughput#

GET /v1/instances/gateway/{instance}/throughput relays the throughput reports of a gateway, for each target it serves, as it publishes them (every metrics_period, 5 s by default):

JSON
{"target": "sat1-fm", "at": "…", "uplink_bps": 2048.0, "downlink_bps": 1843200.0,
 "uplink_bytes": 51200, "downlink_bytes": 94371840, "window_ms": 5000}

Examples#

With websocat:

Shell
websocat ws://localhost:8080/v1/runs/01JA2Z…/events
websocat 'ws://localhost:8080/v1/targets/sim-1/values/watch?measures=tcu%5BTCU1%5D.anode_voltage'
websocat 'ws://localhost:8080/v1/alarms/watch?target=sat1-fm'

With the Python websockets package:

Python
import asyncio, json
import websockets

async def follow(run: str) -> bool:
    async with websockets.connect(f"ws://localhost:8080/v1/runs/{run}/events") as log:
        async for text in log:
            event = json.loads(text)
            print(event["seq"], event["event"], event.get("path", ""))
            if event["event"] == "finished":
                return event["ok"]
    return False

asyncio.run(follow("01JA2Z…"))

The API client of the Python SDK does this for runs, with typed events and reconnection: async for event in run.events().

Stellar Control · v0.1.0

↑↓ to moveEnter to open