Customer-owned STAC ingestion and management

This notebook demonstrates how a customer can manage STAC entities in a protected, customer-specific STAC endpoint.

Covered steps:

  1. Authenticate

  2. Load Data

  3. Create a Collection (POST)

  4. Create Items (POST)

  5. Update an Item (PUT)

  6. Delete STAC entities

Prerequisites:

  • Access to a customer-specific STAC API endpoint

  • A valid access token (temporarily provided via environment variable)

!pip install eodc-connect --upgrade --force-reinstall
Collecting eodc-connect
  Using cached eodc_connect-0.2.1-py3-none-any.whl.metadata (1.1 kB)
Collecting dask==2025.7.* (from eodc-connect)
  Using cached dask-2025.7.0-py3-none-any.whl.metadata (3.8 kB)
Collecting dask-gateway==2025.4.0 (from eodc-connect)
  Using cached dask_gateway-2025.4.0-py3-none-any.whl.metadata (3.1 kB)

1. Authentication

Your username of your eodc account come here, usually it is your email address you have used for registration. After running the next cell, a prompt will open and ask you to enter your password.

from getpass import getpass
from eodc_connect.auth import EODCConnection
import requests
import json

# Prompt for credentials
username = input("Enter your eodc username: ")
password = getpass("Enter your eodc password: ")

# Create connection
conn = EODCConnection(username=username, password=password)

# Get token
token = conn.get_access_token()

# Request 
resp = requests.get(
    "https://cstac.services.eodc.eu/stac",
    headers={"Authorization": f"Bearer {token}"}
)

print(json.dumps(resp.json(), indent=4))
print("Authentication configured.")
{
    "type": "Catalog",
    "id": "stac-fastapi",
    "title": "stac-fastapi",
    "description": "stac-fastapi",
    "stac_version": "1.0.0",
    "conformsTo": [
        "http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2",
        "http://www.opengis.net/spec/cql2/1.0/conf/cql2-json",
        "http://www.opengis.net/spec/cql2/1.0/conf/cql2-text",
        "http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query",
        "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
        "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
        "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30",
        "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter",
        "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/filter",
        "https://api.stacspec.org/v1.0.0-rc.1/collection-search",
        "https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields",
        "https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter",
        "https://api.stacspec.org/v1.0.0-rc.1/collection-search#query",
        "https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort",
        "https://api.stacspec.org/v1.0.0-rc.2/item-search#filter",
        "https://api.stacspec.org/v1.0.0/collections",
        "https://api.stacspec.org/v1.0.0/collections/extensions/transaction",
        "https://api.stacspec.org/v1.0.0/core",
        "https://api.stacspec.org/v1.0.0/item-search",
        "https://api.stacspec.org/v1.0.0/item-search#fields",
        "https://api.stacspec.org/v1.0.0/item-search#query",
        "https://api.stacspec.org/v1.0.0/item-search#sort",
        "https://api.stacspec.org/v1.0.0/ogcapi-features",
        "https://api.stacspec.org/v1.0.0/ogcapi-features#fields",
        "https://api.stacspec.org/v1.0.0/ogcapi-features#query",
        "https://api.stacspec.org/v1.0.0/ogcapi-features#sort",
        "https://api.stacspec.org/v1.0.0/ogcapi-features/extensions/transaction"
    ],
    "links": [
        {
            "rel": "self",
            "type": "application/json",
            "title": "This document",
            "href": "https://cstac.services.eodc.eu:9443/stac/"
        },
        {
            "rel": "root",
            "type": "application/json",
            "title": "Root",
            "href": "https://cstac.services.eodc.eu:9443/stac/"
        },
        {
            "rel": "data",
            "type": "application/json",
            "title": "Collections available for this Catalog",
            "href": "https://cstac.services.eodc.eu:9443/stac/collections"
        },
        {
            "rel": "conformance",
            "type": "application/json",
            "title": "STAC/OGC conformance classes implemented by this server",
            "href": "https://cstac.services.eodc.eu:9443/stac/conformance"
        },
        {
            "rel": "search",
            "type": "application/geo+json",
            "title": "STAC search [GET]",
            "href": "https://cstac.services.eodc.eu:9443/stac/search",
            "method": "GET"
        },
        {
            "rel": "search",
            "type": "application/geo+json",
            "title": "STAC search [POST]",
            "href": "https://cstac.services.eodc.eu:9443/stac/search",
            "method": "POST"
        },
        {
            "rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
            "type": "application/schema+json",
            "title": "Queryables available for this Catalog",
            "href": "https://cstac.services.eodc.eu:9443/stac/queryables",
            "method": "GET"
        },
        {
            "rel": "service-desc",
            "type": "application/vnd.oai.openapi+json;version=3.0",
            "title": "OpenAPI service description",
            "href": "https://cstac.services.eodc.eu:9443/stac/api"
        },
        {
            "rel": "service-doc",
            "type": "text/html",
            "title": "OpenAPI service documentation",
            "href": "https://cstac.services.eodc.eu:9443/stac/api.html"
        }
    ],
    "stac_extensions": [
        "https://stac-extensions.github.io/authentication/v1.1.0/schema.json"
    ],
    "auth:schemes": {
        "oidc": {
            "type": "openIdConnect",
            "openIdConnectUrl": "https://bouncer.eodc.eu/auth/realms/EODC/.well-known/openid-configuration"
        }
    }
}
Authentication configured.

2. Load sample data

We load a ready-to-ingest Collection and Items from sample_data/, available in the eodc-examples GitHub repository.

Make sure that your STAC metadata contains all required fields. In particular, the access field defines who can see the data in the catalogue.

Access control metadata

When creating STAC Collections or Items, it is important to include the access metadata field. This field defines who is allowed to view the data in the catalogue.

Example: make the data visible to all members of an organisation:

{
  "access": {
    "visibility": "organisation",
    "allowed_users": [],
    "allowed_organisations": ["eodc"]
  }
}

Example: restrict access to a specific user:

{
  "access": {
    "visibility": "user",
    "allowed_users": ["john.smith@eodc.eu", "jane.smith@eodc.eu"],
    "allowed_organisations": []
  }
}

If this field is missing or configured incorrectly, the data may not be visible to other users in the catalogue. You also have to have an unique collection id.

from pathlib import Path
import json

collection = json.loads(Path("sample_data/collection_customer_demo.json").read_text())
items = [
    json.loads(path.read_text())
    for path in sorted(Path("sample_data/items").glob("item_*.json"))
]

print("Loaded collection id:", collection["id"])
print("Loaded items:", [item["id"] for item in items])

collection_id = collection["id"]
Loaded collection id: customer-demo
Loaded items: ['item-001', 'item-002', 'item-003']

3. Create the Collection (POST)

We create the Collection via POST /collections.

TIMEOUT = 30

WRITE_BASE = "https://cstac.services.eodc.eu/stac"

HEADERS = {
    "Authorization": f"Bearer {token}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

def request_json(method, url, **kwargs):
    r = requests.request(
        method,
        url,
        headers=HEADERS,
        timeout=TIMEOUT,
        **kwargs
    )

    return r, r.json()
url = f"{WRITE_BASE}/collections"
r, data = request_json("POST", url, json=collection)

r.raise_for_status()
print(f"Create Collection: {collection['id']}")
print(url)
Create Collection: customer-demo
https://cstac.services.eodc.eu/stac/collections

4. Create Items (POST)

We add Items to the Collection via: POST /collections/{collectionId}/items

items_url = f"{WRITE_BASE}/collections/{collection_id}/items"

created_items = []

for item in items:
    r, data = request_json("POST", items_url, json=item)
    r.raise_for_status()
    created_items.append(item["id"])

print("Processed items:", created_items)
print(items_url)
Processed items: ['item-001', 'item-002', 'item-003']
https://cstac.services.eodc.eu/stac/collections/customer-demo/items

5. Update an Item (PUT)

Existing STAC Items can be updated using a PUT request.

The typical workflow is:

  1. Retrieve the current Item with GET

  2. Modify the desired fields (e.g. metadata or assets)

  3. Replace Item via PUT

In this example we update the updated timestamp in the Item metadata.

from datetime import datetime, timezone

def now_utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


target_item_id = items[0]["id"]
item_url = f"{WRITE_BASE}/collections/{collection_id}/items/{target_item_id}"

# 1) Fetch current item
r, current = request_json("GET", item_url)
r.raise_for_status()

# 2) Modify metadata
current.setdefault("properties", {})
current["properties"]["updated"] = now_utc_iso()

# 3) Replace item with updated version
r, data = request_json("PUT", item_url, json=current)
r.raise_for_status()

print(f"Item '{target_item_id}' updated successfully")
print("Updated timestamp:")
print(current["properties"]["updated"])
Item 'item-001' updated successfully
Updated timestamp:
2026-06-23T09:32:11.199642Z

Optionally, the same approach can be used to modify the assets dictionary of an Item. For example, you could add or update an Asset describing additional metadata or data files associated with the Item.

# Fetch item again
r, current = request_json("GET", item_url)
r.raise_for_status()

current.setdefault("assets", {})

# Add a new asset named "metadata"
current["assets"]["metadata"] = {
    "href": f"https://example.com/{target_item_id}.json",
    "type": "application/json",
    "title": "Metadata example",
    "roles": ["metadata"]
}

# Update item with new asset
r, data = request_json("PUT", item_url, json=current)
r.raise_for_status()

print(f"Asset added successfully to {target_item_id}")
print("Assets:")
print(json.dumps(current["assets"], indent=2))
Asset added successfully to item-001
Assets:
{
  "metadata": {
    "href": "https://example.com/item-001.json",
    "type": "application/json",
    "title": "Metadata example",
    "roles": [
      "metadata"
    ]
  }
}

7. Delete Collection (cleanup)

We can have a looka t all available colelctions before and after deletion.

print("BEFORE deletion:")
r, data = request_json("GET", f"{WRITE_BASE}/collections")
for col in data.get("collections", []):
    print("-", col["id"])
BEFORE deletion:
- customer-demo
del_col_url = f"{WRITE_BASE}/collections/{collection_id}"
r, data = request_json("DELETE", del_col_url)
r.raise_for_status()

print(f"Deleted collection: {collection_id}")
Deleted collection: customer-demo
print("AFTER deletion:")
r, data = request_json("GET", f"{WRITE_BASE}/collections")
for col in data.get("collections", []):
    print("-", col["id"])
AFTER deletion:

8. Common mistakes

Missing access metadata — if the access field is missing, the dataset may not be visible to other users.

Invalid STAC structure — make sure the metadata follows the STAC specification. The stac-validator tool can help catch issues before ingestion:

stac-validator collection.json

Incorrect asset links — assets must point to valid and accessible data files.

9. Troubleshooting

Item creation fails — check that the collection exists, item IDs are unique, and the metadata is valid JSON.

Data not visible in the catalogue — check the access settings, organisation name, and permissions.

Validation errors — run a STAC validator before ingestion.

10. Support

If you encounter problems publishing STAC data at eodc, please contact support@eodc.eu.