16 APIs
Three services sit in front of the published data:
- titiler.marinesensitivity.org — TiTiler, serving the pre-rendered Cloud-Optimized GeoTIFFs that every map layer now draws.
- api.marinesensitivity.org — a custom R
plumberAPI for programmatic (non-tile) data access. - A searchable STAC API — one Item per model, with real asset hrefs and real footprints, so a client can ask which models cover this area.
Static vector data (PMTiles) is served directly by Caddy’s file server and needs no API — see Chapter 13. The published Parquet tables can also be range-read straight from S3, including by DuckDB-WASM in a browser.
16.1 Raster tiles: COGs, not SQL per request
Score surfaces are published as one COG per metric × subregion, and per-model distributions as one COG per model. A release’s manifest carries each raster’s URL and its build-time rescale range, so an application reads the href and hands it to titiler’s stock /cog routes — no custom endpoint, no query in the tile path.
The COGs are stored without overviews, deliberately: the renderer decimates from full resolution on each request, so a pre-built pyramid would disagree with it at low zoom. Their object keys are content-addressed including the encoding, because rewriting an object at a stable URL once left /vsicurl serving a cached header for bytes that no longer existed — fine at z5+, HTTP 500 at z2–z4.
16.2 The /msens factory (retired, kept)
The custom factory below is no longer in the serving path. It is retained in the image behind MSENS_FACTORY=1 for exploratory on-the-fly queries, and is documented here because published reports reference its endpoints.
It was replaced by pre-rendered COGs after verifying the two produce the same picture: of 105 sample tiles, 100 were byte-identical and the rest differed by 3–6 pixels out of 262,144, from float32 storage. Nothing reaches it now — every metric on every release has a COG.
The factory is a FastAPI subclass of titiler.core.factory.TilerFactory, registered at the /msens prefix on top of the default TiTiler application.
Source: server/titiler/factory.py.
16.2.1 Endpoints
All /msens/* routes accept a sql query parameter: a base64url-encoded SELECT returning exactly two columns, cell_id (integer) and value (numeric). The SQL is parsed with sqlglot (SELECT-only, no write ops, no dynamic I/O like read_* / httpfs / load_*), executed against a read-only DuckDB connection, and the resulting cell_id → value map is cached in-process with an LRU keyed on the canonical SQL string. A mtime parameter can be passed for cache-busting (typically the mtime of sdm.duckdb).
| Endpoint | Purpose |
|---|---|
GET /msens/bounds |
Geographic (EPSG:4326) bounds of the cell-id COG. |
GET /msens/statistics?sql=… |
{n, min, max, mean, std, p2, p50, p98} of the SQL result — used by clients to set a stable legend rescale without fetching any tiles. Cached by Varnish. |
GET /msens/tilejson.json?sql=…&colormap=…&rescale=… |
TileJSON 2.2.0 document with the tile URL template — lets any TileJSON-aware client (QGIS, MapLibre, Mapbox GL) consume the layer. |
GET /msens/tiles/{z}/{x}/{y}.png?sql=…&colormap=…&rescale=… |
The tile endpoint. Renders a 256×256 RGBA PNG by: (a) executing the SQL and LRU-caching the cell_id → value map; (b) reading the z/x/y window of the cell-id COG with nearest-neighbor resampling; (c) looking the value up per pixel, rescaling and colorizing; (d) returning PNG with Cache-Control: public, max-age=604800. |
GET /msens/tiles/{z}/{x}/{y}.png?sql=…&color=#rrggbb[aa] |
Single-color mask variant: every valid (in-range, finite-value) pixel is painted with the given hex color; colormap and rescale are ignored. Used for binary overlays like “cells outside Program Areas”. |
GET /msens/debug/cog |
Inspect the underlying cell-id COG (bounds, CRS, dtype, nodata, overviews, rio_tiler / rasterio versions). |
GET /msens/debug/tile/{z}/{x}/{y} |
Inspect what rio_tiler.Reader.tile() returns for a given tile (cell-id range, mask valid-pixel count, top-10 cell-ids by count). |
Tile URLs were composed via msens::cell_tile_url() with stats from msens::cell_stats(); see Chapter 17.
16.2.2 Caching (v7 factory only)
Varnish fronts the factory at titilecache:6082. The custom VCL (server/varnish/titiler.vcl):
- Normalizes query-param order via
std.querysort()so semantically-identical URLs share a cache entry. - Strips
Cookie/Authorization(the factory is stateless). - Caches 2xx responses on
/msens/(tiles/|tilejson|bounds|statistics)for 7 days (grace=1h,keep=24h); errors for 60 s to prevent stampede. - Adds
X-Cache: HIT | MISSfor debugging.
When the source DuckDB regenerates, the Shiny app bumps the mtime query parameter to invalidate all cached tiles at once.
16.2.3 Derived cell-id COG
The factory looks values up against a single-band uint32 cell-id COG — one per grid, since the grid changed at v8: r_cellid.tif for the v1–v7 regional grid, r_cellid_global.tif for the v8 global one.
These are lookup images: a pixel’s value is a cell id, not a measurement. A grid’s geometry therefore cannot be inferred from the COG — use the grid registry (msens::grid_spec_for()), which is the authority on extent, size and longitude convention.
16.3 Custom plumber API
The plumber API at api.marinesensitivity.org covers non-tile use cases — CSV exports, species lookups by feature, on-demand PDF/DOCX report generation. Source: MarineSensitivity/api.
16.4 STAC
Each release publishes a SpatioTemporal Asset Catalog describing its distributions and score surfaces, with a project extension for species-distribution-model metadata. Alongside the static catalog there is a searchable STAC API with one Item per model — the static catalog is dataset-level, with assets that are S3 prefixes and a bounding box covering a whole dataset, so it can answer neither “the asset for this model” nor “which models cover this area”.
The API backend reads stac-geoparquet through DuckDB, the same shape as the rest of the stack. It is pinned to an exact upstream commit and marked experimental upstream; treat it as a trial surface rather than load-bearing.
16.5 Retired APIs
Removed from the serving path entirely — the containers are commented out in docker-compose.yml and are not running:
swagger.marinesensitivity.org— PostgREST over PostgreSQL.tile.marinesensitivity.org—pg_tileservvector tiles from PostGIS, superseded by static PMTiles served by Caddy’sfilesubdomain (see Chapter 12).
