All docsStellar LinkRF bench · by Stellar Systems v0.1.0

API Reference

Models

The data structures sent to and returned by the API.

AckIrqDTO#

bitsrequired

integer · int32

≥ 0

ActiveDRO#

idrequired

string

campaignrequired

string

cursorrequired

integer

≥ 0

totalrequired

integer

≥ 0

pausingrequired

boolean

ActiveProfileDRO#

The active profile, AND whether it still describes the board.

name alone was a claim that outlived its truth: moving the frequency, a gain or a register by hand leaves the profile reported as applied while the board no longer matches it. Same idea the RF readiness already applies to the digital tuning, generalised — a status that asserts something no longer true is worse than no status, because it is believed.

name

string · nullable

dirtyrequired

boolean

A manual change landed AFTER the profile was applied.

dirty_reason

string · nullable

What made it dirty — the route or register. "dirty" alone sends the reader looking through everything.

ApiErrorDRO#

Error payload returned to clients.

coderequired

string

Machine-readable error code (not_found, validation_failed, …).

messagerequired

string

Human-readable message.

ApplyResultDRO#

Result of POST /profiles/{id}/apply.

profile_namerequired

string

cfg_epochrequired

integer · int32

≥ 0

tuningrequired

string

What the apply did to the RADIO, in one sentence — tuned, or why not.

Applying a profile used to leave the AD9361 exactly where it was while the reply said nothing about it (STE-994), so a UHF profile could be "applied" onto a part listening in S-band and every field in this struct stayed green. A tuning that did not happen has to be visible where the apply is.

AssertionResultDRO#

One assertion's outcome, surfaced in the report detail endpoint.

namerequired

string

passedrequired

boolean

verifiedrequired

boolean

Whether this assertion read the LINK or only the scenario's own timeline.

It was missing from this DTO while scenario_passed required passed && verified on every assertion — so the field that decides the verdict was the one field a caller could not see, and a green run was indistinguishable from a run that had checked the script against itself. That distinction is the entire reason the flag exists.

messagerequired

string

AttemptDRO#

steprequired

string

outcomerequired

string

run_id

string · nullable

detailrequired

string

at_uptime_msrequired

integer · int64

MILLISECONDS SINCE THE DAEMON STARTED, not since the epoch. The board has no RTC; a client subtracting this from its own wall clock would be out by decades, which has already cost this project a UI feature.

≥ 0

AuxDRO#

availablerequired

boolean

reason

string · nullable

Set when nothing could be mapped — typically satlinkd not running as root, or /dev/mem absent.

regsrequired

array of AuxRegDRO

AuxRegDRO · 5 fields
namerequired

string

addrrequired

string

value

integer · int32 · nullable

≥ 0

docrequired

string

What the value means; present even when the read failed, so a client can show the contract without the number.

error

string · nullable

Why value is absent. Never left implicit: a missing number that does not say why is indistinguishable from a zero.

AuxRegDRO#

namerequired

string

addrrequired

string

value

integer · int32 · nullable

≥ 0

docrequired

string

What the value means; present even when the read failed, so a client can show the contract without the number.

error

string · nullable

Why value is absent. Never left implicit: a missing number that does not say why is indistinguishable from a zero.

BlockMatchDRO#

indexrequired

integer

≥ 0

matchedrequired

integer

≥ 0

totalrequired

integer

≥ 0

rotationrequired

integer

≥ 0

ratiorequired

number · double

CampaignActiveDRO#

active

ActiveDRO · nullable

ActiveDRO · 5 fields
idrequired

string

campaignrequired

string

cursorrequired

integer

≥ 0

totalrequired

integer

≥ 0

pausingrequired

boolean

board_providerrequired

string

Who would take a board action, in words. "the orchestrator power-cycled it" and "nobody did, the step was refused" must not read the same afterwards.

CampaignStateDRO#

idrequired

string

campaignrequired

string

statusrequired

string

cursorrequired

integer

≥ 0

total_stepsrequired

integer

≥ 0

verdict

boolean · nullable

true / false / null — and null is the common case.

A CAMPAIGN THAT DID NOT FINISH HAS NO VERDICT. One paused at step 3 of 15 must not report a pass on twelve steps nobody ran, and a client that rendered false there would be accusing the board of failures it was never asked to produce.

pause_requestedrequired

boolean

A pause is asked for and has not yet taken effect.

attemptsrequired

array of AttemptDRO

AttemptDRO · 5 fields
steprequired

string

outcomerequired

string

run_id

string · nullable

detailrequired

string

at_uptime_msrequired

integer · int64

MILLISECONDS SINCE THE DAEMON STARTED, not since the epoch. The board has no RTC; a client subtracting this from its own wall clock would be out by decades, which has already cost this project a UI feature.

≥ 0

started_at_uptime_msrequired

integer · int64

≥ 0

CampaignSummaryDRO#

namerequired

string

display_name

string · nullable

descriptionrequired

string

stepsrequired

integer

≥ 0

cron

string · nullable

The cron expression, when this campaign schedules itself.

tagsrequired

array of string

CapabilitiesDRO#

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.

CaptureRequest#

blocksrequired

integer

Number of RAW DMA blocks to collect. Obtaining them in a row is the proof of a sustained regime; a short capture is refused, not truncated.

≥ 0

warmup

integer

Blocks to obtain and DISCARD before the measured window starts.

Every capture re-arms the chain, so the leading blocks carry the acquisition transient. Skipping them is legitimate; skipping them SILENTLY, or guessing which ones look like transient, would throw away real head-of-capture errors and call the result clean. Hence explicit, and zero by default.

≥ 0

timeout_ms

integer · int64

Give up after this many milliseconds.

≥ 0

release

boolean

Release the IIO devices afterwards so bring-up tools can open them.

ChainStateDRO#

targetrequired

string

The target these conditions were computed for.

current_target

string · nullable

What the board is wired for right now; null when the two loopback switches could not be read.

reaches_the_connectorrequired

boolean

Does anything leave the board in this target? False for both loopbacks — the AD9361's internal one returns TX to RX BEFORE the mixers, so no LO, gain or attenuation setting can explain an empty analyser.

stagerequired

string

readyrequired

boolean

next

string · nullable

The first unmet condition and the command to run. null when ready.

conditionsrequired

array of ConditionDRO

Every condition, in the order the bring-up needs them.

ConditionDRO · 8 fields
keyrequired

string

Stable key — dac-source, tx-egress, …

stagerequired

string

The stage this must be satisfied to leave.

sourcerequired

string

board when the value was re-read from a register or an IIO attribute, session when it is this daemon's memory and a restart forgets it.

requiredrequired

string

observedrequired

string

What it is, or could not be read.

verdictrequired

string

met / unmet / unknown. An unknown BLOCKS: a register we failed to read is not a register holding the right value.

breaksrequired

string

What goes wrong when it is unmet, as the symptom the operator will see.

fixrequired

string

The command that clears it.

ClockDRO#

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

ClocksDRO#

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

ConditionDRO#

keyrequired

string

Stable key — dac-source, tx-egress, …

stagerequired

string

The stage this must be satisfied to leave.

sourcerequired

string

board when the value was re-read from a register or an IIO attribute, session when it is this daemon's memory and a restart forgets it.

requiredrequired

string

observedrequired

string

What it is, or could not be read.

verdictrequired

string

met / unmet / unknown. An unknown BLOCKS: a register we failed to read is not a register holding the right value.

breaksrequired

string

What goes wrong when it is unmet, as the symptom the operator will see.

fixrequired

string

The command that clears it.

ConditionRequest#

target

string · nullable

Where the samples should go. Defaults to air — the only target of the three that puts anything on a connector, and the one an operator at a spectrum analyser is asking for.

profile

string · nullable

Which profile to apply. Defaults to the one currently applied; with none applied the request is refused rather than guessing, because the profile decides the frame length, the coding and the frequency.

rate_sps

integer · int64

AD9361 rate for the tuning step, when one is needed.

≥ 0

ConditionResultDRO#

stepsrequired

array of string

The steps actually run, in order.

detailrequired

array of string

What each step reported, keyed in the same order as steps.

staterequired

ChainStateDRO

ChainStateDRO · 7 fields
targetrequired

string

The target these conditions were computed for.

current_target

string · nullable

What the board is wired for right now; null when the two loopback switches could not be read.

reaches_the_connectorrequired

boolean

Does anything leave the board in this target? False for both loopbacks — the AD9361's internal one returns TX to RX BEFORE the mixers, so no LO, gain or attenuation setting can explain an empty analyser.

stagerequired

string

readyrequired

boolean

next

string · nullable

The first unmet condition and the command to run. null when ready.

conditionsrequired

array of ConditionDRO

Every condition, in the order the bring-up needs them.

ConditionDRO · 8 fields
keyrequired

string

Stable key — dac-source, tx-egress, …

stagerequired

string

The stage this must be satisfied to leave.

sourcerequired

string

board when the value was re-read from a register or an IIO attribute, session when it is this daemon's memory and a restart forgets it.

requiredrequired

string

observedrequired

string

What it is, or could not be read.

verdictrequired

string

met / unmet / unknown. An unknown BLOCKS: a register we failed to read is not a register holding the right value.

breaksrequired

string

What goes wrong when it is unmet, as the symptom the operator will see.

fixrequired

string

The command that clears it.

DatapathStateDRO#

enabledrequired

boolean

DatapathStatusDRO#

acquiredrequired

boolean

True while the daemon holds the modem's IIO devices.

EnableLoopbackDTO#

enabledrequired

boolean

EventDRO#

Event record returned by /events.

id

integer · int64 · nullable

kindrequired

string

sourcerequired

string

severityrequired

string

messagerequired

string

timestamprequired

string · date-time

ExecutionEventDRO#

One entry in a run's execution timeline. Mirrors satlink_scenario_engine::report::ExecutionEvent with a stringified kind for wire stability (the engine enum is free to add variants).

elapsed_msrequired

integer · int64

≥ 0

event_id

string · nullable

kindrequired

string

descriptionrequired

string

FftCaptureDRO#

sizerequired

integer · int32

≥ 0

sourcerequired

integer · int32

monitor.fft.SRC_SEL — which tap the core is pointed at.

≥ 0

bins_dbrequired

array of number · double

Frequency-domain magnitude samples.

syntheticrequired

boolean

TRUE while these bins are MANUFACTURED HERE and do not come from the PL.

The field exists because nothing in the response distinguished a measurement from an invention: a spectrum served by /api/v1/... reads as signal, and the UI displayed it as such. The client now labels itself from this value rather than from a hard-coded string, which would have gone stale the day the real path landed — and a stale note is this project's dominant failure mode.

Set it to false IN THE SAME COMMIT as the real sample read, not before: a premature false is worse than today's true, because it makes the invention undetectable.

channelrequired

string

Which end of the link this spectrum describes ("rx" / "tx").

enabledrequired

boolean

Whether analysis is switched on for that channel. Off by default: a frame costs a DMA block plus a transform, and nothing should pay that because a dashboard tab happens to be open.

sample_rate_hzrequired

integer · int64

Sample rate the block was taken at, 0 when there is no block.

≥ 0

unavailable

Unavailable · nullable

FlushedDRO#

useful_bytesrequired

integer

Useful bytes HANDED TO THE DMA, padding included. Zero means nothing was pending — a block of pure padding would put silence on the air.

Handed to the DMA is not "on the air". This field used to say it was, and it was wrong: with the TX egress closed the modem discards the block and this number is exactly the same. tx_chain_running is the only field here that tells the two apart.

≥ 0

tx_chain_running

boolean · nullable

Whether the modem's TX egress is open. null when there is no PL driver to ask, which is not the same as false.

FlushRequest#

blocks

integer

How many blocks to emit. Use 2 whenever you intend to see the result come back. An RX block is returned only when ENTIRELY full, and the receive side is always short of what was sent — the first frame of a freshly-armed chain is lost to acquisition, so 4096 useful bytes out come back as 4080. One block therefore stops just before the line and waits for traffic a one-shot sender never sends.

Measured on hardware 2026-09-12: one flushed block never surfaced in 60 s (with tx.framer.frame_cnt confirming all 256 frames had crossed); two came back in 2 s.

≥ 0

FrameDRO#

idrequired

string · uuid

timestamprequired

string · date-time

profile

string · nullable

protocolrequired

string

fec_statusrequired

string

crc_ok

boolean · nullable

payload_hexrequired

string

FramesPageDRO#

One page of the ring, plus what the caller cannot see from the rows.

[] used to be the whole answer, and it conflated two very different facts: "the link carried nothing" and "nothing is reading the modem". The first is information about the RADIO, the second about the DAEMON, and an operator reads the empty array as the first. Same shape as /pl/aux, which reports available: false WITH its reason.

granularityrequired

string

What ONE entry is. "dma_block" today: the producer pushes one entry per DMA block, so an entry is 4096 useful bytes cut at a boundary that has no relation to any protocol frame. Calling that a frame is the defect STE-989 is about; naming it is the honest half of the fix. For packet granularity use /ws/frames?framing=slot&slot_len=N.

clockrequired

string

How to read timestamp. "boot_relative": the board has no RTC and no NTP, so times start at the epoch on every boot. A control centre would otherwise take them for wall-clock.

empty_reason

string · nullable

null when the page is non-empty. Otherwise WHY it is empty.

framesrequired

array of FrameDRO

FrameDRO · 7 fields
idrequired

string · uuid

timestamprequired

string · date-time

profile

string · nullable

protocolrequired

string

fec_statusrequired

string

crc_ok

boolean · nullable

payload_hexrequired

string

HealthDRO#

statusrequired

string

InterruptsDRO#

statusrequired

integer · int32

≥ 0

enablerequired

integer · int32

≥ 0

bitsrequired

array of string

IqAnnotationDTO#

offset_samplesrequired

integer · int64

≥ 0

labelrequired

string

note

string · nullable

IqCaptureDRO#

idrequired

string · uuid

created_atrequired

string · date-time

samplesrequired

integer · int64

≥ 0

sample_rate_hzrequired

integer · int64

≥ 0

center_frequency_hzrequired

integer · int64

≥ 0

pathrequired

string

size_bytesrequired

integer · int64

≥ 0

statusrequired

string

tagsrequired

array of string

annotationsrequired

array of IqAnnotationDTO

IqAnnotationDTO · 3 fields
offset_samplesrequired

integer · int64

≥ 0

labelrequired

string

note

string · nullable

profile_ref

string · nullable

syntheticrequired

boolean

TRUE when the samples were MANUFACTURED rather than captured. Without it a fabricated capture is indistinguishable from a measurement: it carries the true centre frequency and the active profile, and opens in any viewer showing a clean carrier.

JobDRO#

A job descriptor returned when kicking off an async operation (apply profile, run scenario, capture IQ, …).

job_idrequired

string · uuid

kindrequired

string

statusrequired
created_atrequired

string · date-time

updated_atrequired

string · date-time

progress

number · double · nullable

message

string · nullable

MeasureReportDRO#

verdictrequired

string

BytePerfect | Degraded | NoCorrelation.

rotation_sliprequired

boolean

A byte was lost or inserted at a block boundary. NOT a demodulation error — reporting it separately is what stops it being read as one.

blocksrequired

array of BlockMatchDRO

Read this before overall_ratio: an aggregate is exactly what hid the 2026-09-08 defect, where the whole capture scored 49.99 % while every block taken separately scored 99.98 %.

BlockMatchDRO · 5 fields
indexrequired

integer

≥ 0

matchedrequired

integer

≥ 0

totalrequired

integer

≥ 0

rotationrequired

integer

≥ 0

ratiorequired

number · double

overall_ratiorequired

number · double

worst_block_ratiorequired

number · double

capture_bytesrequired

integer

≥ 0

warmuprequired

integer

Blocks obtained and discarded before the measured window. Never hidden: a window that was moved must say so, or the number stops meaning what the reader thinks it means.

≥ 0

PlStatusDRO#

global_ctrlrequired

integer · int32

≥ 0

versionrequired

integer · int32

≥ 0

build_idrequired

integer · int32

≥ 0

cfg_epochrequired

integer · int32

≥ 0

irq_statusrequired

integer · int32

≥ 0

irq_enablerequired

integer · int32

≥ 0

PowerDRO#

taprequired

integer · int32

≥ 0

power_dbfsrequired

number · double

ProfileDRO#

Full profile payload — serialized as the opaque satlink.profile/v1 shape.

object

ProfileRevisionDRO#

One row of a profile's revision history.

versionrequired

integer · int32

≥ 0

cfg_epochrequired

integer · int32

≥ 0

applied_atrequired

string · date-time

applied_by

string · nullable

message

string · nullable

ProfileSequencerDRO#

staterequired

integer · int32

≥ 0

statusrequired

integer · int32

≥ 0

err_coderequired

integer · int32

≥ 0

profile_idrequired

integer · int32

≥ 0

state_namerequired

string

ProfileSummaryDRO#

Summary of a profile for list endpoints.

namerequired

string

display_name

string · nullable

modulationrequired

string

symbol_rate_baudrequired

number · double

center_frequency_hzrequired

integer · int64

≥ 0

PurgeQueryDTO#

older_than_hoursrequired

integer · int64

Purge events older than this many hours.

PurgeResultDRO#

purgedrequired

integer

≥ 0

RadioStatusDRO#

rx_staterequired

string

tx_staterequired

string

center_frequency_hzrequired

integer · int64

≥ 0

rx_gain_dbrequired

integer · int32

tx_gain_dbrequired

integer · int32

loopbackrequired

boolean

timing_lockrequired

boolean

carrier_lockrequired

boolean

rx_lo_hz

integer · int64 · nullable

≥ 0

rx_nco_offset_hzrequired

integer · int64

The DDC's digital offset from the RX LO, signed, in Hz.

tx_nco_offset_hzrequired

integer · int64

The DUC's digital offset from the TX LO, signed, in Hz.

profile_center_frequency_hzrequired

integer · int64

What the applied PROFILE declares as its centre, read from the inert chan_cfg shadow. It describes the configuration, not the radio, and the two diverge the moment anyone tunes by hand.

≥ 0

tx_lo_hz

integer · int64 · nullable

≥ 0

rx_rf_gain_db

number · double · nullable

The part's own gain, in dB at the connector. TX is an ATTENUATION.

tx_rf_gain_db

number · double · nullable

rx_bandwidth_hz

integer · int64 · nullable

The part's ANALOG filter width, per direction, read from IIO.

NOT a PL register — the AD9361 has none in this address space, which is exactly why the datapath diagram could not show it. None means the attribute could not be read, never a default: this is the value that sat at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number here would be worse than an absence.

The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.

SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference is deliberate. An ABSENT field cannot be told apart from an older daemon that never had it; an explicit null says "this daemon knows about the bandwidth and could not read it". For a field whose whole point is that nobody could see it before, that distinction is the feature.

≥ 0

tx_bandwidth_hz

integer · int64 · nullable

≥ 0

sample_rate_sps

integer · int64 · nullable

The part's sample rate. Also on /radio/rf-state; carried here so that one call answers "how is the front end configured" instead of two that have to be reconciled.

≥ 0

RegisterDRO#

addrrequired

integer · int32

≥ 0

valuerequired

integer · int32

≥ 0

RegisterResultDRO#

namerequired

string

replacedrequired

boolean

True when a profile of this name already existed and was replaced.

persistedrequired

boolean

Always false today, and present so a client never has to assume.

noterequired

string

ReportDetailDRO#

Detailed view returned by GET /reports/{run_id}. Embeds the parsed satlink.report/v1 payload (events + assertion results).

run_idrequired

string · uuid

scenario_namerequired

string

profile_ref

string · nullable

Profile referenced by the scenario at the time the run finished, if still known to the daemon's in-memory scenarios map.

statusrequired

string

started_atrequired

string · date-time

ended_at

string · date-time · nullable

duration_ms

integer · int64 · nullable

≥ 0

steps_executed

integer · int32 · nullable

≥ 0

assertions_passed

integer · int32 · nullable

≥ 0

assertions_failed

integer · int32 · nullable

≥ 0

has_reportrequired

boolean

true if the run had a persisted report (completed/failed/cancelled).

verifiedrequired

boolean

Whether ANY assertion in this run read the link rather than the scenario's own script. A report that observed nothing is not a pass, and the reader has to be able to tell the two apart.

abortedrequired

boolean

The run did not reach the end of its timeline. Its assertions were written for a whole one, so neither verdict applies.

eventsrequired

array of ExecutionEventDRO

ExecutionEventDRO · 4 fields
elapsed_msrequired

integer · int64

≥ 0

event_id

string · nullable

kindrequired

string

descriptionrequired

string

assertionsrequired

array of AssertionResultDRO

AssertionResultDRO · 4 fields
namerequired

string

passedrequired

boolean

verifiedrequired

boolean

Whether this assertion read the LINK or only the scenario's own timeline.

It was missing from this DTO while scenario_passed required passed && verified on every assertion — so the field that decides the verdict was the one field a caller could not see, and a green run was indistinguishable from a run that had checked the script against itself. That distinction is the entire reason the flag exists.

messagerequired

string

metric_samplesrequired

array of MetricSampleDRO

The board's own trace across the run. EMPTY IS NOT "NOTHING HAPPENED": it means no sampler was attached, which sample_interval_ms == None is how you tell apart.

sample_interval_ms

integer · int64 · nullable

The sampler's nominal tick, beside the samples — a series without its cadence cannot distinguish a slow poll from a stalled board.

≥ 0

source

string

Where this run's numbers came from: "pl", "stub", "mixed", "none".

SERVED, not kept internal. The point of the field is that a report declares its own provenance to whoever reads it — and the reader of a qualification document is usually not the person who ran it.

ReportSummaryDRO#

One row of the reports list.

run_idrequired

string · uuid

scenario_namerequired

string

profile_ref

string · nullable

Profile referenced by the scenario at the time the run finished, if still known to the daemon's in-memory scenarios map.

statusrequired

string

started_atrequired

string · date-time

ended_at

string · date-time · nullable

duration_ms

integer · int64 · nullable

≥ 0

steps_executed

integer · int32 · nullable

≥ 0

assertions_passed

integer · int32 · nullable

≥ 0

assertions_failed

integer · int32 · nullable

≥ 0

ResetDTO#

scoperequired

string

RfKillDRO#

ensm_moderequired

string

What the part's ENSM reports AFTER the stop. alert is RF off.

stoppedrequired

array of string

Steps that took effect, in the order they were applied.

failedrequired

array of string

Steps that did NOT. Present even when RF is off, because a stop that half-worked and says nothing is the failure mode this exists against.

rf_offrequired

boolean

Is RF actually off at the connector?

recoverrequired

string

How to come back.

RssiDRO#

average_dbmrequired

number · double

instant_dbmrequired

number · double

threshold_dbmrequired

number · double

ScenarioDRO#

object

ScenarioRunDRO#

Public view of a scenario execution (summary).

run_idrequired

string · uuid

scenario_namerequired

string

statusrequired

string

pending, running, passed, failed, cancelled.

started_atrequired

string · date-time

ended_at

string · date-time · nullable

duration_ms

integer · int64 · nullable

≥ 0

steps_executed

integer · int32 · nullable

≥ 0

assertions_passed

integer · int32 · nullable

≥ 0

assertions_failed

integer · int32 · nullable

≥ 0

ScenarioSummaryDRO#

namerequired

string

display_name

string · nullable

description

string · nullable

profile_ref

string · nullable

tagsrequired

array of string

ServiceStatusDRO#

namerequired

string

healthyrequired

boolean

SetFrequencyDTO#

center_frequency_hzrequired

integer · int64

≥ 0

channel

string

Which side to tune: "rx", "tx" or "both" (default).

RX AND TX ARE INDEPENDENT LOs. Measured on this bench 2026-09-12: the RX LO sat at 2.400 GHz while the TX LO was still at its 2.450 GHz default, so an analyser on TX1A at 2.4 GHz saw nothing and the API reported one "center_frequency_hz" that described neither.

tune_lo

boolean

true (default) also drives the AD9361's analogue LO. false writes only the PL's digital NCO (chan_cfg::FREQ_HZ).

The distinction is not pedantry: until today this route wrote ONLY the NCO, so /radio/status reported a centre frequency the transmitter had never been told about.

SetGainDTO#

rx_gain_db

integer · int32 · nullable

tx_gain_db

integer · int32 · nullable

StartCaptureDTO#

samplesrequired

integer · int64

≥ 0

sample_rate_hz

integer · int64 · nullable

≥ 0

center_frequency_hz

integer · int64 · nullable

≥ 0

tags

array of string

annotations

array of IqAnnotationDTO

IqAnnotationDTO · 3 fields
offset_samplesrequired

integer · int64

≥ 0

labelrequired

string

note

string · nullable

profile_ref

string · nullable

SymbolRateDRO#

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.

SystemStatusDRO#

uptime_secondsrequired

integer · int64

≥ 0

pl_driverrequired

string

mock or uio.

servicesrequired

array of ServiceStatusDRO

ServiceStatusDRO · 2 fields
namerequired

string

healthyrequired

boolean

TransmitAcceptedDRO#

packetsrequired

integer

≥ 0

useful_bytesrequired

integer

Useful bytes queued (before the le:u8/32 lane expansion).

≥ 0

wire_bytesrequired

integer

Bytes actually written to the DMA block.

≥ 0

boundaries_preservedrequired

boolean

True when packet boundaries survive the crossing. False in stream mode — stated rather than left for the caller to discover from a receiver that returns bytes at the wrong offsets.

tx_chain_running

boolean · nullable

Whether the modem's TX egress is open.

false means these bytes reach the DMA and the modem DISCARDS them — nothing goes on the air, and no other number in this reply changes. null when there is no PL driver to ask.

block_useful_bytesrequired

integer

Useful bytes that fill one DMA block.

Not a measurement of what is buffered — the assembler's fill level lives in the service and is not queried here, and reporting a guessed one would be the snr_db mistake of STE-977. It is the quantum that matters to a caller: a packet does NOT go on the air until its block is complete, so this is the latency granularity.

≥ 0

chain

ChainStateDRO · nullable

TransmitRequest#

packetsrequired

array of object

Packets to transmit, tagged by protocol: {"protocol":"raw","payload_hex":".."} for bytes forged elsewhere, {"protocol":"csp","header":{..},"payload_hex":".."} to have SatLink build the packet, likewise "ccsds".

framing

object

How packets are laid out in the byte stream.

{"mode":"stream"} packs them end to end: maximum throughput, and NO packet boundary survives — the receiver gets a byte stream cut at the PL's FRAME_LEN, which has no relation to what was pushed.

{"mode":"slot","slot_len":256,"pad":0} gives one packet per slot, so the framer's cut lands on the boundary. slot_len must equal the PL's FRAME_LEN and divide the block's useful bytes, or the request is refused.

force

boolean

Transmit even though the chain cannot carry it (STE-995).

The refusal is a guard, not a wall: probing a closed-egress branch or an unconditioned front-end is legitimate bench work, and dsl/scenarios/bringup/tx_egress_probe.yaml exists to do exactly that. It is deliberately not the default, and the reply carries the chain state either way so no report can claim the chain was healthy.

ValidationDRO#

Validation result — DRO returned by POST /profiles/validate (and POST /scenarios/validate in the scenarios module).

okrequired

boolean

errorsrequired

array of string

VersionDRO#

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).

↑↓ to moveEnter to open