Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

3. The STAC API: stac-fastapi-pgstac

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.

Warning: Do not turn on the transaction or bulk transactions extensions for a public-facing STAC API without some kind of auth layer enabled! Enabling the transactions extensions enables users to POST collections or items to the database via stac-fastapi-pgstac.

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_EXTENSIONS environment variable controls which extensions are enabled

  • pgstac database credentials are set by POSTGRES_* 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.

Note: If you visit the urls listed in the conformance classes you may get a 404 - this is expected

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 parameter

  • datetime: temporal filters

  • bbox: spatial filters

  • filter: 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!

stac-fastapi-pgstac ships with the `collection-search` extension paired with the `free-text` extension which enables simple text searches against the collection title, description, and keywords fields.

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.

Tip: Try out the CQL2 Playground to learn how to write cql2-text or cql2-json queries

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)
Note: If your collection did not appear, try adjusting your collection search terms!
# 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}/items

  • GET 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.

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)