The site is never coupled to a third-party feed. Every value passes through the same chain, and each stage keeps what the previous one produced:
celestrak_gp, celestrak_groups, celestrak_satcat, derived_analytics). Each run is recorded with duration, record counts, a payload hash and any error; repeated failures open a circuit breaker.
raw_records before anything is parsed. Only payloads whose hash changed are processed; unchanged responses are logged as such.
satellites, an append-only orbital_elements history with orbital_state pointing at the latest epoch, field-level history and field_provenance (source, observation time, confidence) for every accepted value. Objects missing from an upstream response are never deleted.
derived_analytics (hourly) using the versioned metric definitions below.
Matching an incoming row to a canonical object follows a strict priority; the first rule that yields exactly one candidate wins:
name-norad, de-duplicated with a numeric suffix).
Anything ambiguous — a COSPAR id that maps to several objects, a name shared by several objects, or a NORAD id that appears alongside a different existing designator — is never merged automatically. It is written to manual_review_queue with both candidates and a confidence; an operator decides merge, keep separate or dismiss. Merges move aliases, identifiers and element history to the kept object and are journaled in entity_merges with a snapshot of the removed row, so they can be audited or reversed.
Positions are propagated with SGP4 (the python-sgp4 implementation of the Vallado et al. reference code, vectorised with SatrecArray) using the WGS-72 gravity constants — the model the element sets were fitted against. Inputs are the OMM element sets from CelesTrak (epoch, mean motion, eccentricity, inclination, RAAN, argument of perigee, mean anomaly, B*, first derivative of mean motion).
SGP4 yields a position vector in the TEME frame. It is rotated to Earth-fixed coordinates (ECEF) about the Z axis by the Greenwich Mean Sidereal Time of the requested instant (UT1 ≈ UTC; polar motion ignored, ≈ 10 m), then converted to geodetic latitude, longitude and altitude on the WGS84 ellipsoid by iteration. Velocity is the norm of the TEME velocity vector.
Derived geometry shown on satellite pages comes from the same element set: semi-major axis a = (μ / n²)^1/3 with μ = 398 600.4418 km³/s², perigee/apogee = a(1 ∓ e) − 6 378.137 km, period = 1440 / n minutes.
SGP4 is an analytical mean-element model: typical along-track error is of the order of a kilometre at epoch and grows by kilometres per day, faster for low, high-drag orbits and after manoeuvres. Positions shown on this site are therefore indicative: good for “where is it over the Earth right now”, not for pointing, conjunction or reentry work. Every position carries the epoch it was propagated from and its age.
Positions are computed on demand and never stored: the propagator keeps the latest element set of every object in memory and answers batch and single-object requests with a 30-second cache. Only element sets are persisted.
| SATCAT code | Meaning | Canonical status |
|---|---|---|
| {c.code} | {c.meaning} | {c.status} |
Two overrides apply after the code lookup, in this order: a decay date always produces DECAYED; debris and rocket bodies with no code are INACTIVE rather than UNKNOWN (they cannot be “operational”). Payloads present in the CelesTrak active GP group but lacking a SATCAT code are considered ACTIVE. Status changes emit decommission / activation events and are kept in the field history.
The rule is evaluated on the latest element set (or, for objects without GP data, on the SATCAT apsides with eccentricity estimated from perigee and apogee). Implementation order, as in orbital/elements.py:
{`if apogee or perigee missing → OTHER
if |period − 1436.07 min| ≤ 30 and e < 0.05 and i < 20° → GEO
if e > 0.25 and apogee > 35 000 km → HEO
if apogee < 2 000 km → LEO
if perigee ≥ 2 000 km and apogee < 35 786 + 2 000 km:
if |period − 1436.07| ≤ 60 → GEO (inclined / drifting geosynchronous)
else → MEO
if |period − 1436.07| ≤ 60 and e < 0.1 → GEO
else → OTHER`}
OTHER therefore collects transfer orbits, highly eccentric non-HEO objects and anything with an apogee below 2 000 km but a perigee above it — impossible by definition, so effectively it is the “does not fit” bucket. Orbit class is a derived label, versioned as metric orbit_class.
Mission type is derived, in this order of preference (services/classify.py):
rocket-body, debris → debris, stations → station;
unknown. A later run never downgrades a known mission type to unknown.
No launch feed is ingested yet. Launches are derived from international designators: the first eight characters of a COSPAR id (YYYY-NNN) identify the launch, so every object sharing that prefix is attached to one launch row. The launch date and site are the earliest date and the site reported for those objects in SATCAT; the “primary” payload is the object whose piece letter is A; payload, object and on-orbit counts are recomputed after every catalog run. Objects with no COSPAR id (analyst objects) belong to no launch and are flagged.
Two scales are in use, and both are shown rather than hidden behind an average:
The health endpoint reports the worst connector freshness as the platform’s data status.
+, and decayed objects may stay “on orbit” until the catalog catches up.