Link
/link/link/chain/link/interferencePurpose
POST /link computes a link budget across every contact between a constellation
and one or more ground stations, returning per-station summaries plus the full
sample and contact detail.
POST /link/chain cascades a multi-hop relay path. POST /link/interference
screens a wanted link against an interfering constellation.
POST /link
Request
| Parameter | Type | Unit | Default | Required | Description |
|---|---|---|---|---|---|
constellation_id | string | n/a | — | Yes | Identifier of an existing constellation. |
ground_stations | array | n/a | — | Yes | Stations to analyze. Note the plural and the underscore; `stations` is silently ignored. |
start | string | RFC 3339 UTC | 2026-01-01T00:00:00Z | No | Analysis start epoch. |
duration_hours | number | h | 6 | No | Analysis window length. |
step_seconds | number | s | 60 | No | Time step. |
propagation_model | string | n/a | two_body | No | Propagation model. Underscored spelling. |
rf | object | n/a | — | No | RF configuration. Omit for a Ku-band default, or supply EVERY field. See below. |
weather | object | n/a | — | No | Legacy weather state. Defaults to clear sky. |
atmosphere | object | n/a | — | No | ITU-R atmosphere configuration. When present, replaces the legacy weather model. |
diversity_pair | array | n/a | — | No | Two station identifiers for a site-diversity gain analysis. Requires a statistical `atmosphere`. |
The rf object is all-or-nothing
rf has no per-field defaults. Omitting it entirely gives a complete,
representative Ku-band downlink. Supplying it with one field fails:
Failed to deserialize the JSON body into the target type: rf: missing field `tx_power_dbw` at line 1 column 187Worse is putting an RF field at the top level, where it belongs to no
schema and is silently discarded. This request succeeds and reports
frequency_ghz: 12.0, the default, despite asking for 20:
{ "constellation_id": "demo", "frequency_ghz": 20.0, "ground_stations": [] }Always check analysis.rf in the response against what you intended to send.
It is the only confirmation that your configuration was applied.
| Parameter | Type | Unit | Default | Required | Description |
|---|---|---|---|---|---|
frequency_ghz | number | GHz | — | Yes | Carrier frequency. |
tx_power_dbw | number | dBW | — | Yes | Transmit power. |
tx_antenna_gain_dbi | number | dBi | — | Yes | Transmit antenna gain. |
rx_antenna_gain_dbi | number | dBi | — | Yes | Receive antenna gain. |
system_noise_temp_k | number | K | — | Yes | System noise temperature. |
bandwidth_hz | number | Hz | — | Yes | Occupied bandwidth. |
data_rate_bps | number | bit/s | — | Yes | Information rate. |
required_ebno_db | number | dB | — | Yes | Demodulator threshold. |
implementation_loss_db | number | dB | — | Yes | Implementation loss. |
pointing_loss_db | number | dB | — | Yes | Antenna pointing loss. |
polarization_loss_db | number | dB | — | Yes | Polarization mismatch loss. |
min_elevation_deg | number | deg | — | Yes | Elevation mask. |
tx_antenna | object | n/a | — | No | Optional parametric transmit antenna model. |
rx_antenna | object | n/a | — | No | Optional parametric receive antenna model. |
Example request
curl -s -X POST http://127.0.0.1:8080/link \
-H 'content-type: application/json' \
-d '{
"constellation_id": "demo",
"duration_hours": 6.0,
"ground_stations": [
{ "id": "madrid", "name": "Madrid",
"latitude_deg": 40.43, "longitude_deg": -3.7, "altitude_km": 0.6 }
],
"rf": {
"frequency_ghz": 20.0, "tx_power_dbw": 5.0,
"tx_antenna_gain_dbi": 15.0, "rx_antenna_gain_dbi": 38.0,
"system_noise_temp_k": 150.0, "bandwidth_hz": 20000000.0,
"data_rate_bps": 10000000.0, "required_ebno_db": 6.0,
"implementation_loss_db": 1.5, "pointing_loss_db": 0.5,
"polarization_loss_db": 0.2, "min_elevation_deg": 10.0
}
}'Response
Top-level fields:
{
"scenario_id": "link-demo",
"run_id": "link-demo-2026-08-13T18:08:53Z-0000",
"status": "completed",
"satellite_count": 60,
"sample_count": 361,
"ground_station_count": 1,
"link_czml_url": "/api/scenarios/link-demo/czml",
"analysis": {}
}The analysis object carries start, step_seconds, duration_seconds,
ground_stations, rf, weather, samples, contacts, station_summaries,
ground_tracks, and station_footprints.
Most callers want station_summaries:
[
{
"ground_station_id": "madrid",
"coverage_percent": 100.0,
"max_simultaneous": 3,
"mean_visible": 1.6703601108033241,
"total_contacts": 92,
"best_margin_db": 12.90753382633514
}
]| Parameter | Type | Unit | Default | Required | Description |
|---|---|---|---|---|---|
coverage_percent | number | percent | — | No | Fraction of the window with at least one satellite above the mask. |
max_simultaneous | integer | count | — | No | Highest number of satellites visible at once. |
mean_visible | number | count | — | No | Average number visible across the window. |
total_contacts | integer | count | — | No | Distinct access intervals. |
best_margin_db | number | dB | — | No | Best margin achieved. This is the BEST case, at the most favorable geometry. |
best_margin_db is the best moment of the best pass. A link that shows positive best
margin can still fail for most of every pass. Use the contacts and samples arrays
for the distribution, and see link
budgets for why the worst geometry is
the one that matters.
Note that contacts and total_contacts count different things: in the run
above, station_summaries reports 92 total contacts while the contacts array
holds 35 entries, because the array is the detailed contact records retained for
the window rather than a count of every access.
POST /link/chain
Cascades a multi-hop relay path, so that a ground-to-satellite-to-satellite-to-ground route is evaluated end to end rather than as independent hops. The limiting hop governs the chain.
POST /link/interference
Screens a wanted link against an interfering constellation, reporting carrier to interference and the combined carrier to noise plus interference.
See also
orbitforge linkfor the command-line equivalent.- API overview for the ignored-field and nested-object traps.
main (pre-release)