https://api.eorouter.io/v1Authorization headerFree 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 ID | Provider | Sensor | Best GSD | Tasking |
|---|---|---|---|---|
planet | Planet Labs | OPT | 0.5 m | ✓ |
iceye | ICEYE | SAR X-band | 0.25 m | ✓ |
capella | Capella Space | SAR X-band | 0.35 m | ✓ |
umbra | Umbra | SAR X-band | 0.25 m | ✓ |
airbus | Airbus OneAtlas | OPT | 0.3 m | ✓ |
maxar | Maxar | OPT | 0.3 m | ✓ ITAR |
blacksky | BlackSky | OPT | 0.5 m | ✓ |
cdse | ESA / CDSE | OPT + SAR | 10 m | — |
sentinelhub | Sentinel Hub | OPT + SAR | 10 m | — |
nasa | NASA CMR | OPT | 30 m | — |
usgs | USGS M2M | OPT | 30 m | — |
earthsearch | Earth Search | OPT + SAR | 10 m | — |
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.
eor_live_sk_...— production keys, billed against your balanceeor_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_...
# 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
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.
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.
# 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.
planet | api_key | value: Planet API key |
iceye | jwt_client | client_id + client_secret |
airbus | oauth2_client | client_id + client_secret |
capella | api_key | value: Capella API key |
umbra | api_key | value: Umbra API key |
sentinelhub | oauth2_client | client_id + client_secret |
# 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":"..."}'
{order_id}, {provider}, {collection_id}, {scene_id}, {filename}, {year}, {month}, {day}.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", ""))
// 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
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
}
}error.code, error.message and an optional error.details array.| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed JSON or missing required field |
| 400 | invalid_geometry | AOI not valid GeoJSON or exceeds 10,000 km² |
| 401 | unauthorized | Missing or invalid API key |
| 402 | insufficient_balance | Balance too low to complete order |
| 403 | itar_restricted | ITAR collection requires EUD on file |
| 404 | not_found | Scene ID, order ID or resource not found |
| 422 | provider_error | Upstream provider rejected the request |
| 429 | rate_limited | Retry after Retry-After seconds |
| 500 | internal_error | Platform error — retryable |