The endpoints you actually use
Eighteen operations, grouped by what you are trying to do. Every request and response below was captured from a real call, not written by hand — where a figure moves (positions, counts, SLA), the live endpoint is the authority.
The complete machine-readable spec is at /openapi.json and rendered at /redoc. That spec deliberately describes the customer surface only: the platform's own operational controls are excluded from it, so what you see there is what you can build on.
1. Authentication and limits
Two header credentials, and most read endpoints need neither.
| Header | Who | Where it comes from |
|---|---|---|
| X-API-Key | Compute buyers — routing, cost compare, managed jobs, usage. | GET /api/v1/keys/demo for a shared evaluation key, or your own from /signup. |
| X-Operator-Key | Satellite operators — federation node management. | POST /api/v1/federation/operators. Shown exactly once. |
| (none) | Every public read: SLA, satellites, ground stations, coverage, snapshot, pricing, status. | Just curl it. |
A shared, rate-limited Free-tier key for evaluation. Not for production — it is shared with every other evaluator.
{
"api_key": "or_…",
"note": "This is a shared demo key with Free tier limits (100 routing decisions/day). Use it in the X-API-Key header to evaluate; sign up at /signup for your own key."
}
Your key's tier, daily limit and consumption. Pass the key in the header — never in a query string.
{"owner": "demo-user", "tier": "free", "daily_limit": 100,
"usage_today": 0, "usage_this_week": 0, "remaining_today": 100}
Daily limits are hard caps, not metered overage. Free 100, Starter 1,000, Pro 100,000, Enterprise 1,000,000 routing decisions per day. A call over the cap is refused with 429 — it is never billed as overage, and GET /api/v1/pricing reports overage_rate_cents: 0 for every tier. There is also a per-IP burst limit in front of everything (120 requests/minute unauthenticated, far higher with a key).
2. Decide where to run
The core call. Returns a decision plus the full reasoning behind it — no execution, no side effects.
{"model_name": "llama-3-70b", "gpu_required": "H100",
"input_size_mb": 512, "estimated_compute_seconds": 30,
"job_type": "inference", "priority": "high", "max_latency_ms": 200}
confidence_basis is the long one){
"job_id": "d26e8183-da6",
"recommended_target": "terrestrial_aws",
"target_name": "terrestrial_aws",
"preference_score": 74.28,
"confidence": null,
"confidence_status": "uncalibrated",
"confidence_basis": {
"status": "uncalibrated",
"measured_base_rate": null,
"base_rate_source": "graded_jobs",
"base_rate_method": "One-sided 95% Wilson lower bound on the deadline hit rate over real graded jobs (internal synthetic load excluded), gated at n >= 50.",
"sample_size": 0,
"min_sample_size": 50,
"degradation_factors": { "element_age": {"factor": 1.0, "why": "…"}, "…": {} },
"degradation_product": 0.9
},
"estimated_latency_ms": 261.0,
"estimated_cost_usd": 0.0346,
"orbital_option": {"asset": "OrbitCloud Alpha", "score": 61.03, "latency_ms": 3316.8,
"cost_usd": 0.0995, "capacity_class": "demo",
"reliability": null, "reliability_status": "unmeasured"},
"terrestrial_option": {"provider": "terrestrial_aws", "score": 74.28,
"latency_ms": 261.0, "cost_usd": 0.0346},
"reasoning": ["Terrestrial (terrestrial_aws) selected — score 74.28", "…"],
"ground_station": "Mumbai, India",
"next_orbital_window_utc": "2026-07-30T15:05:55Z",
"timestamp": "2026-07-30T15:07:28Z"
}
Read preference_score and confidence as different things. preference_score is the engine's weighted ranking of the chosen option, 0–100. confidence is a probability that the prediction holds, and it is null until a measured base rate exists — the platform publishes no confidence it cannot measure. capacity_class tells you whether an orbital option is a demo asset, a modeled expander asset, or federated operator capacity. All of it is derived on /methodology.
Cost only, across three clouds and the modeled orbital rate, plus the price at which orbit wins for this workload. Same request schema as /route.
{
"orbital_cost_usd": 0.0995, "aws_cost_usd": 0.0346,
"gcp_cost_usd": 0.0332, "azure_cost_usd": 0.0358,
"savings_pct": -200.0, "orbital_premium_pct": 200.0,
"basis": "modeled",
"crossover": "Orbital pricing is modeled at 3.0x the cheapest terrestrial cloud rate ahead of the 2027 capacity wave — orbital reaches price parity for this workload when its $/GPU-hr crosses $3.98 (H100).",
"recommendation": "terrestrial"
}
basis: "modeled" is the whole disclosure: the three cloud numbers are published list prices, the orbital number is a modeled multiple. The same math runs client-side, with the rate card visible, on /calculator.
3. Managed jobs
Hand OrbitRoute the job instead of just the question: it queues, binds to a real pass window, dispatches, and grades itself against its own prediction. Jobs are scoped to the key that created them.
Same request as /route, plus an optional deadline_utc. If the deadline cannot be met in orbit, the job is re-homed to terrestrial cloud and remediation records why.
{"model_name": "llama-3-70b", "gpu_required": "H100", "input_size_mb": 512,
"estimated_compute_seconds": 30, "deadline_utc": "2026-08-01T18:00:00Z"}
{
"job_id": "22cbe03b-4a0", "status": "queued",
"target": "terrestrial_aws", "target_name": "terrestrial_aws", "asset_id": "",
"gpu_type": "H100", "model_name": "llama-3-70b", "priority": "medium",
"station_name": "McMurdo, Antarctica",
"scheduled_start": null, "scheduled_end": null,
"deadline_utc": "2026-08-01T18:00:00+00:00",
"predicted_latency_ms": 30261.0, "predicted_cost_usd": 0.0346,
"actual_latency_ms": null, "actual_cost_usd": null,
"submitted_at": "2026-07-30T15:08:48Z", "started_at": null, "completed_at": null,
"failure_reason": "", "remediation": ""
}
predicted_latency_ms is end-to-end — submit to complete, including pass-window wait — not link RTT.
Your jobs, newest first. ?limit= defaults to 50, max 200. Returns [] when you have none.
One JobRecord, including predicted-vs-actual once graded. 404 if it does not exist or is not yours — the two are indistinguishable on purpose.
Cancel while queued or scheduled; returns the record with status: "cancelled". 409 once it is running or terminal.
queued -> scheduled -> running -> completed
\-> failed
queued | scheduled -> cancelled
4. Public grading
The scoreboard behind /sla: predicted vs actual across every graded job, misses included, plus scheduler counters. synthetic_jobs_graded is separated from jobs_measured so internal load can never inflate the customer-facing record.
{"jobs_measured": 9, "avg_latency_error_pct": 115.24,
"deadline_hit_rate_pct": 88.9, "synthetic_jobs_graded": 19838,
"scheduler": {"ticks": 109, "jobs_scheduled": 0, "jobs_dispatched": 13,
"jobs_completed": 15, "jobs_offloaded": 0, "jobs_failed": 0,
"last_tick_utc": "2026-07-30T15:09:13Z"}}
Reading from 2026-07-30. Nine graded jobs is a small number and it is published as one — below 50 the platform calls itself uncalibrated everywhere it matters.
5. Satellites and ground segment
SGP4 positions for every tracked asset, each with its capacity_class and the age of the orbital elements it was propagated from. This is a large payload; for a compact form of the same state use /api/v1/viz/snapshot.
Upcoming ground-station passes for one asset, gated by each station's own elevation mask.
{"asset_id": "OC-ALPHA", "passes": [
{"pass_start": "2026-07-30T15:22:54Z", "pass_end": "2026-07-30T15:29:37Z",
"duration_s": 403, "max_elevation_deg": 54.4,
"station_id": "gs-dongara", "station_name": "Dongara, Western Australia",
"minutes_until": 5, "in_progress": false,
"truncated_at_window": false, "start_clipped": false}]}
in_progress and start_clipped exist because a pass already underway is a different fact from one that has not begun — the API says which.
All 37 modeled sites with position, link type, modeled bandwidth, elevation mask, and current weather-gated link recommendation.
{"count": 37, "stations": [
{"station_id": "gs-svalbard", "name": "Svalbard (SvalSat)",
"lat": 78.2307, "lon": 15.3896, "link_type": "both",
"bandwidth_gbps": 4.0, "elevation_mask_deg": 5.0,
"network": "KSAT", "source": "modeled",
"weather": {"cloud_cover_pct": 62.0, "optical_viable": false,
"rf_available": true, "recommended_link": "rf",
"source": "simulated", "source_kind": "simulated"}}]}
source: "modeled" is on every station for a reason. There are no commercial ground-station agreements. These are real geographic locations with modeled bandwidth and masks — geometry, not booked capacity. The full table is published on /methodology. The weather block carries its own source: live_openmeteo when a provider answered, simulated when it did not.What the network can actually do: contact duty cycle and worst gap for reference orbits, simulated against those exact 37 stations and their masks.
{"stations": 37, "stations_modeled": 37, "aggregate_bandwidth_gbps": 148.5,
"contact_duty_cycle": [
{"orbit_class": "leo_550_53", "label": "550 km / 53.0 deg (Starlink-like)",
"duty_cycle_pct": 44.7, "worst_gap_min": 40.5, "contact_intervals": 79,
"stations_considered": 37, "window_hours": 24, "step_seconds": 30},
{"orbit_class": "sso_550_97", "duty_cycle_pct": 58.2, "worst_gap_min": 30.5},
{"orbit_class": "leo_420_51", "duty_cycle_pct": 32.7, "worst_gap_min": 47.0}]}
A duty cycle in the forties with a worst gap around 40 minutes — the reading above, taken 2026-07-30 — is the honest shape of a 37-site network. A latitude-band count would have read better and meant less. Curl it for the current values.
The compact whole-system state behind the globe: satellites as positional arrays [lat, lon, alt_km, class], stations, modeled uplinks, and the subsolar point. Roughly an order of magnitude smaller than /satellites/positions — prefer it for bulk reads.
{"sats": [[-7.41, 93.16, 432.3, "t"], …],
"legend": {"s": "starlink", "o": "oneweb", "t": "crewed", "x": "other"},
"stations": [{"id": "gs-svalbard", "name": "Svalbard (SvalSat)", "lat": 78.2307,
"lon": 15.3896, "bandwidth_gbps": 4.0, "source": "modeled", …}],
"links": [{"name": "STARLINK-1807", "sat": [35.61, 139.44, 463.6], "si": 8,
"elev": 87.1, "type": "optical", "gbps": 10.0}, …],
"links_note": "Modeled line-of-sight uplinks, LEO only (<= 2000 km), ranked by bandwidth x sin(elevation) — usable geometry, not elevation angle alone.",
"sun": [18.397, -48.545],
"stats": {"tracking": 11690, "active_links": 7113, "pass_windows": 118499,
"fleet_size": 11692, "last_tle_refresh": "2026-07-30T14:48:59Z"}}
6. Federation (operators)
If you fly spacecraft, this is how your capacity becomes routable supply. Listing is free. Full walkthrough on /federation.
Register once. The returned or_op_ key is shown exactly once and is not recoverable.
{"name": "Acme Orbital", "contact_email": "ops@acme.example", "jurisdiction": "US"}
Advertise a node. It is directory-listed immediately; it becomes routable only when OrbitRoute can propagate its orbit (a resolvable norad_id, or TLE lines under 14 days old) and has attested that you operate that spacecraft. The 201 tells you which condition is outstanding.
{"node_id": "acme-tile-1", "norad_id": 25544, "gpu_type": "H100",
"gpu_count": 8, "power_budget_w": 12000, "price_usd_per_gpu_hr": 1.99}
Additional advertised physics — battery_fraction, thermal_margin_c, storage_gb, isl_neighbors, jurisdiction, attestation — is defined in the full reference under CapabilityAdvertisement. Fields the scorer does not yet read are stored and served back, not silently dropped.
The public directory: every advertised node with its live position, illumination and verification state. Returns [] today — no operator has registered yet, and an empty directory is published as empty.
Your own nodes, including anything not yet routable and the reason.
7. Errors
| Code | Means | Do |
|---|---|---|
| 401 | Missing or unknown API key on an endpoint that requires one. | Send X-API-Key. Check you did not paste the key into a query string. |
| 404 | Unknown job — or a job that belongs to another key. | Jobs are owner-scoped; use the key that created it. |
| 409 | Cancelling a job that is already running or terminal. | Re-read the record; the lifecycle is one-way. |
| 422 | Request body failed schema validation. | The response names the field and why. |
| 429 | Over the per-IP burst limit or over your tier's daily cap. | Back off. There is no overage billing — over the cap is refused, not charged. |
| 503 | A dependency is degraded (e.g. disk pressure, or an upstream data source down). | Retry. /status and /api/v1/health/deep say what is unhealthy. |
8. SDKs
# Python — sync + async, one httpx dependency pip install orbitroute # TypeScript / JavaScript — zero dependencies, fetch-based npm install orbitroute
Both wrap routing, cost-compare, managed jobs, SLA and federation, and raise a typed error carrying .status_code and .detail. Prefer plain HTTP? The surface above is the whole surface, and /openapi.json feeds any generator.
Where the numbers come from: /methodology. What a paid audit looks like: /sample-audit. Whether it is up right now: /status.