Methodology

How the numbers are produced

This page is the derivation behind every figure OrbitRoute publishes: what is measured from real data, what is a model with stated assumptions, and what is simulated. It is written for the engineer who has to decide whether the routing output means anything.

One rule governs the page: every claim below names the public endpoint that returns it, so you can reproduce the number instead of taking it. Where a figure moves (satellite counts, graded-job counts, duty cycles), the endpoint is authoritative and the number quoted here is a dated reading.

1. Basis vocabulary 2. Orbital propagation 3. Pass prediction and geometry 4. The latency budget 5. Link availability and rate 6. Power and eclipse 7. The scoring vector 8. Preference score vs. confidence 9. The ground segment (37 sites) 10. Contact duty cycle 11. The pricing model 12. Capacity classes 13. Limits 14. Reproduce it yourself

1. Basis vocabulary

Four words carry the whole disclosure. They mean exactly this, everywhere — in API payloads, on this page, and in an audit deliverable.

TermDefinitionExample
measured Derived from data observed by the platform or fetched from a third party at request time. Reproducible from a public endpoint. Satellite position from published orbital elements; deadline hit rate from graded jobs.
modeled Computed from a stated physical or economic model with parameters that are assumptions, not observations. Directionally useful, wrong in the details. Ground-station bandwidth; the orbital price multiplier; link rate derates.
simulated Produced by a stand-in for hardware that does not exist yet. Not a measurement of anything. Job execution on an orbital node; weather when no live weather source is configured.
unmeasured The platform has no observation for this and refuses to substitute one. Fields report null plus a status string rather than a plausible number. reliability_status: "unmeasured"; confidence: null with confidence_status: "uncalibrated".

The fourth row is the load-bearing one. A platform that never says "unmeasured" is a platform that fabricates.

2. Orbital propagation

Positions come from SGP4 propagation of two-line element sets published by CelesTrak. There is no proprietary ephemeris and no orbit determination of our own: the elements are third-party inputs, and the propagator is the standard NORAD model (the sgp4 library, TEME output).

Check itGET /api/v1/satellites/positions — per-asset lat, lon, altitude_km, velocity_km_s, element_age_days, norad_id. Pull the same NORAD id's elements from CelesTrak, run SGP4 yourself at the returned timestamp, and compare. Reading 2026-07-30: 11,690 satellites tracked, per /api/v1/viz/snapshotsats.

3. Pass prediction and geometry

A contact opportunity is a window in which a satellite is above a station's elevation mask. Three details matter to anyone checking the arithmetic:

rho = sqrt((Re + h)^2 - (Re * cos(el))^2) - Re * sin(el)
link_rtt_ms = 2 * rho / 299.792458        # c in km/ms

Spherical Earth for the range term; the flattening error is under 0.3%, which is negligible next to what this replaced — a fixed 25 ms link latency applied to ranges spanning 400 km to 110,000 km.

Check itGET /api/v1/satellites/{asset_id}/passes — per pass: pass_start, pass_end, duration_s, max_elevation_deg, station_id, station_name, minutes_until, plus in_progress, truncated_at_window and start_clipped so a partial window is never presented as a whole one. GET /api/v1/ground-stations for each site's elevation_mask_deg.

4. The latency budget

estimated_latency_ms for an orbital candidate is time-to-first-result, not link round-trip time. It is the only quantity comparable with the terrestrial path, which has always included its own payload term, and it is disclosed term by term in the decision's reasoning.

latency_ms = time_to_contact_min * 60000     # wait for the next usable pass
           + link_rtt_ms                      # 2 * slant_range / c
           + 15.0                             # modeled on-node processing (ms)
           + transfer_ms                      # uplink + downlink over the pass
           + queue_wait_ms                    # live queue depth on that node

transfer_s(payload_mb, bw_gbps, derate) = (payload_mb * 8 / 1000) / (bw_gbps * derate)

Terrestrial, for comparison: 5 ms base + 0.5 ms per MB of input, plus 40 ms for a non-US region.

The hard veto

If the uplink cannot fit inside the contact window — payload transfer seconds greater than the pass duration, or a station with no usable rate at pass time — the candidate is removed, not penalised. A 100 GB job cannot route to a node whose best pass is 400 seconds long, at any score, and the veto reason is returned in the reasoning naming the payload, the required contact seconds, the available seconds, and the station. Without this term a large job flipped to orbital on a low apparent latency, which was the defect this replaced.

Check itPOST /api/v1/route and read the reasoning array. On an orbital recommendation it contains a line of the form "Latency budget N ms = X min wait for contact + Y ms light time over Z km slant range + 15 ms modeled handling + T ms to move U MB up / D MB down at B Gbps + Q ms queue (searched H h of pass geometry)". Every term is named with its value and they sum to estimated_latency_ms. Raise input_size_mb until the transfer term dominates, then past the point where the payload no longer fits the pass, and the candidate disappears with a stated veto reason instead of getting cheaper.

Optical downlinks do not work through cloud. The engine gates the link choice on weather at the specific station at pass time, and derates the achieved rate for both link type and pass geometry. Both derate tables are modeled — no measured link budget exists for these sites.

ConditionChosen linkRate derate
cloud cover < 30%optical1.0
30% – 60% cloudoptical, degraded0.5
> 60% cloud, site has RFRF fallback0.4 (dual-capable site loses its optical headroom)
RF-only site, any weatherRF1.0 (the advertised rate already is the RF rate)
> 60% cloud, optical-only siteno usable link — the station is dropped from the candidate set, not switched to RF it does not have0.0

Pass-geometry derate, applied on top: 0.5 + 0.5 × sin(el) — 1.0 at zenith, about 0.59 at a 10° pass. A low pass runs through more atmosphere at longer range for less link margin. Modeled.

Weather is fetched live per station when a weather source is configured; otherwise it is simulated, and each weather record carries its own source_kind (live or simulated) so a consumer never has to guess which one gated the decision. That flag also feeds the confidence degradation in §8.

Check itGET /api/v1/ground-stations — each station carries a nested weather object with cloud_cover_pct, recommended_link, optical_viable, optical_degraded, rf_available, and the source / source_kind pair that says whether the reading was live or simulated. GET /api/v1/weather/{station_id} for one site.

6. Power and eclipse

A node in Earth's shadow runs on battery, so its available power — and therefore its routing score on the power dimension — is not a constant. Illumination is computed from physics, not assumed:

Check itPOST /api/v1/route: when the recommended node is in shadow the reasoning array carries "Node in eclipse — battery-limited to N W", and when illumination cannot be determined from the elements it says so and scores battery-limited rather than assuming full sun. /api/v1/viz/snapshot returns the sun subsolar point and the lit/eclipsed split of the satellite particles, which is the same shadow test — the terminator and eclipse shading on /viz are drawn from it.

7. The scoring vector

Each candidate — every orbital asset and every terrestrial provider — is scored 0–100 on five dimensions, then combined as a weighted sum. These are the engineered baseline weights; they sum to 1.0.

DimensionWeightWhat the 0–100 sub-score is
latency0.25Log-scaled from 5 ms (100) to the 6 h search ceiling (0), identical curve for both paths — they were previously on different axes, which made the comparison meaningless.
cost0.30Modeled job cost against the rate card in §11; terrestrial sits at a fixed 50 baseline.
availability0.20Usable-contact quality: contact seconds against a 480 s reference, decayed by time-to-contact (45 min constant). Not a binary "has a pass".
queue_depth0.15Live queue occupancy on the node, from the real job queue.
power0.10Effective available watts under the eclipse model in §6.

Weights are adjusted by the learning loop from graded-job outcomes and clamped to [0.05, 0.50] per dimension, so no single dimension can be driven to dominate or to zero. Among orbital candidates within 3.0 points of the top orbital score, capacity class breaks the tie (committed > federated > demo > modeled), so capacity the platform can describe honestly is preferred over a marginally higher-scoring simulation of someone else's satellite. This can select a candidate up to 3.0 points below the top score and never more; when it fires, the reasoning array names both candidates and both scores (§12).

8. Preference score vs. confidence

This distinction used to be wrong, and the fix is worth stating plainly because it changes the API.

What it was. confidence was total_score / 100 — the weighted preference sum from §7 divided by 100 and given a probability's name. It moved with the cost multiplier and the solar wattage and with nothing that had anything to do with the recommendation being right: not element age, not whether weather was live or simulated, not pass resolution, not whether the target had any measured reliability at all. A response could report confidence: 0.802 one field away from an honest reliability_status: "unmeasured".

The response now separates ranking from certainty:

FieldMeaning
preference_score0–100. The weighted ranking from §7 under its correct name. This is the number that used to be confidence × 100. It is not a probability.
confidenceA calibrated probability that the decision meets its predicted deadline — or null. Never a rescaled score.
confidence_status"uncalibrated" (confidence is null) or "calibrated".
confidence_basisThe audit trail: measured base rate, sample size, method, and every modeled degradation factor with the reason it applied. Divide any factor back out and reproduce the number.

How a calibrated confidence is built

confidence = base_rate * product(degradation_factors)

base_rate  = one-sided 95% Wilson lower bound on the deadline hit rate
             over REAL graded jobs (internal synthetic load excluded),
             published only at n >= 50

The base rate is measured; every factor below is modeled. The lower bound rather than the raw hit rate means a small-but-sufficient sample cannot overclaim: at n=50 with a perfect record the bound is 0.9487, not 1.0; a 90% observed rate over 60 jobs gives 0.8181.

Degradation factorValueWhen it applies
element_age1.0 → 0.801.0 inside 2 days past epoch, decaying linearly to 0.80 at the 14-day refusal boundary. 0.90 if the pass predictor did not report an age at all — unverifiable freshness cannot score as fresh.
weather_source0.85The optical/RF gate ran on simulated weather rather than live.
pass_geometry0.70No usable contact in the searched window, so the reported latency is a horizon bound rather than a pass-derived budget.
target_reliability0.90No graded jobs for this specific target yet (reliability_status: "unmeasured").

On a terrestrial recommendation the three orbital-only factors are reported as 1.0 with "not applicable to this target", so the factor set has the same shape on every response.

Why the gate is 50 and not 10

As of 2026-07-30 the platform has 9 real graded jobs (88.9% deadline hit rate, 115.24% mean latency error), plus over 19,600 graded internal synthetic jobs that are excluded from calibration because they measure the scheduler, not customer workloads. (That synthetic count rises with every scheduler tick, so the live reading below is authoritative for it.) The learning loop starts nudging weights at 10 graded jobs; that is a reasonable floor for adjusting an internal weight and an unreasonable one for printing a probability at a customer. A single-digit hit rate carries a sampling interval tens of points wide, so publishing its point estimate would be the same defect with better arithmetic. 50 is the boundary /sla already publishes as its calibration phase, and using the same number in both places means the API and the scoreboard cannot tell different stories.

So today confidence is null on every response, with confidence_status: "uncalibrated" and a confidence_basis stating the sample size and the threshold. It will stay null until the graded-job count reaches 50.

Live, right nowreading the graded-job record from /api/v1/jobs-stats/sla

What changed for SDK users

Check itGET /api/v1/jobs-stats/slajobs_measured, deadline_hit_rate_pct, avg_latency_error_pct, synthetic_jobs_graded. No API key required. POST /api/v1/route and read preference_score, confidence, confidence_status, confidence_basis. Multiply confidence_basis.measured_base_rate by confidence_basis.degradation_product — or by the individual factors — and you get confidence back, to the 3 decimals it is published at.

9. The ground segment (37 sites)

There are no commercial ground-station agreements. All 37 sites below are modeled: real geographic locations chosen to represent a plausible global network, with modeled bandwidth and elevation masks. No capacity at any of them is contracted, reserved, or purchased. Every station in every API payload carries source: "modeled" for this reason. These are geometry figures, not a service-level commitment.

Aggregate modeled bandwidth is 148.5 Gbps across the network; the sites span all five latitude bands (arctic, north, tropical, south, antarctic), which is a statement about geographic spread and not an availability figure — for availability, see the duty cycle in §10.

SiteNetworkLatLonLinkMask°GbpsBasis
Accra, GhanaATLAS5.6037-0.187rf10.02.0modeled
GuamATLAS13.48144.8rf10.02.0modeled
Papeete, TahitiATLAS-17.5325-149.5677rf10.02.0modeled
Hermiston, OregonAWS Ground Station45.84-119.289both10.06.0modeled
Manama, BahrainAWS Ground Station26.066750.5577rf10.03.0modeled
Sydney, AustraliaAWS Ground Station-33.8688151.2093both10.04.0modeled
Darmstadt, Germany (ESOC)ESA49.87288.6251rf10.01.5modeled
Santa Maria, AzoresESA36.997-25.136rf10.02.0modeled
Ascension IslandESA/USSF-7.9467-14.4022rf10.02.5modeled
Awarua, New ZealandKSAT-46.5289168.381rf10.03.0modeled
Inuvik, CanadaKSAT68.3607-133.723both5.04.0modeled
Puertollano, SpainKSAT38.687-4.107both10.04.0modeled
Punta Arenas, ChileKSAT-53.1638-70.9171both10.03.0modeled
Svalbard (SvalSat)KSAT78.230715.3896both5.04.0modeled
Troll, AntarcticaKSAT-72.01172.535rf5.02.0modeled
Tromsø, NorwayKSAT69.649218.9553both5.05.0modeled
McMurdo, AntarcticaNASA NSN-77.8463166.6683rf5.02.0modeled
Fairbanks, AlaskaNASA NSN/ASF64.8581-147.8542rf10.02.0modeled
Hartebeesthoek, South AfricaSANSA/KSAT-25.88727.712rf10.02.5modeled
Clewiston, FloridaSSC26.753-80.944rf10.02.5modeled
Dongara, Western AustraliaSSC-29.046115.348rf10.03.0modeled
Esrange (Kiruna), SwedenSSC67.883621.0684both5.05.0modeled
MauritiusSSC-20.283357.5167rf10.03.0modeled
Santiago, ChileSSC-33.4489-70.6693optical10.06.0modeled
Maui, HawaiiUSSF AMOS20.7984-156.3319optical10.010.0modeled
Bogotá, Colombiaindependent4.711-74.0721rf10.02.0modeled
Cape Town, South Africaindependent-33.924918.4241rf10.02.5modeled
Dubai, UAEindependent25.204855.2708optical15.08.0modeled
Manaus, Brazilindependent-3.119-60.0217rf10.02.0modeled
Mumbai, Indiaindependent19.07672.8777both10.05.0modeled
Nairobi, Kenyaindependent-1.292136.8219both10.03.0modeled
Perth, Australiaindependent-31.9505115.8605optical15.010.0modeled
Reykjavik, Icelandindependent64.1466-21.9426both5.04.0modeled
Seoul, South Koreaindependent37.5665126.978both10.08.0modeled
Singaporeindependent1.3521103.8198both10.06.0modeled
Tokyo, Japanindependent35.6762139.6503optical15.010.0modeled
Wake Islandindependent19.2823166.647rf10.02.0modeled

Network names identify the real operator at each geographic site and describe the location only. They are not a claim of any relationship with those operators.

Check itGET /api/v1/ground-stations or /api/v1/viz/snapshotstations. Every field in the table above is in that payload, including source. Count the rows.

10. Contact duty cycle

The honest answer to "how often can you talk to my satellite" is a duty cycle, not a coverage percentage. contact_duty_cycle is the fraction of a 24 h window in which a reference orbit is in line of sight of at least one station above that station's own elevation mask, computed by stepping the orbit at 30 s over circular two-body reference orbits with an Earth-fixed ground track (J2 nodal regression is not modeled).

This replaced a 5-latitude-bucket "geographic coverage" figure that reported values near 100% — which measured where stations exist, not when a satellite can be reached.

Reference orbitAlt (km)Incl (°)Duty cycleWorst gapContacts / 24 h
Starlink-like550.053.044.7%40.5 min79
Sun-synchronous550.097.658.2%30.5 min77
ISS-like420.051.632.7%47.0 min72
OneWeb-like1200.087.989.7%21.5 min32
Mid-inclination1015.029.782.3%17.0 min50

Computed against the 37-station network in §9 on 2026-07-30. Every figure moves when a station is added or a mask changes, so the endpoint is authoritative — the table above is pinned to the computation by a CI test rather than maintained by hand.

The number worth internalising: a 550 km / 53° shell is reachable 44.7% of the day with a worst blackout of 40.5 minutes. Any latency figure for a job routed to such a shell is dominated by wait-for-contact, which is exactly why the latency budget in §4 leads with time_to_contact_min.

Check itGET /api/v1/ground-stations/coveragecontact_duty_cycle per reference orbit with duty_cycle_pct, worst_gap_min, contact_intervals, plus method and coverage_note. No API key required.

11. The pricing model

Terrestrial rates are a modeled public on-demand rate card, approximately March 2026. They are not quotes and they do not reflect committed-use or negotiated discounts.

GPUGCP $/GPU-hrAWS $/GPU-hrAzure $/GPU-hr
H1003.984.154.30
A1002.102.212.35
L40S1.151.221.30
Orbital price is a modeled multiple, not a measurement. Orbital $/GPU-hr = the cheapest terrestrial rate × 3.0. The multiple prices launch mass, radiation tolerance, thermal rejection and ground-segment overhead, anchored on the best public space-datacenter cost analysis available (SemiAnalysis, 2026). No commercial orbital GPU capacity is on the market — first capacity is expected 2027 — so there is no rate card to measure against and nobody has quoted this price. Terrestrial wins on price today; orbital becomes competitive only when its $/GPU-hr falls below the cheapest cloud rate. The multiplier is a single configuration value and will be replaced by a real figure the moment an operator publishes one.

The crossover for a specific workload is computed on /calculator using this same table and multiple.

Check itPOST /api/v1/cost-compare returns the per-provider rates and job costs the engine actually scored against, with the orbital figure labeled modeled.

12. Capacity classes

Every orbital asset row in the public asset and routing payloads carries a capacity_class/api/v1/satellites/positions stamps it per asset alongside a capacity_class_labels dictionary, and a routing decision's orbital_option.capacity_class reports it for the option it scored. It is a claim about how real the capacity is, ranked best to worst for routing preference:

ClassMeansCount it yourself
committedReal capacity under a signed contract.Zero. No asset carries this class.
federatedReal operator nodes self-advertised through the federation API, routable only after owner attestation of the operator./api/v1/federation/nodes
demoOrbitRoute's own validation fleet: real GPU specs propagated on real orbits from real element sets, used to exercise the engine against true orbital physics. Not contracted third-party hardware./api/v1/satellites/positions, rows with source: "core"
modeledCatalog satellites with category-estimated specs — a network-scale simulation of third-party assets./api/v1/satellites/positions, the remainder

Nothing is committed. When a contract closes, that asset's id is added to a configuration list and it flips class without a logic change — which is also why the class is a payload field rather than page copy.

13. Limits

The parts a buyer should weigh against everything above.

14. Reproduce it yourself

Every endpoint below is public and needs no API key except /api/v1/route and /api/v1/cost-compare, which take a free key from /signup.

EndpointVerifies
/api/v1/satellites/positions§2 propagation, element age, capacity class per asset.
/api/v1/satellites/{asset_id}/passes§3 pass geometry, elevation, duration.
/api/v1/ground-stations§9 the station table, masks, modeled basis — and §5 link gating per site, including whether the weather behind it was live or simulated.
/api/v1/weather/{station_id}§5 one site's conditions and recommended link.
/api/v1/ground-stations/coverage§10 contact_duty_cycle, worst gaps, method.
/api/v1/jobs-stats/sla§8 the graded-job record the confidence gate reads.
/api/v1/route§4 the latency budget term by term, §7 the scores, §8 preference_score / confidence_status / confidence_basis.
/api/v1/cost-compare§11 the rate card and the modeled orbital figure.
/api/v1/viz/snapshotSatellite count, station list, modeled line-of-sight geometry.
curl https://www.orbitroute.ai/api/v1/ground-stations/coverage
curl https://www.orbitroute.ai/api/v1/jobs-stats/sla

If a number on this page disagrees with the endpoint next to it, the endpoint is right and the page is stale — send it to the contact route on /about and it gets corrected.