Context

A full-stack app shows one place, the Sheffield Cholera Monument, at two scales at once: a satellite vegetation view from orbit and a centimeter-scale ground model. The backend is a FastAPI service that authenticates against the Copernicus Sentinel Hub, pulls the newest real Sentinel-2 scene over a bounding box, computes NDVI, and pins a satellite marker to the point in orbit where the satellite passed closest to the monument for that scene. It depends on external systems it does not control: the Copernicus APIs and a live Celestrak orbital-elements feed.

Two properties matter for this record. First, an upstream API can be slow or down. Second, the imagery, the reported acquisition date, and the satellite position must all describe the same acquisition, or the view is quietly lying.

Problem

If endpoints pass upstream failures straight through, a single upstream outage breaks the frontend globe. And if the satellite position is computed from an arbitrary timestamp rather than the scene actually shown, the imagery and the marker can silently describe different acquisitions, producing a self-inconsistent view that looks fine.

There is also a credential concern: the Copernicus secret must never reach the browser.

Decision

Endpoints are designed so the frontend always receives a shape it can render, even when an upstream API is unavailable, and so that every view is pinned to one real acquisition.

  • The newest scene is a resolved query, not a timestamp. The service finds the newest real Sentinel-2 scene over the bounding box and treats that single scene as the anchor for everything else.

  • Imagery, date, and orbit are pinned to that one scene. The satellite position is computed for the moment of the anchored acquisition, so imagery, reported date, and marker all describe the same pass. Self-consistency is defined explicitly as this agreement.

  • Endpoints cannot break the frontend. Each endpoint returns a frontend-renderable response rather than propagating an upstream failure as an unhandled error, so a degraded upstream produces a degraded but stable view, not a broken globe.

  • The secret lives server-side only. OAuth2 client-credentials are exchanged for a short-lived bearer token in the backend, set from environment variables, which is the entire reason a backend exists here rather than calling Copernicus from the browser.

  • Caching keeps it light by avoiding repeated identical upstream calls.

Alternatives Considered

Proxy upstream errors straight to the frontend

Rejected. It couples frontend stability to third-party uptime, so any upstream hiccup breaks the user-facing globe.

Compute the satellite position from the current time or an arbitrary timestamp

Rejected. It decouples the marker from the imagery being shown, producing a view whose parts describe different acquisitions while appearing consistent.

Call the Copernicus API directly from the browser

Rejected. It exposes the client secret and removes the server-side boundary that keeps it safe.

Consequences

Positive

  • An upstream outage degrades the view instead of breaking it.

  • Imagery, date, and orbital position always describe one acquisition.

  • The client secret never leaves the server.

  • Caching reduces load and latency.

Trade-offs

  • Each endpoint must define its degraded response, which is more design work than passing errors through.

  • Pinning everything to one resolved scene adds a resolution step before other calls.

  • Cached data can be slightly stale, a deliberate trade against upstream load.

Implementation Evidence

The service exposes four endpoints (latest-scene, ndvi, ndvi-stats, and orbit-at-scene-time) built with FastAPI and Uvicorn, Copernicus Sentinel Hub for imagery and metadata, Skyfield with SGP4 for orbital mechanics, a Celestrak TLE feed for live elements, and vectorized NumPy for the math.

  • Related article:

https://dev.to/dobybaxter127/building-a-fastapi-backend-that-serves-live-satellite-imagery-and-orbital-position-3dpn

Relationship to the Broader Engineering Approach

The through-line in these ADRs is refusing to let an invalid or inconsistent state reach the layer that acts on it. Here the acted-on layer is the rendered view: an inconsistent scene, or an unhandled upstream error, is prevented from reaching the frontend by making self-consistency and graceful degradation properties of the endpoint contract.

Decision Outcome

Endpoint contracts guarantee a renderable, self-consistent response regardless of upstream state:

Upstream sources → resolve one scene → pin imagery, date, orbit → always-renderable response

This ADR should be revisited if a future feature genuinely requires the frontend to distinguish a degraded response from a healthy one, in which case the degraded shape should carry an explicit status.