Search with pystac-client and curl
The catalog routes follow STAC API 1.0, so standard STAC tools work. Search needs a key in lean v1.
POST search
Section titled “POST search”POST is the better form for anything beyond a simple query: long geometries, many ids and the cloud filter.
curl -s -X POST https://api.satellitedata.net/v1/search \ -H "X-API-Key: $SDN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "collections": ["sentinel-2-l2a", "landsat-ot-l2"], "bbox": [-93.3, 44.9, -93.2, 45.0], "datetime": "2026-06-01T00:00:00Z/2026-08-31T23:59:59Z", "limit": 10, "query": {"eo:cloud_cover": {"lte": 40}} }'collectionstakes canonical ids or aliases, sosentinel-2-c1-l2aandlandsat-c2-l2work too.bboxis west, south, east, north in longitude and latitude.datetimeis an RFC 3339 instant or interval. An open end is two dots:2026-06-01T00:00:00Z/...intersectstakes a GeoJSON geometry instead ofbbox(not both).queryis the STAC query extension. Operators includelt,lte,gt,gte,eqandin.sortbyandfieldswork too, for example"sortby": [{"field": "properties.eo:cloud_cover", "direction": "asc"}].
GET search
Section titled “GET search”curl -s "https://api.satellitedata.net/v1/search?collections=sentinel-2-l2a&bbox=-93.3,44.9,-93.2,45.0&datetime=2026-07-01T00:00:00Z/2026-07-31T23:59:59Z&limit=5&sortby=properties.eo:cloud_cover" \ -H "X-API-Key: $SDN_API_KEY"On GET, query and intersects are JSON strings (URL-encode them), and sortby takes a - prefix for descending order.
Items of one collection
Section titled “Items of one collection”curl -s "https://api.satellitedata.net/v1/collections/landsat-ot-l2/items?bbox=-93.3,44.9,-93.2,45.0&limit=5" \ -H "X-API-Key: $SDN_API_KEY"One item
Section titled “One item”A single item is anonymous:
curl -s https://api.satellitedata.net/v1/collections/sentinel-2-l2a/items/S2C_T15TVK_20260712T171233_L2Apystac-client
Section titled “pystac-client”pystac-client opens the landing page, reads the conformance classes and follows paging links for you. Pass the key as a header:
import osfrom pystac_client import Client
client = Client.open( "https://api.satellitedata.net/v1", headers={"X-API-Key": os.environ["SDN_API_KEY"]},)
search = client.search( collections=["sentinel-2-l2a", "landsat-ot-l2"], bbox=[-93.3, 44.9, -93.2, 45.0], datetime="2026-06-01/2026-08-31", query={"eo:cloud_cover": {"lte": 40}}, max_items=50,)
for item in search.items(): print(item.id, item.collection_id, item.properties.get("eo:cloud_cover"))pystac-client searches with POST by default. Use query=, not filter=: lean v1 does not list the filter extension in its conformance classes, and a CQL2 filter is rejected with invalid-filter.
What comes back
Section titled “What comes back”A search answers a GeoJSON FeatureCollection. Each feature is a STAC item:
id: the native id (see Collections and ids).collection: the canonical collection id.properties:datetime,platform(such assentinel-2corlandsat-8),eo:cloud_cover, and mission properties such ass2:processing_baseline,s2:mgrs_tile,landsat:wrs_pathandlandsat:wrs_row.assets: links to the upstream Cloud-Optimized GeoTIFFs.links: rewritten to point atapi.satellitedata.net/v1.
Asset links point at the public upstream files. Sentinel-2 files are free to read over HTTPS. Landsat files are in the requester-pays usgs-landsat S3 bucket, so reading them directly costs you the S3 request charges. To avoid that, let the API read the pixels with tiles or process.
Paging
Section titled “Paging”The answer has a next link when more results exist. For POST search the next link has method: POST, a body holding the token, and merge: true, which tells you to merge that body into your original request; pystac-client does this automatically. Follow next until it is absent.
Coming later
Section titled “Coming later”- CQL2 filters (
filterin CQL2 text on GET and CQL2 JSON on POST), withGET /v1/collections/{collectionId}/queryableslisting the properties you can filter on. - The
optical-srcollection, which searches every optical sensor at once and removes duplicate overpasses. - Anonymous search under per-IP limits.