Skip to Content
ArchitectureAPI architecture

API architecture

What this page is

The shape of the HTTP interface: 46 endpoints over an in-memory store, a three-code error model, and a set of hard limits that decide which measurements can be made here and which cannot.

State is a single in-memory store

type AppState = Arc<Mutex<Store>>; struct Store { constellations: HashMap<String, Constellation>, scenarios: HashMap<String, StoredScenario>, }

Two consequences follow directly, and both matter operationally.

State is process-local and volatile. There is no database. Constellations and scenarios live in a HashMap for the lifetime of the process.

Restarting the service discards everything. Running two replicas behind a load balancer gives each its own store, so a constellation created through one is absent from the other and requests fail with not_found.

The service is therefore a single-instance analysis backend, not a horizontally scalable one. Treating it otherwise produces failures that look intermittent and are not.

The lock is not held during computation

The obvious worry with one mutex over all state is that a long analysis blocks every other request. It does not, because handlers take the lock only to clone what they need:

let constellation = { let store = state.lock().expect("store mutex poisoned"); store .constellations .get(&req.constellation_id) .cloned() .ok_or_else(|| ApiError::not_found(/* ... */))? }; // lock released here; the expensive work happens after

Cloning a constellation costs memory proportional to the satellite count and buys concurrent analysis. For a 5000-satellite ceiling that is a sound trade.

Errors are three codes and a message

struct ApiError { status: StatusCode, code: &'static str, message: String, }

Rendered as:

{ "error": { "code": "invalid_request", "message": "..." } }

The entire vocabulary is three codes:

CodeStatusMeaning
invalid_request400The request is malformed or out of bounds
not_found404A referenced id is not in the store
internal_error500Something failed that should not have

A small, fixed code vocabulary with a descriptive message is a deliberate choice. Clients branch on code; humans read message.

The messages carry the specifics, and they are unusually good at naming the fix:

unknown propagation_model `two-body` (expected `two_body`, `j2`, `sgp4`, `numerical`, or `ephemeris`) constellation `ph1` not found; generate it first grid has 64800 points, exceeding the limit of 40000; use a coarser grid_deg or a smaller region

Each names what was wrong and what to do instead.

Validation happens before the expensive work

Handlers check bounds, build and validate the grid, and only then touch the store and run the analysis. The comment in the coverage handler states the intent plainly:

// Build and validate the grid up front so an over-fine request is rejected // before the (expensive) simulation runs.

That ordering is why an over-large coverage request fails in milliseconds rather than after a minute of work.

The limits are the interesting part

Every endpoint is bounded. These are the constants, read from the source.

LimitValueBounds
MAX_SATELLITES5000Constellation size
MAX_DURATION_HOURS168 hOne week per request
MIN_STEP_SECONDS1 sFinest propagation interval
MAX_COVERAGE_POINTS40000Grid points per coverage run
MAX_GROUND_STATIONS50Stations per request
MAX_MC_DRAWS20000Monte Carlo resilience draws
MAX_TRADE_CANDIDATES48Trade-sweep candidates
MAX_BER_POINTS25Es/N0 points per BER sweep
MAX_BER_FRAMES2000Frames per BER point
MAX_BER_TOTAL_FRAMES20000Points times frames
MAX_THRESHOLD_FRAMES12Frames per MODCOD threshold

These exist because the service is shared and a single request must not be able to consume it. That is the right instinct. But the specific values have a consequence worth stating directly.

Some measurements cannot be converged here

Compare the API’s ceilings against what BER sweeps and coding gain and measured MODCOD thresholds actually needed.

MeasurementAPI defaultAPI ceilingWhat convergence needed
BER frames per point20020001000000
MODCOD frames per point612over 200, still rising

The API caps BER at 2000 frames per point. Converging a single 1e-7 BER point took 1000000 frames, five hundred times the ceiling.

The MODCOD threshold endpoint caps frames at 12, which is below the CLI’s default of 20, and the guide showed the threshold still climbing at 200. Those are frame counts, not decibels.

The API says so itself. The rejection message is:

frames must be 1..=12 (CLI for higher confidence)

That parenthetical is the architecture stating its own division of labor:

  • The API is for exploration. Interactive, bounded, safe to expose, fast enough to sit behind a UI.
  • The CLI is for results you intend to quote. Unbounded, local, as slow as the measurement requires.

A waveform number obtained from the API is a sketch. Note also that the API defaults are lower than the CLI defaults, not merely capped lower, so a call that sets no options is further from convergence over HTTP than it is on the command line.

Reproducing a published figure means using the CLI and recording its settings.

Route layout

46 endpoints, grouped by leading path segment. The largest groups:

PrefixEndpointsPrefixEndpoints
/scenarios5/design2
/waveform4/import2
/analysis3/maneuver2
/fleet3/resilience2
/link3
/od3

The remainder are single endpoints: /simulate, /coverage, /eclipse, /isl, /econ, /trade, /replan, /transfer, /stationkeep, /conjunction, /platform, /stations, /orbits, /export, /ephemeris, /fleets, /constellations, and /health.

/scenarios is the largest group because a stored scenario is exposed in four representations, its CZML, OEM, ephemeris CSV, and contacts CSV, alongside the scenario itself. It and /fleet/snapshots/{id} are the only routes with path parameters; everything else is a POST with a JSON body.

Two prefixes cover fleets: /fleet/snapshots and /fleets/{constellation_id}/reconcile, singular and plural.

They are the same domain concept reached under two different names. Worth knowing when you are searching the route table for something you cannot find.

Two spelling conventions

The API takes two_body; the CLI takes two-body.

Each is idiomatic for its interface, JSON snake case against POSIX kebab case, and neither accepts the other’s spelling. The error message names the valid values, so the failure is loud rather than silent.

Deploying it

Given the state model, the operational shape is fixed:

  1. One instance. Do not scale horizontally without an external store.
  2. Expect cold restarts to lose state. Anything durable belongs in files the client keeps.
  3. The limits are per request, not per client. Nothing here rate-limits a caller, so put that in front if the service is exposed.
  4. No authentication is built in. The only middleware in the router is request telemetry. There is no auth layer and no CORS layer, so the service assumes a trusted network or a gateway that supplies both.

Next steps

Question? Give us feedbackDocuments Varaha Constellation Designer main (pre-release)
Last updated on