Week 29 · Space GIS Architect~7 min · 685 words

Geospatial APIs: PostGIS + FastAPI + spatial REST

Most geospatial work ends with someone wanting a JSON API. The notebook that detected the plume, the model that classified the reef, the pipeline that tracked the ship — none of it matters until other people can query it. This week you build that endpoint properly: FastAPI in front, PostGIS behind, auth and rate limits at the door, OGC API patterns on the wire.

If you built a tool that detects red-tide blooms along Hawaiian coastlines, how would you let other people use your tool?

Through an API. This week you'll learn the patterns — same patterns the State Climate Data Portal uses, same patterns NOAA uses. By the end, your work can serve other people's work.

Learning objectives

PacIOOS at UH Mānoa publishes Pacific oceanographic data via open APIs. Reciprocity in practice: take, give back.

Primer

The endpoint where space GIS meets the rest of the world is almost always a REST API. Someone downstream — another team, a paying customer, a frontend app — wants geospatial data over HTTP, with predictable URLs, ergonomic parameters, and JSON responses. This week you build that endpoint properly.

The shape of a spatial REST endpoint

The four query patterns that show up everywhere:

  1. By ID — single feature by its identifier.
  2. By bounding box — features inside a lon_min, lat_min, lon_max, lat_max box. Useful for map tiles.
  3. By point and radius — features within radius_km of a lat, lon. Useful for near-me queries.
  4. By polygon — features inside an arbitrary GeoJSON polygon. POST it as the request body.

Common parameters across all of them: time range, pagination, attribute filtering, sorting.

FastAPI plus PostGIS

The minimal stack:

from fastapi import FastAPI, Query
from sqlalchemy import create_engine, text

engine = create_engine("postgresql://...")
app = FastAPI(title="LaunchDetect API")

@app.get("/detections")
def detections(bbox: str | None = None, limit: int = 50):
    if bbox:
        lon_min, lat_min, lon_max, lat_max = map(float, bbox.split(","))
        sql = text("SELECT id, ST_AsGeoJSON(position) AS geom FROM detections "
                   "WHERE position && ST_MakeEnvelope(:l1,:l2,:l3,:l4,4326) "
                   "ORDER BY detected_at DESC LIMIT :lim")
        with engine.begin() as conn:
            rows = conn.execute(sql, dict(l1=lon_min,l2=lat_min,l3=lon_max,l4=lat_max,lim=limit)).all()
        return rows

OpenAPI for free

FastAPI auto-generates a full OpenAPI 3.0 specification from your function signatures and Pydantic models. Visit /docs for interactive Swagger UI, /redoc for ReDoc. Other teams can generate client SDKs in any language from the OpenAPI spec.

Pagination

Spatial endpoints often return many features. Two approaches:

  • Offset pagination — easy to implement but degrades with large offsets and breaks if features are inserted or deleted between pages.
  • Cursor pagination — a cursor parameter encodes the last item seen. Stable under writes, scales to any depth. Use the Link header per RFC 5988.

For high-traffic endpoints, cursor is the right default.

Caching

Spatial responses cache well. Set Cache-Control: public, max-age=60 on bbox queries. Use a CDN in front. For Lambda-fronted APIs (the architecture you built in Week 27), CDN caching can drop your origin load by 95 percent or more — and your cost with it. Don't cache user-scoped or rate-limited responses; vary by Authorization header if needed.

Auth, rate limits, and abuse

Public geospatial APIs get hammered the moment they're discovered. Three layers of defense, in order:

  • API keys — minimum bar. Issue per-user keys; clients send them in an X-Api-Key header. Pair with usage metering so you know who's calling what.
  • Rate limiting — fixed-window or token-bucket. Public unauthenticated: 60 req/min. Authenticated: 600 req/min. Paid tier: higher. Return 429 Too Many Requests with Retry-After and X-RateLimit-* headers so well-behaved clients back off.
  • OAuth2 / scopes — for partner integrations and anything writeable. Use bearer tokens with scope claims (read:detections, read:tracks); validate at the edge.

CORS and response design

If a browser frontend will call your API directly, you need CORS. Set Access-Control-Allow-Origin precisely (a whitelist, not *, if you ever return auth-gated data). For public read-only endpoints, * is fine.

Design the response shape with the consumer in mind. Always return a GeoJSON FeatureCollection (not a raw array of features) so clients can extend with metadata: a top-level numberMatched + numberReturned field, a links array for pagination cursors, and per-feature properties with typed values (ISO 8601 timestamps, not Unix ints; integer IDs, not strings).

OGC API — Features (the standard)

Don't reinvent the URL scheme. The OGC API — Features standard (the successor to WFS) formalizes the four patterns above into a stable contract: GET /collections, GET /collections/{id}, GET /collections/{id}/items?bbox=...&datetime=..., GET /collections/{id}/items/{featureId}. Stable URLs, predictable parameters, a conformance document so clients know what's supported. Adopting it for free gets you compatibility with QGIS, pygeoapi, and every other OGC-aware tool. STAC (SpatioTemporal Asset Catalog) is built on top of OGC API — Features for satellite imagery specifically.

Versioning

Public APIs are contracts. Use /v1/ in the path; reserve /v2/ for breaking changes. Add a Deprecation header on endpoints scheduled for removal so clients see it coming. Support the previous version for at least six months after deprecation.

One more responsibility

An API broadcasts your data to whoever finds it. Before you flip the switch on a public endpoint, re-read Week 28. Sub-meter coordinates, real-time launch detections, vessel tracks — these can carry export-control implications (ITAR) or privacy harm even when individual records seem innocuous. The right answer is sometimes "this endpoint authenticates" or "this endpoint exists only inside the partner VPC."

The lab

You'll build a FastAPI app that wraps a PostGIS detections table, exposes the four endpoint patterns above with API-key auth and per-key rate limits, returns OGC API — Features compliant GeoJSON, and ships with auto-generated OpenAPI docs at /docs. This is the same architecture in production at launchdetect.com/space-data-api/.

Connecting to Hawaiʻi: Hawaiian data APIs and reciprocity

The Pacific Islands Ocean Observing System (PacIOOS) at the University of Hawaiʻi publishes real-time Pacific oceanographic data via APIs — wave height, sea surface temperature, currents, ocean acidification — all free, all open, all available to any developer. The Department of Land and Natural Resources has been steadily moving its datasets into accessible APIs. When you build an API for your own geospatial work, you're joining this network. Knowledge flows both ways: you use their data, your tools can give back.

PacIOOS at pacioos.hawaii.edu has dozens of free APIs. Try one. Then build something that uses it.

Hands-on lab: Spatial REST API for launch detections

Build a FastAPI app with bbox / radius / id endpoints over a PostGIS detections table. Returns GeoJSON.

Quiz — click an answer to check it

No grade, no shame. Tap any option; you'll see if it's right plus the answer if not. The point is to notice what you already know and what's still settling.

Q1. FastAPI generates what for free?
  1. OpenAPI spec and interactive Swagger UI at /docs
  2. Database migration
  3. Frontend
  4. Hosting
Q2. A bbox query in PostGIS uses:
  1. ST_MakeEnvelope to build the bbox geometry, then ST_Intersects or && operator
  2. Just SQL LIKE
  3. JSON parsing
  4. Random sampling
Q3. GeoJSON FeatureCollection structure is:
  1. A FeatureCollection containing an array of Feature objects
  2. Just a list of coordinates
  3. A binary blob
  4. XML
Q4. Pagination for spatial endpoints typically uses:
  1. Cursor-based with cursor parameter or HTTP Link header
  2. Offset only
  3. No pagination
  4. Random
Q5. Returning EWKB vs GeoJSON tradeoff:
  1. GeoJSON is human-readable and web-friendly; EWKB is more compact
  2. EWKB is always better
  3. Same thing
  4. Neither matters

Reflection

Take five minutes with this. Write your answer somewhere. Carry it into next week.

An API turns your work into a building block other people can use. What's something you'd want to expose as an API — and what's something you'd hold back?
Mark this week complete Visiting alone doesn't count it as 'done'. Click when you've actually worked through the primer + lab + quiz.
Share + discuss on Twitter/X Discuss on GitHub