{ "cells": [ { "cell_type": "markdown", "id": "74e04b22", "metadata": {}, "source": [ "# Customer-owned STAC ingestion and management \n", "\n", "This notebook demonstrates how a customer can manage STAC entities in a protected, customer-specific STAC endpoint.\n", "\n", "Covered steps:\n", "1. Authenticate \n", "2. Load Data\n", "3. Create a Collection (POST)\n", "4. Create Items (POST)\n", "5. Update an Item (PUT)\n", "6. Delete STAC entities\n", "\n", "Prerequisites:\n", "- Access to a customer-specific STAC API endpoint\n", "- A valid access token (temporarily provided via environment variable)" ] }, { "cell_type": "code", "execution_count": null, "id": "1bf4048c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting eodc-connect\n", " Using cached eodc_connect-0.2.1-py3-none-any.whl.metadata (1.1 kB)\n", "Collecting dask==2025.7.* (from eodc-connect)\n", " Using cached dask-2025.7.0-py3-none-any.whl.metadata (3.8 kB)\n", "Collecting dask-gateway==2025.4.0 (from eodc-connect)\n", " Using cached dask_gateway-2025.4.0-py3-none-any.whl.metadata (3.1 kB)\n" ] } ], "source": [ "!pip install eodc-connect --upgrade --force-reinstall" ] }, { "cell_type": "markdown", "id": "cc7aa1e6", "metadata": {}, "source": [ "## 1. Authentication \n", "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." ] }, { "cell_type": "code", "execution_count": 2, "id": "295ab9eb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"type\": \"Catalog\",\n", " \"id\": \"stac-fastapi\",\n", " \"title\": \"stac-fastapi\",\n", " \"description\": \"stac-fastapi\",\n", " \"stac_version\": \"1.0.0\",\n", " \"conformsTo\": [\n", " \"http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2\",\n", " \"http://www.opengis.net/spec/cql2/1.0/conf/cql2-json\",\n", " \"http://www.opengis.net/spec/cql2/1.0/conf/cql2-text\",\n", " \"http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query\",\n", " \"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core\",\n", " \"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson\",\n", " \"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30\",\n", " \"http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter\",\n", " \"http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/filter\",\n", " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search\",\n", " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields\",\n", " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter\",\n", " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#query\",\n", " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort\",\n", " \"https://api.stacspec.org/v1.0.0-rc.2/item-search#filter\",\n", " \"https://api.stacspec.org/v1.0.0/collections\",\n", " \"https://api.stacspec.org/v1.0.0/collections/extensions/transaction\",\n", " \"https://api.stacspec.org/v1.0.0/core\",\n", " \"https://api.stacspec.org/v1.0.0/item-search\",\n", " \"https://api.stacspec.org/v1.0.0/item-search#fields\",\n", " \"https://api.stacspec.org/v1.0.0/item-search#query\",\n", " \"https://api.stacspec.org/v1.0.0/item-search#sort\",\n", " \"https://api.stacspec.org/v1.0.0/ogcapi-features\",\n", " \"https://api.stacspec.org/v1.0.0/ogcapi-features#fields\",\n", " \"https://api.stacspec.org/v1.0.0/ogcapi-features#query\",\n", " \"https://api.stacspec.org/v1.0.0/ogcapi-features#sort\",\n", " \"https://api.stacspec.org/v1.0.0/ogcapi-features/extensions/transaction\"\n", " ],\n", " \"links\": [\n", " {\n", " \"rel\": \"self\",\n", " \"type\": \"application/json\",\n", " \"title\": \"This document\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/\"\n", " },\n", " {\n", " \"rel\": \"root\",\n", " \"type\": \"application/json\",\n", " \"title\": \"Root\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/\"\n", " },\n", " {\n", " \"rel\": \"data\",\n", " \"type\": \"application/json\",\n", " \"title\": \"Collections available for this Catalog\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/collections\"\n", " },\n", " {\n", " \"rel\": \"conformance\",\n", " \"type\": \"application/json\",\n", " \"title\": \"STAC/OGC conformance classes implemented by this server\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/conformance\"\n", " },\n", " {\n", " \"rel\": \"search\",\n", " \"type\": \"application/geo+json\",\n", " \"title\": \"STAC search [GET]\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/search\",\n", " \"method\": \"GET\"\n", " },\n", " {\n", " \"rel\": \"search\",\n", " \"type\": \"application/geo+json\",\n", " \"title\": \"STAC search [POST]\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/search\",\n", " \"method\": \"POST\"\n", " },\n", " {\n", " \"rel\": \"http://www.opengis.net/def/rel/ogc/1.0/queryables\",\n", " \"type\": \"application/schema+json\",\n", " \"title\": \"Queryables available for this Catalog\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/queryables\",\n", " \"method\": \"GET\"\n", " },\n", " {\n", " \"rel\": \"service-desc\",\n", " \"type\": \"application/vnd.oai.openapi+json;version=3.0\",\n", " \"title\": \"OpenAPI service description\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/api\"\n", " },\n", " {\n", " \"rel\": \"service-doc\",\n", " \"type\": \"text/html\",\n", " \"title\": \"OpenAPI service documentation\",\n", " \"href\": \"https://cstac.services.eodc.eu:9443/stac/api.html\"\n", " }\n", " ],\n", " \"stac_extensions\": [\n", " \"https://stac-extensions.github.io/authentication/v1.1.0/schema.json\"\n", " ],\n", " \"auth:schemes\": {\n", " \"oidc\": {\n", " \"type\": \"openIdConnect\",\n", " \"openIdConnectUrl\": \"https://bouncer.eodc.eu/auth/realms/EODC/.well-known/openid-configuration\"\n", " }\n", " }\n", "}\n", "Authentication configured.\n" ] } ], "source": [ "from getpass import getpass\n", "from eodc_connect.auth import EODCConnection\n", "import requests\n", "import json\n", "\n", "# Prompt for credentials\n", "username = input(\"Enter your eodc username: \")\n", "password = getpass(\"Enter your eodc password: \")\n", "\n", "# Create connection\n", "conn = EODCConnection(username=username, password=password)\n", "\n", "# Get token\n", "token = conn.get_access_token()\n", "\n", "# Request \n", "resp = requests.get(\n", " \"https://cstac.services.eodc.eu/stac\",\n", " headers={\"Authorization\": f\"Bearer {token}\"}\n", ")\n", "\n", "print(json.dumps(resp.json(), indent=4))\n", "print(\"Authentication configured.\")\n", "\n" ] }, { "cell_type": "markdown", "id": "aeb0c337", "metadata": {}, "source": [ "## 2. Load sample data\n", "\n", "We load a ready-to-ingest Collection and Items from `sample_data/`, available in the [eodc-examples GitHub repository](https://github.com/eodcgmbh/eodc-examples/tree/POC_STAC_Customer_Ingestion/sample_data).\n", "\n", "Make sure that your STAC metadata contains all required fields. In particular, the `access` field defines who can see the data in the catalogue.\n", "\n", "### Access control metadata\n", "\n", "When creating STAC Collections or Items, it is important to include the `access` metadata field.\n", "This field defines who is allowed to view the data in the catalogue.\n", "\n", "Example: make the data visible to all members of an organisation:\n", "\n", "```json\n", "{\n", " \"access\": {\n", " \"visibility\": \"organisation\",\n", " \"allowed_users\": [],\n", " \"allowed_organisations\": [\"eodc\"]\n", " }\n", "}\n", "```\n", "\n", "Example: restrict access to a specific user:\n", "\n", "```json\n", "{\n", " \"access\": {\n", " \"visibility\": \"user\",\n", " \"allowed_users\": [\"john.smith@eodc.eu\", \"jane.smith@eodc.eu\"],\n", " \"allowed_organisations\": []\n", " }\n", "}\n", "```\n", "\n", "If this field is missing or configured incorrectly, the data may not be visible to other users in the catalogue. \n", "You also have to have an unique collection id." ] }, { "cell_type": "code", "execution_count": 3, "id": "4ef17c65", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loaded collection id: customer-demo\n", "Loaded items: ['item-001', 'item-002', 'item-003']\n" ] } ], "source": [ "from pathlib import Path\n", "import json\n", "\n", "collection = json.loads(Path(\"sample_data/collection_customer_demo.json\").read_text())\n", "items = [\n", " json.loads(path.read_text())\n", " for path in sorted(Path(\"sample_data/items\").glob(\"item_*.json\"))\n", "]\n", "\n", "print(\"Loaded collection id:\", collection[\"id\"])\n", "print(\"Loaded items:\", [item[\"id\"] for item in items])\n", "\n", "collection_id = collection[\"id\"]" ] }, { "cell_type": "markdown", "id": "c1a4220b", "metadata": {}, "source": [ "## 3. Create the Collection (POST)\n", "\n", "We create the Collection via `POST /collections`." ] }, { "cell_type": "code", "execution_count": null, "id": "bf73bd1a", "metadata": {}, "outputs": [], "source": [ "TIMEOUT = 30\n", "\n", "WRITE_BASE = \"https://cstac.services.eodc.eu/stac\"\n", "\n", "HEADERS = {\n", " \"Authorization\": f\"Bearer {token}\",\n", " \"Accept\": \"application/json\",\n", " \"Content-Type\": \"application/json\",\n", "}\n", "\n", "def request_json(method, url, **kwargs):\n", " r = requests.request(\n", " method,\n", " url,\n", " headers=HEADERS,\n", " timeout=TIMEOUT,\n", " **kwargs\n", " )\n", "\n", " return r, r.json()" ] }, { "cell_type": "code", "execution_count": 7, "id": "e1b3341f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Create Collection: customer-demo\n", "https://cstac.services.eodc.eu/stac/collections\n" ] } ], "source": [ "url = f\"{WRITE_BASE}/collections\"\n", "r, data = request_json(\"POST\", url, json=collection)\n", "\n", "r.raise_for_status()\n", "print(f\"Create Collection: {collection['id']}\")\n", "print(url)" ] }, { "cell_type": "markdown", "id": "243cdace", "metadata": {}, "source": [ "## 4. Create Items (POST)\n", "\n", "We add Items to the Collection via:\n", "`POST /collections/{collectionId}/items`" ] }, { "cell_type": "code", "execution_count": 8, "id": "ebb3785e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Processed items: ['item-001', 'item-002', 'item-003']\n", "https://cstac.services.eodc.eu/stac/collections/customer-demo/items\n" ] } ], "source": [ "items_url = f\"{WRITE_BASE}/collections/{collection_id}/items\"\n", "\n", "created_items = []\n", "\n", "for item in items:\n", " r, data = request_json(\"POST\", items_url, json=item)\n", " r.raise_for_status()\n", " created_items.append(item[\"id\"])\n", "\n", "print(\"Processed items:\", created_items)\n", "print(items_url)" ] }, { "cell_type": "markdown", "id": "af799842", "metadata": {}, "source": [ "## 5. Update an Item (PUT)\n", "\n", "Existing STAC Items can be updated using a `PUT` request.\n", "\n", "The typical workflow is:\n", "\n", "1. Retrieve the current Item with `GET`\n", "2. Modify the desired fields (e.g. metadata or assets)\n", "3. Replace Item via `PUT`\n", "\n", "In this example we update the `updated` timestamp in the Item metadata." ] }, { "cell_type": "code", "execution_count": 9, "id": "709e32c1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Item 'item-001' updated successfully\n", "Updated timestamp:\n", "2026-06-23T09:32:11.199642Z\n" ] } ], "source": [ "from datetime import datetime, timezone\n", "\n", "def now_utc_iso() -> str:\n", " return datetime.now(timezone.utc).isoformat().replace(\"+00:00\", \"Z\")\n", "\n", "\n", "target_item_id = items[0][\"id\"]\n", "item_url = f\"{WRITE_BASE}/collections/{collection_id}/items/{target_item_id}\"\n", "\n", "# 1) Fetch current item\n", "r, current = request_json(\"GET\", item_url)\n", "r.raise_for_status()\n", "\n", "# 2) Modify metadata\n", "current.setdefault(\"properties\", {})\n", "current[\"properties\"][\"updated\"] = now_utc_iso()\n", "\n", "# 3) Replace item with updated version\n", "r, data = request_json(\"PUT\", item_url, json=current)\n", "r.raise_for_status()\n", "\n", "print(f\"Item '{target_item_id}' updated successfully\")\n", "print(\"Updated timestamp:\")\n", "print(current[\"properties\"][\"updated\"])" ] }, { "cell_type": "markdown", "id": "4eb8d9d6", "metadata": {}, "source": [ "Optionally, the same approach can be used to modify the `assets` dictionary of an Item.\n", "For example, you could add or update an Asset describing additional metadata or data files associated with the Item." ] }, { "cell_type": "code", "execution_count": 10, "id": "7fb16ff6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Asset added successfully to item-001\n", "Assets:\n", "{\n", " \"metadata\": {\n", " \"href\": \"https://example.com/item-001.json\",\n", " \"type\": \"application/json\",\n", " \"title\": \"Metadata example\",\n", " \"roles\": [\n", " \"metadata\"\n", " ]\n", " }\n", "}\n" ] } ], "source": [ "# Fetch item again\n", "r, current = request_json(\"GET\", item_url)\n", "r.raise_for_status()\n", "\n", "current.setdefault(\"assets\", {})\n", "\n", "# Add a new asset named \"metadata\"\n", "current[\"assets\"][\"metadata\"] = {\n", " \"href\": f\"https://example.com/{target_item_id}.json\",\n", " \"type\": \"application/json\",\n", " \"title\": \"Metadata example\",\n", " \"roles\": [\"metadata\"]\n", "}\n", "\n", "# Update item with new asset\n", "r, data = request_json(\"PUT\", item_url, json=current)\n", "r.raise_for_status()\n", "\n", "print(f\"Asset added successfully to {target_item_id}\")\n", "print(\"Assets:\")\n", "print(json.dumps(current[\"assets\"], indent=2))" ] }, { "cell_type": "markdown", "id": "6dad676b", "metadata": {}, "source": [ "## 7. Delete Collection (cleanup)\n", "We can have a looka t all available colelctions before and after deletion. " ] }, { "cell_type": "code", "execution_count": 11, "id": "df858fc9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BEFORE deletion:\n", "- customer-demo\n" ] } ], "source": [ "print(\"BEFORE deletion:\")\n", "r, data = request_json(\"GET\", f\"{WRITE_BASE}/collections\")\n", "for col in data.get(\"collections\", []):\n", " print(\"-\", col[\"id\"])" ] }, { "cell_type": "code", "execution_count": 12, "id": "584718ad", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deleted collection: customer-demo\n" ] } ], "source": [ "del_col_url = f\"{WRITE_BASE}/collections/{collection_id}\"\n", "r, data = request_json(\"DELETE\", del_col_url)\n", "r.raise_for_status()\n", "\n", "print(f\"Deleted collection: {collection_id}\")" ] }, { "cell_type": "code", "execution_count": 13, "id": "fc906b59", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "AFTER deletion:\n" ] } ], "source": [ "print(\"AFTER deletion:\")\n", "r, data = request_json(\"GET\", f\"{WRITE_BASE}/collections\")\n", "for col in data.get(\"collections\", []):\n", " print(\"-\", col[\"id\"])" ] }, { "cell_type": "markdown", "id": "", "metadata": {}, "source": [ "## 8. Common mistakes\n", "\n", "**Missing access metadata** — if the `access` field is missing, the dataset may not be visible to other users.\n", "\n", "**Invalid STAC structure** — make sure the metadata follows the STAC specification. The `stac-validator` tool can help catch issues before ingestion:\n", "\n", "```\n", "stac-validator collection.json\n", "```\n", "\n", "**Incorrect asset links** — assets must point to valid and accessible data files.\n" ] }, { "cell_type": "markdown", "id": "", "metadata": {}, "source": [ "## 9. Troubleshooting\n", "\n", "**Item creation fails** — check that the collection exists, item IDs are unique, and the metadata is valid JSON.\n", "\n", "**Data not visible in the catalogue** — check the `access` settings, organisation name, and permissions.\n", "\n", "**Validation errors** — run a STAC validator before ingestion.\n" ] }, { "cell_type": "markdown", "id": "", "metadata": {}, "source": [ "## 10. Support\n", "\n", "If you encounter problems publishing STAC data at eodc, please contact **[support@eodc.eu](mailto:support@eodc.eu)**.\n" ] } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }