API reference
Every scene in the console is also a plain JSON file on a CDN. There is no server, no key, no account and no rate limit, because there is nothing to authenticate against. Fetch the files, or use the Python client.
Base URL
https://www.pmuguda.com/open-sar-triad/api/v1
The catalog is rebuilt weekly from the providers' own open-data STAC records.
Responses are static files, so they cache well and work offline once fetched.
Every file carries its own license, attribution and
disclaimer fields, so the terms travel with the data.
CORS is open. You can call these endpoints directly from a browser, a notebook or a static site, with no proxy.
01 Endpoints
{provider} is one of iceye, umbra or
capella.
GET /stats.json
Counts by provider, mode, year and format, plus the temporal extent.
~2 KB. Start here to see what exists before pulling anything larger.
GET /index.json
Compact search index over every scene.
~2.5 MB. One array per scene rather than one object, to keep it small.
GET /catalog.json
STAC root catalog, linking to the three collections.
The entry point for pystac and other STAC clients.
GET /collections/{provider}.json
STAC Collection: spatial and temporal extent, licence, provider metadata.
GET /items/{provider}.json
STAC ItemCollection with every scene for that provider.
One file rather than 14,000 Items, which would churn the repository every week.
GET /scenes/{provider}.geojson
Raw GeoJSON FeatureCollection, for dropping straight into QGIS or geopandas.
Try it
curl -s https://www.pmuguda.com/open-sar-triad/api/v1/stats.json | jq '.total, .by_provider'
02 The search index
index.json stores each scene as a positional array rather than an object,
which roughly halves the file. Zip the fields list against each row:
{
"api_version": "1",
"generated": "2026-10-04T09:01:12+00:00",
"count": 14889,
"fields": ["id", "provider", "date", "mode", "orbit", "look", "pol", "landuse", "formats", "bbox"],
"scenes": [
["ICEYE_4EKGVZ_20251110T124428Z_7004635_X42_SLEDF", "iceye", "2025-11-10",
"spotlight", "descending", "right", ["VV"], ["ice"],
["GRD", "SLC", "CSI", "VID"], [-60.662, -71.0467, -60.4715, -70.9847]]
]
}
const r = await fetch('https://www.pmuguda.com/open-sar-triad/api/v1/index.json');
const { fields, scenes } = await r.json();
const rows = scenes.map(s => Object.fromEntries(fields.map((f, i) => [f, s[i]])));
// Everything Umbra imaged over Los Angeles in 2026
const hits = rows.filter(s =>
s.provider === 'umbra' &&
s.date >= '2026-01-01' &&
s.bbox[0] > -118.7 && s.bbox[2] < -117.6 &&
s.bbox[1] > 33.7 && s.bbox[3] < 34.3);
bbox is [west, south, east, north] in EPSG:4326.
mode is lower-cased here even though providers disagree on capitalisation.
pol is a list, so a dual-pol scene can be matched on either channel.
landuse is a list too, and is explained below.
03 STAC
The catalog is STAC 1.0.0. Point any STAC client at catalog.json:
import pystac
cat = pystac.Catalog.from_file(
"https://www.pmuguda.com/open-sar-triad/api/v1/catalog.json")
for collection in cat.get_children():
print(collection.id, collection.extent.temporal.intervals)
Items carry the sar, sat and view extensions:
sar:instrument_mode, sar:polarizations,
sar:resolution_range, sat:orbit_state,
sar:observation_direction and view:incidence_angle.
sar:polarizations is an array, as the spec types it.
Two fields are ours: ost:formats lists the formats a scene ships in,
and ost:first_seen records when this catalog first saw it.
Each asset key is a product format, and every one is paired with a
_metadata sibling, so SICD has SICD_metadata
next to it.
04 Python client
pip install open-sar-triad # core: no dependencies at all pip install "open-sar-triad[plot]" # adds matplotlib for the maps
from opensartriad import Catalog
cat = Catalog() # no key, no config
print(cat.stats()["total"])
scenes = cat.search(
bbox=(-118.7, 33.7, -117.6, 34.3), # Los Angeles
start="2024-01-01",
mode="spotlight",
orbit="ascending",
polarization="VV", # or ["VV", "HH"]
landuse="airport", # or ["port", "urban"]
)
print(len(scenes))
s = scenes[0]
print(s.id, s.provider, s.date, s.formats)
print(s.url("SICD"), s.metadata_url("SICD"))
The first search downloads the index once and caches it, so reuse a single
Catalog and every later search is local and instant. Filters combine
with AND, and mode is case-insensitive.
Product families
The three providers name the same product differently, so you can ask for a family
and let the client resolve it per scene. Umbra's complex product is
SICD; ICEYE's is SLC.
s.resolve("detected") # GRD | GEC | GEO | SIDD
s.resolve("complex") # SLC | SICD
s.resolve("phase") # CPHD, or None if the scene has none
s.resolve("visual") # CSI | VID
05 Land use
Every scene carries a landuse list describing
what a map says is at its location. Nothing here looks at
a pixel: the tags come from intersecting each footprint against published
geographic vectors. A scene tagged airport covers an airport;
whether anything is visible in the data is a separate question this
catalog has no opinion on.
Tags are a list because a footprint is several kilometres across and routinely spans more than one thing. A scene over Rotterdam is a port and urban and partly water, all at once.
airport port urban industrial military
Built infrastructure.
A point feature counts when it falls within its own half-extent of the
footprint — 2 km for an airport, 1 km for a port — so a city near
an airport is not tagged as one.agriculture forest
Vegetated land use.
From OpenStreetMap, so present only where that enrichment has run.mountain hilly
Measured terrain relief in a ~10 km window: 500 m and 200 m.
From AWS Terrain Tiles, public domain. Not from being inside a named
mountain range — the Andes polygon covers Santiago, which is flat ground.desert wetland ice
Land cover.water offshore
Inland water covering a third of the footprint, or sea covering
most of it.cat.search(landuse="port") # any scene over a port cat.search(landuse=["airport", "urban"]) # either one cat.search(landuse="port", providers="umbra", start="2026-01-01")
stats.json carries a by_landuse block with the
catalog-wide counts, and STAC Items carry the same list as
ost:landuse.
Infrastructure and land-cover tags come from Natural Earth
and terrain from AWS Terrain Tiles, both public domain. agriculture, forest,
industrial and military come from
OpenStreetMap, which is ODbL.
06 Downloads
Asset URLs point at the providers' own S3 buckets. This catalog never proxies or re-hosts imagery, so a download is between you and the provider.
picks = cat.search(bbox=(-118.7, 33.7, -117.6, 34.3), limit=3)
picks.download("data/", family="complex", dry_run=True) # print, fetch nothing
paths = picks.download("data/", family="complex") # actually fetch
picks.save_geojson("selection.geojson")
ic = picks.to_stac() # STAC ItemCollection
df = picks.to_dataframe() # needs pandas
Every download brings the scene's metadata sidecar alongside the data file, because a SAR product without its metadata is not much use.
07 Plotting
With the [plot] extra installed, result sets draw themselves against a
country basemap.
scenes.plot_coverage() # footprints on a world map, coloured by provider scenes.plot_timeline() # acquisitions over time, stacked by provider scenes.plot_providers() # scene counts per provider scene.plot_footprint() # one scene, with surrounding geography
Each returns a matplotlib Axes and accepts ax=, so they
compose into your own figures. A SAR footprint is thinner than a pixel on a world
view, so plot_coverage draws scenes either as geometry or as markers,
never as a mix of the two: markers merge per target and scale by how many scenes
sit behind them, and zooming in far enough that every scene is visible brings the
polygons back at true scale.
08 Licence and attribution
Scene metadata is © ICEYE, Umbra and Capella, published under their open data programmes and licensed CC-BY 4.0. This catalog is assembled from their published STAC records and is an independent third-party index, not endorsed by any of them.
If you redistribute this data or publish derived work, credit the originating provider. Every endpoint repeats these terms in its own body, so the notice travels with whatever you fetch.