How to access Copernicus Sentinel data at eodc

This guide covers the replacement for eodc’s old NFS-mounted Copernicus archive (/eodc/products/copernicus.eu/..., one ZIP per scene): discovering Sentinel products through eodc’s STAC catalogue, and fetching them on request. Recent, commonly-used products are often already mirrored into eodc’s storage ahead of time (the “Rolling Archive”) and served instantly; anything else is staged from the Copernicus Data Space Ecosystem on demand — you request data the same way either way, so you don’t need to know in advance which case you’re in.

What’s different from the old NFS archive

Old (NFS/GPFS)

New (Rolling Archive + on-demand staging)

Access

Filesystem path on an eodc VM

STAC catalogue search → HTTP request → object storage

Availability

Always present once ingested (multi-year history)

Recent data served instantly via Rolling Archive; everything else staged on request (low minutes)

Unit of access

Whole ZIP per scene

Whole product or individual bands/files

Filenames

Fixed, predictable path per product

Resolved dynamically from catalogue metadata per item

Bulk retrieval

Directory listing/scan over an AOI or date range was effectively free

One request per product/asset today — a bulk-request feature is on the roadmap (see below)

Prerequisites

  • Asset hrefs resolve to eodc’s data access host, https://data.eodc.eu — always take the href from the STAC response rather than hardcoding this, since the exact path depends on the collection and asset.

  • CDSE collections now require an eodc account. Anonymous requests for CDSE Sentinel asset downloads get a 401 — see Get Access if you don’t already have one, then authenticate as shown below. Browsing and thumbnails (e.g. via STAC Browser) still work anonymously; only the actual data download is gated. Presigned S3 download URLs themselves are unaffected once issued — the account check only happens on the initial request to data.eodc.eu.

  • Plain Python (requests) is enough — no client SDK required for anything in this guide.

Authenticating

CDSE collections require this now: authenticate with either your eodc username/password directly (HTTP Basic) or a Bearer token exchanged for them:

import requests

def get_token(username: str, password: str) -> str:
    response = requests.post(
        "https://bouncer.eodc.eu/auth/realms/EODC/protocol/openid-connect/token",
        data={
            "grant_type": "password",
            "client_id": "data-access",
            "username": username,
            "password": password,
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["access_token"]

token = get_token("you@eodc.eu", "...")
response = requests.get(href, headers={"Authorization": f"Bearer {token}"}, timeout=30)

The token is cached server-side after first use, so only the very first request pays the cost of a real login — later ones are fast. There’s nothing CDSE-collection-specific about this flow; it’s the same one eodc’s other protected collections already use.

Coverage and retention

Mission/product

Extent

Retention in the Rolling Archive

Sentinel-1 GRD

Global

7 days

Sentinel-1 AUX

Global

30 days

Sentinel-1 GRD (COG variant)

Europe

7 days

Sentinel-1 OCN

Global

7 days

Sentinel-1 RAW

Europe

7 days

Sentinel-1 SLC

Austria

7 days

Sentinel-2 MSI (L1C/L2A)

Austria

7 days

Sentinel-2 AUX

Global

30 days

Sentinel-3 (OLCI/SLSTR)

Austria

7 days

Sentinel-3 Synergy

Global

7 days

Sentinel-3 AUX

Global

30 days

Sentinel-5P (L1/L2)

Europe

7 days

Sentinel-5P AUX

Global

30 days

Sentinel-1 CARD is not currently covered. “Retention in the Rolling Archive” is how long a product is proactively kept for instant access — it’s not a limit on what you can request. Anything outside that window (or outside the table above) is simply staged from CDSE on request instead of being instant, with no change to how you ask for it. Check the EODC data catalogue for current, authoritative coverage — extents can and have changed (several collections are scoped to Austria rather than the originally-planned Europe).

When a product ages out of its retention window, it’s removed from fast-access storage but stays fully discoverable in the catalogue — requesting it again triggers staging from CDSE like any other not-currently-held product. Re-fetching it doesn’t grant any special long-term status: it goes back on the same retention clock as everything else in that collection, so a product you haven’t touched in a while may need a fresh staging wait the next time you request it too.

Discover a product

Search eodc’s STAC catalogue directly:

import requests

CATALOGUE = "https://catalogue.services.eodc.eu"

response = requests.get(
    f"{CATALOGUE}/collections/S1_SAR_GRD/items",
    params={
        "datetime": "2026-08-01T00:00:00Z/2026-08-07T23:59:59Z",
        "bbox": "15.0,47.0,17.0,49.0",
        "limit": 10,
    },
    timeout=30,
)
response.raise_for_status()
item = response.json()["features"][0]

# always read the real asset name from the item, never assume one
asset_name = next(name for name in item["assets"] if "vv" in name.lower())
href = item["assets"][asset_name]["href"]

Some collections (the Sentinel-1 SAR family especially, also S3_SLSTR/S3_SRAL) have internal filenames that vary per product — always resolve the asset name from the STAC response, never hardcode or reuse one from a different product.

Request and stage the asset

GET the asset href:

  1. Already staged → 302 redirect to a presigned S3 download URL, valid for a few minutes — fetch it immediately, don’t store it for later.

  2. Not staged → 202 with a Retry-After header — poll again after that interval, with a real retry/backoff loop, not a single retry.

  3. 404 means the asset name doesn’t match anything real for that item — re-check the STAC response. 502 means staging itself failed — retry a bounded number of times, then treat it as a genuine error.

import time
import requests

def fetch_asset(href: str, max_wait_seconds: int = 900) -> bytes:
    waited = 0
    while True:
        response = requests.get(href, allow_redirects=False, timeout=30)

        if response.status_code == 302:
            download_url = response.headers["Location"]
            return requests.get(download_url, timeout=300).content

        if response.status_code == 202:
            retry_after = int(response.headers.get("Retry-After", 120))
            if waited + retry_after > max_wait_seconds:
                raise TimeoutError(f"asset not staged after {waited}s")
            time.sleep(retry_after)
            waited += retry_after
            continue

        if response.status_code == 404:
            raise LookupError(f"asset not found: {href}")

        response.raise_for_status()  # 502 or anything else: don't loop forever

data = fetch_asset(href)

Expect low minutes, not seconds, for the first request to any not-yet-staged product — this is the change most likely to break a pipeline that assumed synchronous, instant file access.

Whole product vs. per-asset

Both are supported: the full ZIP/SAFE/SEN3 package (closest to the old NFS workflow), or an individual band/internal file if your pipeline only reads one or two bands from a scene — request just those assets to save bandwidth and local storage.

Cloud-native access (skip the download)

If you read data with rioxarray/rasterio/GDAL anyway, you can often open the staged asset’s URL directly instead of downloading it first — GDAL issues HTTP range requests against any URL that supports them, including presigned S3 URLs:

import os
import rioxarray

# avoids GDAL probing ~15-20 non-existent companion-file extensions per open,
# each a round trip through HDA -- can turn a ~1s open into 30-60s without this
os.environ["GDAL_DISABLE_READDIR_ON_OPEN"] = "EMPTY_DIR"

def get_presigned_url(href: str) -> str:
    response = requests.get(href, allow_redirects=False, timeout=30)
    response.raise_for_status()
    return response.headers["Location"]  # assumes already staged; poll first otherwise

url = get_presigned_url(href)
da = rioxarray.open_rasterio(url, masked=True)
aoi = da.rio.clip_box(minx=15.0, miny=47.0, maxx=17.0, maxy=49.0)

This pays off best for Cloud-Optimized GeoTIFF (COG) assets — such as the Sentinel-1 GRD COG collection — which are internally tiled specifically for efficient partial reads. Plain SAFE-packaged imagery (regular GeoTIFF/JPEG2000) still works this way but without the same tiling benefit, so a full download is often simpler unless you’re deliberately reading a small window from a large scene.

Set GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR as shown above — without it, opening a Sentinel-1 GRD COG band this way can take anywhere from 1.5 to 59 seconds on the identical file, because GDAL probes for sidecar files that don’t exist, each a real network round trip through HDA. With it set, opens are consistently ~1.1-1.3s.

The presigned URL is short-lived (on the order of a few minutes), and rioxarray reads lazily — if you hold a handle open too long before actually reading, request a fresh URL and re-open rather than assuming the old one still works.

For stacking several scenes together (e.g. a time series over an AOI), the general pattern is to resolve each item’s asset URL and combine them with xarray.concat, or use a library built for this such as stackstac or odc-stac. These are general-purpose tools, not something built or verified specifically against eodc’s catalogue — test at small scale first.

Migrating existing NFS-based code

Before — reading a known file straight off the mounted filesystem:

import zipfile

path = "/eodc/products/copernicus.eu/S1/GRD/2026/08/15/S1A_IW_GRDH_..._SAFE.zip"
with zipfile.ZipFile(path) as z:
    data = z.read("measurement/s1a-iw-grd-vv-....tiff")

After — discover the product via STAC, resolve the real asset name, request it:

item = requests.get(
    f"{CATALOGUE}/collections/S1_SAR_GRD/items/S1A_IW_GRDH_...",
    timeout=30,
).json()

asset_name = next(name for name in item["assets"] if "vv" in name.lower())
href = item["assets"][asset_name]["href"]
data = fetch_asset(href)

Migration checklist:

  • Stop hardcoding NFS paths — replace with a catalogue search (by product ID, or by collection/date/bbox if you were relying on directory listings).

  • If you relied on bulk directory scans, don’t replace them with an unbounded loop — see Bulk retrieval below.

  • Stop assuming ZIP-only access — switch to per-asset requests, or skip downloading entirely (see Cloud-native access), if you only need a band or two.

  • Add retry/backoff around staging — the change most likely to break existing pipelines.

  • Re-check any code relying on fixed filename patterns — always read the name from the catalogue item.

  • If your pipeline depends on re-reading a scene from months or years back, expect the first read after a long gap to take longer than a warm one — it’s re-staged from CDSE transparently, not guaranteed to still be in the Rolling Archive’s window.

Bulk retrieval

There is no bulk/batch-request endpoint yet — each product or asset is requested (and staged, if needed) individually, and there’s no server-side rate limiting either, so nothing stops you from firing many requests at once — but each one triggers a real backend job, and eodc has no visibility into how much concurrent load the backend can currently absorb. A bulk-request feature is on the roadmap; until it ships, if your old pipeline did bulk retrieval (e.g. “every S1 GRD scene over this AOI for 6 months”):

  • Bounded-concurrency client loop — request staging with a capped number in flight, a handful at a time, not hundreds:

    from concurrent.futures import ThreadPoolExecutor
    
    def fetch_many(hrefs: list[str], max_concurrent: int = 5):
        with ThreadPoolExecutor(max_workers=max_concurrent) as pool:
            return list(pool.map(fetch_asset, hrefs))
    
  • The third-party eodag library’s direct-to-CDSE bulk search+download — talks directly to the CDSE provider API, bypassing eodc’s staging layer entirely (no presigned-S3 delivery, no Rolling-Archive benefit, needs your own CDSE credentials). A genuinely separate capability — reach for it only for true bulk/backfill jobs.

If neither fits your need, contact support@eodc.eu — a batch-submit endpoint is on the roadmap, but no date is committed yet.

Common issues

404 for a file I expected to exist — the asset name used doesn’t match what the catalogue lists for that product; re-check the item’s assets, don’t hardcode names.

502 — staging itself failed on eodc’s side; retry a reasonable number of times, then contact support@eodc.eu with the product ID and timestamp.

Download link stopped working — presigned links are short-lived and single-use; request a fresh one rather than reusing an old one.

Slow or timed-out catalogue lookups for Sentinel-2 L2A or Sentinel-3 Synergy items — a known, currently-open backend performance issue specific to looking up a single item by ID for these two collections (searching/listing items is unaffected and stays fast); retry rather than assuming the collection is unavailable.

Support

If something here doesn’t match what you’re seeing, contact support@eodc.eu with the product ID (or collection + date range) and what you expected vs. what happened.