The STAC API provided by eoAPI is stac-fastapi-pgstac: a stac-fastapi application with a pgstac backend. stac-fastapi-pgstac translates STAC API requests into pgstac queries and returns the results to the requester.
The stac-fastapi-pgstac STAC API can be accessed using any HTTP client but STAC API clients like pystac-client provide a more intuitive interface. In this tutorial you will learn how to use HTTP requests via httpx as well as pystac-client methods.
3.1 stac-fastapi-pgstac structure¶
A standard eoAPI deployment will run an unmodified version of the FastAPI application defined in stac_fastapi.pgstac.app:app (source). Unless otherwise specified, all of the extensions except the transaction and bulk-transaction extensions will be enabled but be sure to double check this in your own deployment.
stac-fastapi-pgstac implements a pgstac client that is capable of serving the routes defined by stac-fastapi’s base StacApi factory class (source). The pgstac client’s methods contain the logic for translating API requests into pgstac database queries.
For example, a search request for items in the “amazing” collection where the item bounding box intersects (0, 0, 10, 10) would get converted to a PostgreSQL query like this pseudo-sql:
SELECT * FROM items
WHERE
collection = 'amazing' AND
ST_Intersects(bbox, ST_MakeEnvelope(0, 0, 10, 10));stac-fastapi-pgstac transforms the search results into the format expected in the API response and return it to the user. If you want to see how the actual SQL queries look in pgstac, check out the pgstac source code.
3.1.1 Customization¶
There are several options in the default stac-fastapi-pgstac application that are configurable at run time via environment variables (using pydantic’s settings features):
the
ENABLED_EXTENSIONSenvironment variable controls which extensions are enabledpgstacdatabase credentials are set byPOSTGRES_*environment variables (source)take a look at stac
_fastapi /pgstac /config .py for the settings module.
Any other modifications to the default application will require a custom runtime in your eoAPI deployment. If you do this you will need to provide the full custom runtime (application code and handler) via a Dockerfile. Check out eoapi-devseed for an example of building custom runtimes for eoAPI services.
3.1.2 Authentication¶
stac-fastapi-pgstac does not contain any authentication mechanism out-of-the-box, meaning your STAC API will be accessible to anyone if it is deployed to a public web address. If you want to make your STAC API accessible only with a username/password or token, check out the FastAPI docs for examples of how to add them to the application in a custom runtime.
There is a new project called stac-auth-proxy that can provide fine-grained access controls to a STAC API by adding a proxy layer between users and the actual STAC API. In this workshop stack, the STAC API is exposed through stac-auth-proxy at http://localhost:8084.
3.1.3 STAC API interface¶
Once your STAC API is up and running, its capabilities will be described in the /conformance endpoint response:
import json
import os
import httpx
stac_api_endpoint = os.getenv("STAC_API_ENDPOINT")
conformance_response = httpx.get(f"{stac_api_endpoint}/conformance").json()
print(stac_api_endpoint)
print(json.dumps(conformance_response, indent=2))The result is hard (for a human) to read, but these conformance classes help client applications (like pystac-client or STAC Browser) understand the API’s capabilities. The list will change as you enable/disable various extensions or endpoints.
3.2 Collections¶
The /collections endpoint is useful for finding collections in the catalog. To retrieve all collections in the catalog you can simply send a GET request to the /collections endpoint. This will return a paginated list (length of each page is set by the limit parameter) of all of the collections in the catalog.
collections_response = httpx.get(
f"{stac_api_endpoint}/collections", params={"limit": 2}
).json()
print(json.dumps(collections_response, indent=2))3.2.1 All Collections¶
You can retrieve all of a catalog’s collection using the get_all_collections method from pystac-client:
import pystac_client
client = pystac_client.Client.open(stac_api_endpoint)
collections = list(client.get_all_collections())
for collection in collections:
print(collection.id)3.2.2 Collection Search Query¶
Some APIs contain many many collections so, if the collection-search extension is enabled, it can be helpful to apply filters using the available query parameters like:
q: free-text search parameterdatetime: temporal filtersbbox: spatial filtersfilter: cql2-text filters
To check if any STAC API has the collection-search extension enabled, you can look for it in the /conformance endpoint response.
for conformance_class in conformance_response["conformsTo"]:
if "collection-search" in conformance_class:
print(conformance_class)Since the collection-search base conformance class is listed that means we can pass the bbox and datetime parameters to the /collections endpoint. Additional parameters are unlocked by the various extensions that are implemented alongside the collection-search extension. For example, you can also see https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter which means we can use the filter parameter in requests to the /collections endpoint!
For a nice view of the available query parameters for the /collections endpoint, check out the spiffy API documentation that the stac-fastapi-pgstac application generates using FastAPI.
from IPython.display import IFrame
local_stac_api_endpoint = os.getenv(
"STAC_API_BROWSER_URL"
) or stac_api_endpoint.replace("stac-auth-proxy:8000", "localhost:8084")
api_docs = (
f"{local_stac_api_endpoint}/api.html#/default/Get_Collections_collections_get"
)
print(api_docs)
IFrame(
api_docs,
1200,
800,
)Try applying the filter parameter to do a cql2-text query on the id field to find the collection you created in the database exercies.
If you didn’t run Part 2 on Databases, you can either go back to make a username or you can copy one from section 3.2.1 collection ids.
import ipywidgets as widgets
from IPython.display import display
username_input = widgets.Text(
value=None,
placeholder="Enter your username",
description="username:",
disabled=False,
)
display(username_input)# using pystac-client
my_collection_search = client.collection_search(
filter=f"id LIKE '%{username_input.value}%'"
)
results = my_collection_search.collection_list()
if results:
my_collection = results[0]
display(my_collection)# using http client
print(
json.dumps(
httpx.get(
f"{stac_api_endpoint}/collections",
params={"filter": f"id LIKE '%{username_input.value}%'"},
).json(),
indent=2,
)
)Now that you found your collection, you have what you need to do an effective item search within your collection!
3.3 Items¶
Once you have the collection ID there are several ways to perform an effective item search:
GET request to
/collections/{collection_id}/itemsGET or POST request to
/search
There are not any particular advantages to either approach unless you want to search for items using an intersection with a geometry in which case you should use a POST request to /search with the intersects parameter in the request body (instead of url-encoding a geojson!).
Item search request responses will be returned in pages with {limit} results. If your search returns more than a single page of results, the next page will be retrievable via the next link in the list of links.
3.3.1 Item Search¶
Use the /search endpoint to find all items in your collection with a timestamp after April 4, 2025
from datetime import datetime, UTC
search = client.search(
collections=[my_collection.id],
datetime=[datetime(2025, 1, 4), None],
)
items = search.item_collection()
print(f"found {len(items)} items")
items[0]The same query can be made with an HTTP client:
datetime_string = datetime(2025, 1, 4, tzinfo=UTC).isoformat()
item_search_request = httpx.get(
f"{stac_api_endpoint}/search",
params={
"collections": my_collection.id,
"datetime": f"{datetime_string}/..", # open interval from 2025-04-04 forward
"limit": 1, # one result per page for brevity in this example
},
)
print(json.dumps(item_search_request.json(), indent=2))stac-fastapi-pgstac constructs the next link using a token that it can pass to a pgstac query to retrieve the next page of results from this search. STAC API clients like pystac-client use these links to concatenate paginated results without any additional input from the user.
Now limit the search to items where eo:cloud_cover is less than 10
search = client.search(
collections=[my_collection.id],
filter={
"op": "lt",
"args": [
{"property": "eo:cloud_cover"},
10,
],
},
max_items=10,
)
items = search.item_collection()
print(f"found {len(items)} items")
items[-1]3.3.2 All Items¶
The API /collections/{collection_id}/items endpoint will get you all items in a collection.
You can also run the same search but instead of passing collections as a query parameter you can include collection_id as a path parameter in the request URL itself. All of the other query parameters for the /search GET request will be available.
datetime_string = datetime(2025, 1, 4, tzinfo=UTC).isoformat()
item_search_request = httpx.get(
f"{stac_api_endpoint}/collections/{my_collection.id}/items",
params={
"datetime": f"{datetime_string}/..", # open interval from 2025-04-04 forward
"limit": 1000,
"filter": "eo:cloud_cover < 10", # less than 10% cloud cover
},
)
response = item_search_request.json()
print(f"found {len(response['features'])} items")3.3.3 Single Item by ID¶
To retrieve a specific item from the catalog, you can use the /collections/{collection_id}/items/{item_id} endpoint.
item_id = response["features"][0]["id"]
item_request = httpx.get(
f"{stac_api_endpoint}/collections/{my_collection.id}/items/{item_id}"
)
print(json.dumps(item_request.json(), indent=2))pystac-client can do the same thing
collection_client = client.get_collection(my_collection.id)
collection_client.get_item(item_id)Conclusion¶
That’s it! You have taken a full tour of the stac-fastapi-pgstac STAC API. Here is a look at the full API documentation for the deployed API:
api_docs = f"{local_stac_api_endpoint}/api.html"
print(api_docs)
IFrame(api_docs, 1200, 800)