All docsStellar LinkRF bench · by Stellar Systems v0.1.0

API Reference

System

Health, version, capabilities

GET/api/v1/health

Health#

Responses

200Process is aliveapplication/json
statusrequired

string

Request

curl "http://192.168.2.1:8080/api/v1/health"

Response

{
  "status": "string"
}
GET/api/v1/metrics

Metrics#

The Prometheus registry, as JSON, on the API port.

WHY IT EXISTS ALONGSIDE :9090/metrics. The exposition is served on the observability port, which is right for a scraper and unusable from the dashboard: the frontend talks to the API port, a second origin needs CORS the metrics server does not set, and a browser has no business splitting exposition text by hand.

404 WHEN THERE IS NO EXPORTER, rather than an empty list. "This daemon has no metrics" and "every metric reads zero" are different facts and a caller cannot tell them apart from [].

Responses

200OKapplication/json

array of MetricFamilyDRO

404Not found

Request

curl "http://192.168.2.1:8080/api/v1/metrics"

Response

[
  {}
]
GET/api/v1/system/capabilities

Capabilities#

Responses

200OKapplication/json
modulationsrequired

array of string

Supported modulations (strings matching the profile enum).

rx_channelsrequired

integer · int32

Number of RX channels exposed by the platform.

≥ 0

tx_channelsrequired

integer · int32

Number of TX channels exposed by the platform.

≥ 0

mockrequired

boolean

true when the PL driver is a mock (no real HW attached).

symbol_ratesrequired

array of SymbolRateDRO

THE SYMBOL RATES THIS MODEM CAN PRODUCE, and the two it cannot.

Not a menu someone chose: the rate is ad9361_rate / (sps * interp) with sps fixed at 4 and interp decoded only over 1..32, so the set is fixed by the hardware. Clients build their forms from this rather than offering a free number — phy.symbol_rate_baud accepted anything and reached no register at all, so a profile declaring 9600 baud transmitted at about 2 MBaud (measured 2026-09-13).

The unreachable entries are PRESENT, with the bound they hit, rather than silently absent: a list that quietly drops what it cannot do reads as a complete list of what exists.

SymbolRateDRO · 6 fields
baudrequired

number · double

The exact rate in baud. Fractional at the bottom of the ladder: the hardware produces 39 062.5, and rounding it would publish a rate nobody can select.

labelrequired

string

As an operator writes it, e.g. "39.063 ksym/s".

availablerequired

boolean

False when this rate cannot be produced at all.

ad9361_rate_sps

integer · int64 · nullable

What the AD9361 must be clocked at to realise it. null when unavailable.

≥ 0

interp

integer · int32 · nullable

The DUC interpolation that realises it. null when unavailable.

≥ 0

unavailable_reason

string · nullable

Which bound it hits. Present exactly when available is false.

Request

curl "http://192.168.2.1:8080/api/v1/system/capabilities"

Response

{
  "modulations": [
    "string"
  ],
  "rx_channels": 0,
  "tx_channels": 0,
  "mock": true,
  "symbol_rates": [
    {
      "baud": 0.0,
      "label": "string",
      "available": true,
      "ad9361_rate_sps": 0,
      "interp": 0,
      "unavailable_reason": "string"
    }
  ]
}
GET/api/v1/system/clocks

Clocks#

Responses

200OKapplication/json
availablerequired

boolean

None when debugfs is not mounted — distinct from "no clocks", which would read as a healthy empty set.

sourcerequired

string

clocksrequired

array of ClockDRO

ClockDRO · 4 fields
namerequired

string

enablerequired

integer · int32

Authoritative: non-zero means the clock is enabled.

≥ 0

preparerequired

integer · int32

Authoritative: non-zero means it is prepared.

≥ 0

rate_hzrequired

integer · int64

NOT authoritative. Computed by the clock framework from the parent and the dividers, and printed unchanged with the MMCM off (STE-943). Present for display; never judge on it.

≥ 0

Request

curl "http://192.168.2.1:8080/api/v1/system/clocks"

Response

{
  "available": true,
  "source": "string",
  "clocks": [
    {
      "name": "string",
      "enable": 0,
      "prepare": 0,
      "rate_hz": 0
    }
  ]
}
GET/api/v1/system/status

Status#

Responses

200OKapplication/json
uptime_secondsrequired

integer · int64

≥ 0

pl_driverrequired

string

mock or uio.

servicesrequired

array of ServiceStatusDRO

ServiceStatusDRO · 2 fields
namerequired

string

healthyrequired

boolean

Request

curl "http://192.168.2.1:8080/api/v1/system/status"

Response

{
  "uptime_seconds": 0,
  "pl_driver": "string",
  "services": [
    {
      "name": "string",
      "healthy": true
    }
  ]
}
GET/api/v1/version

Version#

Responses

200OKapplication/json
firmwarerequired

string

satlinkd crate version.

apirequired

string

API contract version (v1).

fpga

string · nullable

PL bitstream identifier read from the shell — None in mock mode.

This is the shell's BUILD_ID register, read live over /dev/uio0. It says which bitstream is LOADED, which no host-side manifest can.

releaserequired

string

Release bundle this daemon was built for, or "unreleased".

Compare it with the manifest of the bundle you deployed: they must be equal. On a ramdisk rootfs, deploying and running are two different things, and this field is what separates them (STE-985).

Request

curl "http://192.168.2.1:8080/api/v1/version"

Response

{
  "firmware": "string",
  "api": "string",
  "fpga": "string",
  "release": "string"
}

↑↓ to moveEnter to open