HTTP API overview
The Axum service exposes the same engine as the CLI over HTTP. It is what the web UI talks to, and it is the integration point for anything that is not a shell.
orbitforge-api
# listens on 127.0.0.1:8080 by defaultRead this before your first request
Three behaviors cause more lost time than anything else in this API, and none of them produces an error.
1. Unknown fields are ignored, not rejected. A misspelled or misplaced field is silently dropped and its default is used. The request succeeds and returns a plausible result computed from something other than what you sent.
2. The CLI and the API do not share spelling. The CLI writes --model two-body; the API field is propagation_model and its value is two_body.
Hyphens on the command line, underscores over HTTP.
3. Nested config objects are all-or-nothing. Omit rf entirely and you get
a complete default. Supply rf with one field and the request fails, because
the object has no per-field defaults.
Behavior 1 and behavior 3 interact badly. Putting frequency_ghz at the top
level instead of inside rf is ignored, so the request succeeds and quietly
uses the default 12 GHz. Putting it inside rf alone fails loudly with
rf: missing field tx_power_dbw. The failing case is the safer one.
Conventions
| Convention | Value |
|---|---|
| Base URL | http://127.0.0.1:8080 by default |
| Content type | application/json on request and response |
| Field naming | lower_snake_case |
| Enum values | lower_snake_case, for example two_body |
| Times | RFC 3339 UTC, for example 2026-01-01T00:00:00Z |
| Units | Named in the field, as in duration_hours, altitude_km, frequency_ghz |
The service is stateful
This is the most important structural fact about the API. Constellations and scenarios live in the service’s memory and are addressed by identifier.
Analysis endpoints take a constellation_id, not a constellation document. You
create the constellation once, then run many analyses against it.
State is in memory. Restarting the service loses every constellation and scenario, and
a 404 on an identifier you know you created almost always means the process
restarted or you are talking to a different instance.
Endpoint groups
| Group | Purpose |
|---|---|
/health | Liveness |
/constellations/walker | Generate a Walker constellation |
/import/tle, /import/ephemeris | Import real objects |
/design, /design/templates | Requirements-driven synthesis |
/simulate | Propagate and register a scenario |
/scenarios/{id} and its exports | Retrieve results as CZML, OEM, or CSV |
/coverage, /export/coverage/geojson | Coverage statistics and export |
/link, /link/chain, /link/interference | Link budgets |
/eclipse | Umbra and penumbra intervals |
/isl | Inter-satellite link topology |
/analysis/doppler, /elements, /footprint | Per-satellite analysis |
/platform/access | Fixed or moving platform access |
/maneuver/*, /stationkeep, /transfer/lambert | Maneuvers and transfers |
/od/* | Orbit determination |
/conjunction/screen | Close-approach screening |
/resilience/* | Reliability and availability |
/replan, /fleets/{id}/reconcile, /fleet/snapshots | Fleet operations |
/trade/sweep, /econ/estimate | Trade space and cost |
/waveform/* | Waveform generation and analysis |
/stations, /orbits/special, /ephemeris/solar-system | Reference data |
Errors
Application errors return a consistent envelope:
{
"error": {
"code": "not_found",
"message": "constellation `nope` not found; generate it first"
}
}Deserialization failures do not. They return 422 with a plain-text body:
Failed to deserialize the JSON body into the target type: missing field `ground_stations` at line 1 column 174A client that parses every error response as JSON will fail on exactly the errors a new integration hits most often. Branch on the status code, or on the response content type, before parsing.
| Status | Meaning | Body |
|---|---|---|
| 200 | Success | JSON result |
| 400 | Well-formed but cannot be honored: unknown enum value, incompatible model | JSON envelope |
| 404 | Identifier not found | JSON envelope |
| 422 | Body could not be deserialized | Plain text |
The plain-text 422 is genuinely useful once you expect it: it names the missing
field and its position, which is how the all-or-nothing behavior of nested config
objects gets diagnosed.
A minimal session
API=http://127.0.0.1:8080
curl -s $API/health
curl -s -X POST $API/constellations/walker \
-H 'content-type: application/json' \
-d '{"name":"demo","planes":6,"sats_per_plane":10,
"altitude_km":550.0,"inclination_deg":53.0,"fov_deg":45.0}'
curl -s -X POST $API/simulate \
-H 'content-type: application/json' \
-d '{"constellation_id":"demo","duration_hours":6.0,
"step_seconds":60.0,"propagation_model":"two_body"}'
curl -s $API/scenarios/scenario-demo{"status":"ok","service":"orbitforge-api","version":"0.1.0"}
{"constellation_id":"demo","name":"demo","satellite_count":60}
{"scenario_id":"scenario-demo","status":"completed","satellite_count":60,"sample_count":361,"czml_url":"/api/scenarios/scenario-demo/czml"}
{"scenario_id":"scenario-demo","name":"demo","start_time":"2026-01-01T00:00:00Z","duration_seconds":21600.0,"step_seconds":60.0,"model":"two_body","satellite_count":60,"sample_count":361}See also
- CLI overview for the same engine from a shell.
- Units and conventions.
main (pre-release)