A pass is a window of visibility of a target over a ground station, between its acquisition of
signal (AOS) and its loss of signal (LOS). Passes come from two sources: the flight dynamics
system (FDS), which predicts them, and the ground station provider, which books them. They are
not part of the configuration: they are operational state that changes often, stored in the
stellar_passes KV bucket under <target>.<id>.
The MCS never fetches passes. Third-party scripts — feeders — push them through the API, at their own pace, and the MCS merges the two sources into one pass. Passes then carry scheduled runs, bound the runs launched during them, give the expected rates to the file transfers, and are checked against the gateways before their AOS.
The pass model#
passes:
- id: gs-a-2026-10-02T1014Z-sat1
target: sat1-fm
station: gs-a
aos: 2026-10-02T10:14:32Z
los: 2026-10-02T10:24:05Z
max_elevation: 47.3 deg
uplink: true
transceiver: sband
protocol: ccsds
rates: {uplink: 64 kbps, downlink: 2 Mbps}
source: provider
status: booked| Field | Required | Meaning |
|---|---|---|
id | yes | Stable identifier, given by the feeder |
target | yes | Target, as named in the topology |
station | yes | Ground station |
aos | yes | Acquisition of signal, UTC (RFC 3339) |
los | yes | Loss of signal, UTC |
max_elevation | yes | Maximum elevation, an angle (47.3 deg) |
source | yes | fds (predictions) or provider (bookings) |
status | yes | predicted, booked or cancelled |
uplink | no | Whether telecommands can be sent; true by default |
transceiver | no | On-board transceiver: sband, xband… |
protocol | no | Protocol: ccsds, csp, dvb-s2… |
rates | no | Expected rates, {uplink, downlink}, as bit rates (64 kbps, 2 Mbps) |
The MCS adds three fields, which a feeder does not write:
| Field | Meaning |
|---|---|
feeder | Identity of the feeder that wrote the last contribution |
updated_at | Time of the last contribution |
contributions | For a merged pass: the pass as each source last wrote it |
Validation#
A pass is refused with 422 pass::invalid when:
id,targetorstationis not a token (letters, digits,-and_);aosis not beforelos;max_elevationis not an angle between 0 and 90 degrees;- a rate is not a positive bit rate;
- an
fdspass isbooked: only the provider books a pass.
A pass whose target is not in the current configuration is refused with
422 pass::unknown-target. The environment of a pass is not written: it follows from its target.
Status#
| Status | Meaning |
|---|---|
predicted | Predicted by the flight dynamics system |
booked | Booking confirmed by the ground station provider |
cancelled | Cancelled by its source |
In in_orbit, only a booked pass carries a run: a run bound to a pass that is not booked is
refused (409 run::not-booked), and a schedule on it is at_risk.
Receive-only passes#
A pass with uplink: false allows no telecommand. A run that sends telecommands is refused on it
(409 run::receive-only), and a schedule of such a run is at_risk.
Feeders#
Each feeder script has its own identity and writes the passes of one source only. The feeders are declared in the global configuration:
passes:
feeders: {fds-feeder: fds, station-feeder: provider}The identity of a feeder is the subject of its service token (Authorization: Bearer), or, where
no identity provider is used, its declared identity (X-Stellar-User, --as in the CLI).
| Refusal | Code |
|---|---|
The caller is not in passes.feeders | 403 pass::not-a-feeder |
| The pass, or the snapshot, is of another source than the feeder's | 403 pass::forbidden-source |
| A token is refused | 401 auth::invalid-token |
Writing a pass: upsert#
POST /v1/passes writes one pass, new or refined, and answers the pass as stored (merged with
the other source when there is one). The upsert is idempotent by identifier: a refined prediction
replaces the previous one.
curl -s -X POST http://localhost:8080/v1/passes \
-H 'Content-Type: application/json' -H 'X-Stellar-User: fds-feeder' \
-d '{"id": "gs-a-20261002T1014-sat1", "target": "sat1-fm", "station": "gs-a",
"aos": "2026-10-02T10:14:32Z", "los": "2026-10-02T10:24:05Z",
"max_elevation": "47.3 deg", "source": "fds", "status": "predicted"}'Writing a snapshot: replace_window#
A script that exports its whole planning sends it as a snapshot: every pass of its source for one
target over an interval. POST /v1/passes/replace_window writes each pass of the snapshot, then
cancels the contribution of the source to every pass of the interval that is absent from it. A
pass removed from the planning is thus cancelled.
{
"source": "fds",
"target": "sat1-fm",
"from": "2026-10-02T00:00:00Z",
"to": "2026-10-03T00:00:00Z",
"passes": [ … ]
}The reply lists what was written and what was cancelled, as stored:
{"upserted": ["gs-a-20261002T1014-sat1"], "cancelled": ["gs-b-20261002T1151-sat1"]}Every pass of the snapshot must be a pass of the target that overlaps the interval, and from
must precede to; otherwise the whole request is refused (422 pass::invalid). A pass still
booked by the provider stays booked when the FDS drops its prediction: only the contribution of
the source is cancelled.
Merging the two sources#
A prediction of the FDS and a booking of the provider for the same pass are merged into one pass. A submitted pass is matched, in this order, with:
- the stored pass with the same identifier;
- the stored pass one of whose contributions has this identifier, from the same source;
- a stored pass of the same target and the same station whose window overlaps it.
Without a match, it is a new pass. A pass keeps the identifier under which it was first stored; schedules and run requests may name either that identifier or the identifier of any of its contributions.
The merged pass takes from the booking (the provider contribution) its window (aos, los),
its elevation, its status and uplink. The transceiver, the protocol and the rates come from the
booking, else from the prediction. feeder and updated_at are those of the latest
contribution. contributions keeps the latest version written by each source.
flowchart LR
F["FDS feeder<br/>predicted, aos 10:14:32"] --> M{"same target,<br/>station, overlapping"}
P["Provider feeder<br/>booked, aos 10:14:40"] --> M
M --> S["Stored pass<br/>window and status of the booking<br/>contributions: fds + provider"]Writes are conditional on the revision of the stored pass: two feeders writing at the same time never overwrite each other, the loser merges again.
Gateway of a pass#
During a pass, the gateway of the default link of the target is the one attached to the station.
A gateway serves a station when it carries the name of the station as a tag, in addition to the
tags the link requires: for gateway: "any(tag: sband)", a pass over gs-a needs a healthy
gateway registered with the tags sband and gs-a. A link to a named gateway (a bench) ignores
the station.
At every reconciliation, the leader of the reconciler checks every pass that is not cancelled and
starts within 10 minutes or is in progress. The result goes to the stellar_pass_gateways bucket
under <target>.<pass>, rewritten only when it changes:
{"gateway": "gs-a-sband-1", "checked_at": "2026-10-02T10:05:00Z"}
{"reason": "no healthy gateway registered with the tags `sband` and `gs-a`", "checked_at": "…"}A missing gateway is also logged as a warning by the reconciler. The API and the CLI show the check next to each pass.
Listing passes#
stellar passes # passes not over yet
stellar passes --target sat1-fm
stellar passes --all # passes over tooOne line per pass: target, identifier, station, window, elevation, status (with
receive-only), the checked gateway or why there is none, the feeder and the time of the last
update.
sat1-fm gs-a-20261002T1014-sat1 gs-a 2026-10-02T10:14:40Z → 2026-10-02T10:23:50Z 46.9 deg booked gateway gs-a-sband-1 by station-feeder at 2026-10-01T18:02:11ZThe API gives the same: GET /v1/passes (?target=, ?all=true) and
GET /v1/passes/{target}/{id}, each pass with its gateway check. A pass is over once its LOS is
past.
Importing passes from a file#
stellar passes import <file> writes passes as the feeder named by --as (or identified by
--token); the options of stellar passes come before import. The file holds a list of passes in the model above:
passes:
- id: gs-a-20261002T1014-sat1
target: sat1-fm
station: gs-a
aos: 2026-10-02T10:14:32Z
los: 2026-10-02T10:24:05Z
max_elevation: 47.3 deg
source: fds
status: predictedEach pass is upserted in turn, and printed as stored. With a replace_window header, the file is
a snapshot, sent as one replace_window request:
replace_window:
source: fds
target: sat1-fm
from: 2026-10-02T00:00:00Z
to: 2026-10-03T00:00:00Z
passes:
- id: gs-a-20261002T1014-sat1
target: sat1-fm
station: gs-a
aos: 2026-10-02T10:14:32Z
los: 2026-10-02T10:24:05Z
max_elevation: 47.3 deg
source: fds
status: predictedstellar passes --as fds-feeder import planning.yaml
# 1 written, 1 cancelled: gs-b-20261002T1151-sat1A feeder in Python#
The Python SDK offers PassFeeder. The example examples/python/pass_feeder.py reads a CSV
planning and sends it as a snapshot per target:
cd examples/python
uv run pass_feeder.py planning.csv # identity fds-feeder, source fds
uv run pass_feeder.py bookings.csv --identity station-feeder --source provider
uv run pass_feeder.py planning.csv --watch 60 # pushes again when the file changes
uv run pass_feeder.py --demo sat1-fm # invented passes of the next 24 h
stellar passes --target sat1-fmid,target,station,aos,los,max_elevation,uplink,status
gs-a-20261002T1014-sat1,sat1-fm,gs-a,2026-10-02T10:14:32Z,2026-10-02T10:24:05Z,47.3,true,
gs-b-20261002T1151-sat1,sat1-fm,gs-b,2026-10-02T11:51:10Z,2026-10-02T11:58:40Z,18.6,false,cancelledThe booking ksat-7781 of bookings.csv overlaps the first prediction on the same station: the
MCS merges them, and the merged pass takes the window and status of the booking. See
Writing a Pass Feeder.
When a pass changes#
Every change of a pass is seen by the scheduler, which evaluates again the schedules bound to it:
a schedule whose run no longer fits becomes at_risk, a schedule on a cancelled pass is
cancelled, and the validation of a supervisor falls when the window of the pass changes. See
Scheduling.
See also#
- Scheduling: runs bound to passes.
- API reference: passes.