Quickstart
Get started with EO Router in minutes. Unified satellite imagery search, ordering and tasking across 14 providers — one API, one balance.
Overview
EO Router is an authorized reseller and API aggregator for commercial and open EO data. You interact with one STAC-compliant endpoint regardless of which satellite provider holds the data. The platform handles authentication, normalization, billing, delivery and webhooks.
Base URL: https://api.eorouter.io/v1
Auth: Bearer token in Authorization header
Format: JSON request bodies, STAC FeatureCollection responses

Free tier: Sentinel-2 L2A, Sentinel-1 GRD, Landsat C2 and NASA CMR are always free. Open data search is free. A €0.50 flat delivery fee applies per order.

Provider IDProviderSensorBest GSDTasking
planetPlanet LabsOPT0.5 m✓
iceyeICEYESAR X-band0.25 m✓
capellaCapella SpaceSAR X-band0.35 m✓
umbraUmbraSAR X-band0.25 m✓
airbusAirbus OneAtlasOPT0.3 m✓
maxarMaxarOPT0.3 m✓ ITAR
blackskyBlackSkyOPT0.5 m✓
cdseESA / CDSEOPT + SAR10 m—
sentinelhubSentinel HubOPT + SAR10 m—
nasaNASA CMROPT30 m—
usgsUSGS M2MOPT30 m—
earthsearchEarth SearchOPT + SAR10 m—
Generate your API key
Your API key authenticates all requests and is tied to your account balance. Create one here, or from Dashboard → API Keys.
API Key

Keep this secret. Never commit your API key to source control. Use environment variables or a secrets manager. Keys can be rotated from your dashboard at any time.

Two key types:
  • eor_live_sk_... — production keys, billed against your balance
  • eor_test_sk_... — sandbox keys, no charges, return mock responses
# Unix / macOS
export EOR_API_KEY="eor_live_sk_..."

# Windows (PowerShell)
$env:EOR_API_KEY = "eor_live_sk_..."

# .env file (never commit this)
EOR_API_KEY=eor_live_sk_...
Install the SDK
Official SDKs for Python and TypeScript. Both wrap the REST API with typed models, retry logic and async support.
# Python (requires 3.9+)
pip install eorouter

# TypeScript / Node.js (requires Node 18+)
npm install @eorouter/sdk

# Verify
python -c "import eorouter; print(eorouter.__version__)"  # 1.4.0
Your first search
Search for optical and SAR imagery over an AOI. The platform queries all matching providers in parallel and returns a scored, normalized STAC FeatureCollection.
import os
from eorouter import EoRouterClient

client = EoRouterClient(api_key=os.environ["EOR_API_KEY"])

# AOI: Rome city centre
rome = {
    "type": "Polygon",
    "coordinates": [[[12.45, 41.87], [12.55, 41.87],
                       [12.55, 41.95], [12.45, 41.95],
                       [12.45, 41.87]]]
}

results = client.search(
    aoi=rome,
    date_from="2026-03-01",
    date_to="2026-03-21",
    max_cloud_cover=20,
    sensor_types=["optical", "sar"],
    routing="sar_fallback_if_cloud",
    limit=10,
)

print(f"Found {'{'}results.matched{'}'} scenes across {'{'}len(results.provider_stats){'}'} providers")
for scene in results:
    print(
        f"  {'{'}scene.provider:12{'}'} | {'{'}scene.collection_id:30{'}'} | "
        f"GSD {'{'}scene.gsd_m{'}'}m | cloud {'{'}scene.cloud_cover{'}'}% | "
        f"score {'{'}scene.score:.2f{'}'} | {'{'}scene.price_display{'}'}"
    )

Output: Each scene has scene_id, provider, collection_id, gsd_m, cloud_cover, datetime, score, price_eur, geometry and the full STAC properties dict — regardless of which provider it came from.

Your first order
Take a scene_id from search results and place an archive order. The platform groups scenes by provider, clips to your AOI, converts to COG and delivers to your destination.
top_scene = results[0]

# Estimate cost first (no charge)
estimate = client.orders.estimate(
    scene_ids=[top_scene.scene_id],
    aoi=rome,
)
print(f"Estimated cost: €{'{'}estimate.total_eur{'}'}")

# Place the order (amount pre-authorized)
order = client.orders.create(
    scene_ids=[top_scene.scene_id],
    processing={"format": "COG", "clip_to_aoi": True, "aoi": rome},
    delivery={"type": "s3", "destination_id": "dest_prod"},
    confirm=True,
)
print(f"Order {'{'}order.order_id{'}'} → status: {'{'}order.status{'}'}")

# Poll until delivered (or use webhooks)
order.wait(timeout_minutes=60)
manifest = order.manifest()
for item in manifest["items"]:
    print(f"  {'{'}item['filename']{'}'}  {'{'}item['size_mb']:.1f{'}'} MB  s3://{'{'}item['bucket']{'}'}/{'{'}item['key']{'}'}")

Billing: The order amount is pre-authorized on confirm and captured on delivery based on actual clipped area — not the estimated area.

BYO credentials
If you have your own API contracts with Planet, ICEYE, Airbus or other providers, register your credentials. EO Router uses them for your orders — your existing contract pricing applies and the provider bills you separately. EO Router charges only the platform routing fee.
# Register a Planet API key
curl -X POST https://api.eorouter.io/v1/credentials \
  -H "Authorization: Bearer $EOR_API_KEY" \
  -d '{"provider": "planet", "type": "api_key", "value": "PLxxx...", "name": "Planet prod"}'

Routing fee: When BYOK is active for a provider, the platform fee is max(area_km² × €0.10, €0.50) per order — not 2.5%. All other providers in the same order still use the 2.5% rate.

planetapi_keyvalue: Planet API key
iceyejwt_clientclient_id + client_secret
airbusoauth2_clientclient_id + client_secret
capellaapi_keyvalue: Capella API key
umbraapi_keyvalue: Umbra API key
sentinelhuboauth2_clientclient_id + client_secret
Delivery destinations
Register at least one delivery destination before placing orders. Files are delivered there with checksums after processing. Supported: Amazon S3, Google Cloud Storage, Azure Blob Storage, and platform storage.
# Amazon S3
curl -X POST https://api.eorouter.io/v1/destinations \
  -H "Authorization: Bearer $EOR_API_KEY" \
  -d '{
    "name": "prod-s3", "type": "s3",
    "bucket": "my-eo-data", "region": "eu-west-1",
    "access_key_id": "AKIAxxx", "secret_access_key": "xxx",
    "path_template": "/{year}/{month}/{order_id}/{provider}/{filename}"
  }'

# Google Cloud Storage
curl -X POST https://api.eorouter.io/v1/destinations \
  -d '{"name":"gcs","type":"gcs","bucket":"my-bucket","service_account_json":"..."}'

# Azure Blob
curl -X POST https://api.eorouter.io/v1/destinations \
  -d '{"name":"azure","type":"azure_blob","container":"eo-deliveries","connection_string":"..."}'
Path template tokens: {order_id}, {provider}, {collection_id}, {scene_id}, {filename}, {year}, {month}, {day}.
Webhooks
Register an HTTPS endpoint to receive real-time events. All payloads are signed with HMAC-SHA256. Deliveries are retried with exponential backoff (max 5 attempts) on non-2xx responses.
curl -X POST https://api.eorouter.io/v1/webhooks \
  -H "Authorization: Bearer $EOR_API_KEY" \
  -d '{
    "url": "https://yourapp.io/webhooks/eorouter",
    "event_types": [
      "order.completed", "order.failed",
      "tasking.confirmed", "tasking.acquired",
      "subscription.scene_match"
    ]
  }'

# Verify signatures in Python:
import hmac, hashlib

def verify(payload_bytes, headers, secret):
    sig = headers.get("X-EoRouter-Signature", "")
    parts = dict(p.split("=", 1) for p in sig.split(","))
    signed = parts["t"].encode() + b"." + payload_bytes
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
MCP server
The EO Router MCP server exposes all platform capabilities as named tools. Connect to Claude Desktop, Cursor, Zephyr or any MCP-compatible agent. Included in the Professional plan.
// Claude Desktop — claude_desktop_config.json
{
  "mcpServers": {
    "eo-router": {
      "url":     "https://api.eo-router.com/mcp",
      "headers": { "Authorization": "Bearer eor_live_sk_..." }
    }
  }
}

// Cursor — .cursor/mcp.json
{
  "mcpServers": {
    "eo-router": {
      "url": "https://api.eo-router.com/mcp",
      "env": { "EOR_API_KEY": "eor_live_sk_..." }
    }
  }
}

Available tools: eo_search, eo_get_scene, eo_order_archive, eo_get_order_status, eo_get_order_manifest, eo_tasking_feasibility, eo_order_tasking, eo_create_subscription, eo_get_balance

Zephyr integration
In Zephyr workflows, use the eo_procure node. It wraps search + order with optional human-approval gates.
{
  "type": "eo_procure",
  "id": "get_sar_image",
  "config": {
    "aoi":          "{{workflow.inputs.target_aoi}}",
    "sensor_types": ["sar"],
    "routing":      "highest_resolution",
    "providers":    ["iceye", "capella", "umbra"],
    "budget_cap":   5000,
    "auto_confirm": false
  }
}
Errors & rate limits
All errors return a JSON body with error.code, error.message and an optional error.details array.
StatusCodeMeaning
400invalid_requestMalformed JSON or missing required field
400invalid_geometryAOI not valid GeoJSON or exceeds 10,000 km²
401unauthorizedMissing or invalid API key
402insufficient_balanceBalance too low to complete order
403itar_restrictedITAR collection requires EUD on file
404not_foundScene ID, order ID or resource not found
422provider_errorUpstream provider rejected the request
429rate_limitedRetry after Retry-After seconds
500internal_errorPlatform error — retryable
Rate limits: Starter — 1,000 calls/month. Professional — unlimited. Search: 10 req/sec. Order creation: 5 req/sec. Per API key.