API Reference
Base URL: https://api.solana.eo-router.com/v1 โ€” STAC-API 1.0.0 compliant. Paid endpoints use x402 USDC payments on Solana devnet. No API key needed โ€” just a funded wallet.
Solana Platform Overview
EO Router Solana is a satellite imagery marketplace where AI agents pay per API call with USDC on Solana. No accounts, no API keys, no credit cards โ€” just a funded Solana wallet.
Every image ordered through the platform carries a cryptographic provenance proof anchored on Solana, verifiable by anyone. The full trust chain:
LayerTechnologyWhat it proves
1. Data sourceTLSNotary MPC-TLSAPI response came from a specific provider domain (e.g. earth-search.aws.element84.com) โ€” no tampering
2. VerificationRISC Zero zkVMImage metadata matches the order: correct location, time window, and TLSNotary hash โ€” all verified inside a zero-knowledge proof
3. On-chain proofBonsol (Groth16)RISC Zero STARK wrapped to Groth16 SNARK, verified on-chain in <200k compute units
4. SettlementSolana ProgramsUSDC escrow, provenance attestation, and treasury fees โ€” all on Solana devnet

Devnet. This instance runs on Solana devnet. USDC is the Circle devnet faucet token (4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU). No real funds are involved.

x402 USDC Payments
Paid endpoints return HTTP 402 Payment Required with a JSON body describing the payment terms. The client (agent or wallet) constructs a Solana USDC transfer and re-sends the request with an X-PAYMENT header.
# 1. Call a paid endpoint without payment
POST /v1/search
{ "bbox": [8.9, 44.4, 9.0, 44.5], "datetime": "2025-01-01/2025-12-31" }

// โ†’ HTTP 402
{
  "x402Version": 2,
  "error": "Payment required",
  "accepts": [{
    "scheme":   "exact",
    "network":  "solana-devnet",
    "asset":    "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",  // USDC mint
    "payTo":    "ALpKsLNWqg3iPYz13b5ZqExESK4BGsDni9ASszLA7N5K",  // treasury
    "maxAmountRequired": "50000",  // 0.05 USDC (6 decimals)
    "resource": "/v1/search"
  }]
}

# 2. Client signs USDC transfer, re-sends with X-PAYMENT header
POST /v1/search
X-PAYMENT: <base64-encoded Solana transaction>
{ "bbox": [8.9, 44.4, 9.0, 44.5], "datetime": "2025-01-01/2025-12-31" }
// โ†’ HTTP 200 with search results
Pricing per endpoint:
EndpointUSDCNotes
POST /v1/search0.05Per search query
MCP eo_search tool0.05Same as REST search
POST /v1/orders (confirm=true)Scene priceLocked in escrow
GET /v1/collectionsFreeDiscovery endpoint
POST /v1/orders (estimate)FreeDry-run, no charge
POST /v1/tasking/feasibilityFreeFeasibility check
POST /v1/chat0.01AI chat per message

No account needed. Any agent or wallet that can sign a Solana USDC transfer can use the API. The x402 protocol (x402.org) is an open standard by Coinbase for machine-to-machine payments.

On-Chain Escrow
When an agent places a confirmed order, USDC is locked in an on-chain escrow PDA (Program Derived Address). The escrow guarantees the buyer's funds are held until delivery, with automatic refund on expiry.
// Escrow lifecycle

1. lock_funds    โ†’ Buyer's USDC โ†’ escrow PDA vault
                   Stores: order_id, amount, buyer, seller, expiry_ts

2. release_funds โ†’ Escrow vault โ†’ treasury (on successful delivery)
                   Requires: delivery_proof_hash (image manifest SHA-256)
                   Must happen before expiry

3. refund_expired โ†’ Escrow vault โ†’ buyer (if delivery fails)
                    Only callable after expiry_ts passes
Escrow account fields:
FieldTypeDescription
order_idString (32)EO Router order ID
buyerPubkeyBuyer wallet address
sellerPubkeyTreasury wallet
amountu64USDC amount (6 decimals)
expiry_tsi64Unix timestamp โ€” refundable after this
deliveredboolSet true on release_funds
delivery_proof_hash[u8; 32]SHA-256 of delivered image manifest

Escrow PDA is derived from ["escrow", order_id]. Anyone can look up an escrow on Solana Explorer to verify locked funds.

TLSNotary MPC-TLS
TLSNotary uses multi-party computation (MPC) to cryptographically prove that data came from a specific TLS server โ€” without revealing the TLS session keys. This proves the satellite provider's API response is authentic and untampered.
EO Router runs a TLSNotary sidecar using the tlsn crate (v0.1.0-alpha.14). The Prover and Verifier run in-process connected via tokio::io::duplex. This is real MPC-TLS, not a simulation.
# TLSNotary prove endpoint (internal sidecar, port 8090)
POST /prove
{
  "url": "https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a/items/S2A_...",
  "image_hash": "593674d2b62d017d07b41a24b6bfa9d7..."
}

// โ†’ Response
{
  "mpc_tls":            true,
  "presentation_hash":  "32bd574b0a1288...",   // SHA-256 of verified transcript + domain
  "provider_domain":    "earth-search.aws.element84.com",
  "binding_hash":       "8dfe6f11437103...",   // sha256(image_hash || tlsn_hash || scene_id_hash)
  "revealed_recv_bytes": 18984,              // bytes of TLS transcript revealed
  "scene_id":           "S2A_T32TQM_20250115T103251_L2A",
  "lat": 44.4, "lon": 8.9,
  "capture_timestamp":  1742054400
}
How MPC-TLS works:
1. The Prover connects to the provider's HTTPS server through a TLS channel that is jointly controlled by Prover and Verifier via MPC.
2. Neither party alone has the TLS session key โ€” they split the key via 2-party computation.
3. The Verifier can confirm the server's TLS certificate chain (proving the domain) and the response data, without the Prover being able to forge anything.
4. The Prover uses selective disclosure to reveal only the API response while redacting auth headers.

Verified against real providers: Earth Search (18,984 bytes, 1.8s), NASA CMR (11,553 bytes, 1.3s). Logs show starting MPC-TLS โ†’ finished MPC-TLS for every proof.

ZK Provenance (RISC Zero + Bonsol)
After TLSNotary authenticates the provider response, a RISC Zero zkVM guest program verifies the provenance claims inside a zero-knowledge proof. The guest is written in normal Rust and compiled to RISC-V.
The guest verifies three checks:
CheckWhat it doesFailure means
TLSNotary hash matchPrivate tlsnotary_presentation_hash == public expected_tlsn_hashMetadata was not authenticated by TLSNotary
Spatial (AOI)|lat - aoi_center_lat| <= tolerance AND |lon - aoi_center_lon| <= toleranceImage was captured outside the requested area of interest
Temporal (window)timestamp >= time_start AND timestamp <= time_endImage was captured outside the requested time window
// RISC Zero guest output (committed to journal, public on-chain)
{
  "image_hash":      [u8; 32],    // SHA-256 of delivered image
  "provenance_valid": true,        // all 3 checks passed
  "aoi_center_lat":   44400000,   // ร— 1e6
  "aoi_center_lon":   8900000,    // ร— 1e6
  "tlsnotary_hash":   [u8; 32],    // TLSNotary presentation hash
  "binding_hash":     [u8; 32]     // sha256(image || tlsn || scene_id)
}

// Guest compiled to RISC-V ELF (220 KB)
// IMAGE_ID: 2c8c4671fc085bd1d54580eb986671bd790f13ad8f1cc09d30649e5d1c5033bd
// Registered with Bonsol on Solana devnet
Bonsol handles the full ZK pipeline: RISC Zero STARK proof (off-chain, ~30s) โ†’ Groth16 SNARK wrapping โ†’ on-chain Solana verification in <200k compute units. No trusted setup required.
Trust Chain
Every delivered image has an end-to-end cryptographic trust chain from the provider's TLS connection to an on-chain attestation.
Provider TLS  โ†’  TLSNotary (MPC-TLS)  โ†’  RISC Zero zkVM  โ†’  Bonsol (Groth16)  โ†’  Solana

Step 1: Provider HTTPS API response authenticated via real MPC-TLS
        โ†’ presentation_hash = SHA-256(verified_transcript || domain)

Step 2: RISC Zero guest verifies: TLSNotary hash match + AOI check + time window
        โ†’ STARK proof of correct execution

Step 3: Bonsol wraps STARK โ†’ Groth16 SNARK (constant-size, cheap to verify on-chain)

Step 4: eo_provenance program stores attestation on Solana:
        โ†’ image_hash, binding_hash, tlsnotary_hash, scene_id, coordinates, timestamp

Binding hash: sha256(image_hash || tlsnotary_hash || scene_id_hash)
               Ties the image file to its provenance proof โ€” anyone can recompute and verify

The binding hash is the key verifiability primitive. Given an image file and its provenance record on Solana, anyone can: (1) SHA-256 the image, (2) read the on-chain attestation, (3) recompute the binding hash, and (4) confirm it matches. No trust in EO Router required.

Solana Programs
Three Anchor programs deployed on Solana devnet:
ProgramAddressPurpose
eo_escrow2widvLF5ZU3gXcoyHLMd8EeghwSudbZjiLKGUCfWNLX3USDC lock/release/refund for orders
eo_treasury6THM4f1Cqbpn9Uhb57fgNw42e9hYtHyahpftEifhdmg7Platform fee collection + OpenRouter sweep
eo_provenanceCeeXzXw3XEYXNgL6CmhRWy9qYGJG857Zkea3nyb3R9BqZK attestation registry (Bonsol callback)
All programs are viewable on Solana Explorer (devnet). Escrow PDAs derived from ["escrow", order_id], provenance PDAs from ["provenance", order_id].
Bonsol ZK Verifier: Guest image registered on-chain with Bonsol program (BoNsHRcyLLNdtnoDf8hiCNZpyehMC4FDMxs6NTxFi3ew). Deployment account: 5rz7EQjdGei34ehsnNCR61y9ntsdJaWJ3ViTRnR2XQwr.
Treasury Flywheel
A self-sustaining business model: USDC fees collected from x402 payments and escrow releases accumulate in the treasury program. When the balance exceeds a threshold, it sweeps to fund OpenRouter API credits โ€” which power the AI chat and agent capabilities.
// The flywheel

  Agent pays USDC  โ†’  x402 / escrow  โ†’  Treasury PDA  โ†’  sweep_to_openrouter
        โ†‘                                                        โ†“
        โ””โ”€โ”€โ”€โ”€ AI-powered search, chat, MCP tools โ†โ”€โ”€ OpenRouter credits

// Treasury instructions
receive_fee(order_id, fee_amount)       // 2.5% platform fee from each order
sweep_to_openrouter(amount)             // USDC โ†’ OpenRouter wallet when threshold hit
set_sweep_threshold(new_threshold)      // governance (authority only)

// Treasury wallet
ALpKsLNWqg3iPYz13b5ZqExESK4BGsDni9ASszLA7N5K

The flywheel is the core economic model: AI agents pay for satellite imagery โ†’ fees fund the AI that helps them find and order imagery. Self-reinforcing.

Authentication
All API endpoints require authentication via Bearer token. You can use either a JWT (from login/signup) or an API key.
# 1. Sign up
POST /v1/auth/signup
{ "email": "you@company.com", "password": "SecurePass10+", "name": "Your Name" }
// โ†’ { "access_token": "eyJ...", "user": { ... } }

# 2. Create an API key (recommended for production)
POST /v1/keys
{ "name": "CI/CD pipeline", "type": "live" }
// โ†’ { "full_key": "eor_live_sk_a1b2c3..." }  โ† save this! shown ONCE

# 3. Use in all requests
Authorization: Bearer eor_live_sk_a1b2c3...

API keys are bcrypt-hashed โ€” the full key is only returned at creation. Store it in a secrets manager immediately. Test keys (eor_test_sk_...) work identically but incur no real charges.

Provider Reference
EO Router connects to 14 satellite imagery providers. Commercial providers are billed per kmยฒ; free providers serve open data at no cost.
ProviderIDSensorGSDFreeTaskingBYOKCredential Type
Planet Labsplanetoptical3m / 0.5mNoYesYesapi_key
ICEYEiceyesar0.5m / 3mNoYesYesoauth2_client
Capella Spacecapellasar0.35mNoYesYesapi_key
Umbraumbrasar0.25mPartialYesYesapi_key
Airbusairbusoptical0.3m / 1.5mNoYesYesoauth2_client
Maxarmaxaroptical0.31mNoYesNoapi_key
BlackSkyblackskyoptical0.5mNoYesYesapi_key
Earth Searchearthsearchoptical10m / 30mYesNoNoโ€”
ESA / CDSEcdseoptical, sar10m / 5mYesNoNoโ€”
NASA CMRnasaoptical30mYesNoNoโ€”
USGS M2Musgsoptical30mYesNoNoโ€”
Sentinel Hubsentinelhuboptical10mYesNoYesoauth2_client
Google Earth Enginegeeoptical, sar500m / 10mYesNoYesservice_account
LGND.ailgndoptical, sarvariesYesNoYesbearer_token

Maxar WV-3 / WV-4: ITAR-controlled. Only available to users with country_code: "US" in their profile. See ITAR Compliance section.

GET /collections
Returns all available collections across all providers. Filter by provider, sensor type and access tier.
GET /v1/collections?provider=iceye&sensor_type=sar&limit=20
GET /v1/collections/iceye_spotlight_grd
Orders
Archive orders purchase existing scenes. The order amount is pre-authorized on confirm and captured on delivery against actual clipped area.
POST /v1/orders
{
  "type": "archive",
  "items": [{ "scene_id": "ps_20260315_..." }],
  "processing": { "format": "COG", "clip_to_aoi": true, "aoi": {...} },
  "delivery": { "type": "s3", "destination_id": "dest_prod" },
  "license_accepted": true,
  "confirm": true   // false = estimate only
}
// โ†’ 202 { "order_id": "ord_7e2a1b4d", "status": "queued", "amount_eur": 89.4 }
Status lifecycle: queued โ†’ running โ†’ processing โ†’ delivering โ†’ completed (or failed โ€” amount released)
MethodPathDescription
POST/v1/ordersCreate order (confirm=false for estimate)
GET/v1/ordersList orders, paginated
GET/v1/orders/{id}Order status and event log
GET/v1/orders/{id}/manifestDelivery manifest with checksums
DELETE/v1/orders/{id}Cancel (pre-delivery only)
Tasking
Task a satellite for a future acquisition. Call feasibility first (free) to get opportunity windows and pricing from all 7 commercial providers: ICEYE, Capella, Planet, Airbus, Maxar, Umbra, BlackSky. Then confirm with orders.
// Step 1: feasibility (free, no charge)
POST /v1/tasking/feasibility
{
  "aoi": { ... },
  "time_window": { "from": "2026-03-22T00:00:00Z", "to": "2026-03-28T23:59:59Z" },
  "requirements": { "sensor_type": "sar", "max_gsd_m": 1.0, "orbit_direction": "ascending" },
  "providers": ["iceye", "capella", "umbra"],
  "priority": "standard",  // standard (1x) | priority (1.75x) | rush (3x)
  "budget_cap": 2000       // optional: max EUR per opportunity
}

// Step 2: order from opportunity_id
POST /v1/tasking/orders
{
  "opportunity_id": "topp_iceye_...",
  "estimated_eur": 1240.5,  // from feasibility response
  "confirm": true                 // false = estimate only
}
Priority cost multipliers: standard = 1x base cost, priority = 1.75x (faster scheduling), rush = 3x (next available pass).
Deviation alerts: Each opportunity includes a deviations array warning you when it differs from your request:
DeviationSeverityDescription
time_shiftwarningOpportunity is outside the requested time window
gsd_exceededwarningResolution is coarser than requested max_gsd_m
off_nadir_exceededwarningOff-nadir angle exceeds requested maximum
orbit_direction_mismatchinfoOrbit direction differs from preference
budget_exceededcriticalEstimated cost exceeds budget_cap
high_off_nadirinfoOff-nadir > 40ยฐ (geometric distortion risk)

The response also includes a top-level warnings array for systemic issues (e.g. all opportunities shifted, all exceed budget, provider API failures).

Subscriptions
Monitor an AOI for new matching imagery. Set auto_order: true with budget caps to purchase and deliver every match automatically.
POST /v1/subscriptions
{
  "name": "port-genoa-sar",
  "aoi": { ... },
  "filter": { "sensor_types": ["sar"], "max_gsd_m": 3.0 },
  "providers": ["iceye", "cdse"],
  "auto_order": true,
  "auto_order_config": {
    "delivery_destination_id": "dest_prod",
    "max_eur_per_match": 200,
    "daily_eur_cap": 500
  }
}

// Pause/resume
PATCH /v1/subscriptions/{id}
{ "status": "paused" }
BYOK Credentials
Bring Your Own Key (BYOK) lets you use your own provider API credentials. Orders route directly to the provider at wholesale cost with a 10% routing fee (vs 2.5% platform fee without BYOK).
POST /v1/credentials
{
  "provider": "planet",
  "type": "api_key",
  "name": "Production Planet key",
  "value": "PLAKxxxxxxxxxxxxxxxx"
}

// For OAuth2 providers (ICEYE, Airbus, SentinelHub):
POST /v1/credentials
{
  "provider": "iceye",
  "type": "oauth2_client",
  "name": "ICEYE production",
  "client_id": "your-client-id",
  "client_secret": "your-client-secret"
}
ProviderCredential TypeFields
Planetapi_keyvalue
Capellaapi_keyvalue
Maxarapi_keyvalue
BlackSkyapi_keyvalue
Umbraapi_keyvalue
ICEYEoauth2_clientclient_id + client_secret
Airbusoauth2_clientclient_id + client_secret
Sentinel Huboauth2_clientclient_id + client_secret

All credentials are encrypted at rest with AES-256-GCM. The plaintext is never stored or logged.

Destinations
Pre-configure cloud delivery destinations for orders and subscriptions. Credentials are encrypted at rest.
// Amazon S3
POST /v1/destinations
{
  "name": "Production S3",
  "type": "s3",
  "bucket": "my-imagery-bucket",
  "region": "eu-west-1",
  "access_key_id": "AKIA...",
  "secret_access_key": "wJa...",
  "path_template": "imagery/{date}/{scene_id}.tif"
}

// Google Cloud Storage
{ "type": "gcs", "bucket": "...", "service_account_json": "{...}" }

// Azure Blob Storage
{ "type": "azure_blob", "container": "...", "connection_string": "..." }

// Platform (download from dashboard โ€” no config needed)
{ "type": "platform", "name": "Dashboard download" }
Path template placeholders: {order_id}, {scene_id}, {date}
Webhooks
Receive real-time notifications for order and tasking events. Payloads are signed with HMAC-SHA256.
POST /v1/webhooks
{
  "url": "https://your-server.com/hooks/eorouter",
  "event_types": ["order.completed", "order.failed", "tasking.acquired"]
}
// โ†’ { "signing_secret": "whsec_a1b2c3..." }  โ† save this! shown ONCE
Event types:
EventWhen
order.queuedOrder accepted and amount pre-authorized
order.completedDelivery finished, amount captured
order.failedOrder failed, amount released
tasking.confirmedTasking order accepted by provider
tasking.acquiredSatellite has acquired the image
subscription.matchNew imagery matches subscription criteria
Signature verification (Python):
import hmac, hashlib

def verify_webhook(payload: bytes, header: str, secret: str) -> bool:
    # header format: "t=1711234567,v1=a1b2c3d4..."
    parts = dict(p.split("=", 1) for p in header.split(","))
    expected = hmac.new(
        secret.encode(), f'{parts["t"]}.'.encode() + payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Failed deliveries are retried up to 5 times with exponential backoff (1min, 5min, 30min, 2h, 12h).

Jobs
Every async operation produces an immutable job with an append-only event log.
GET /v1/jobs/{job_id}

{
  "job_id": "job_8f3a2b1c",
  "status": "completed",
  "events": [
    { "type": "job_created",   "time": "10:31:00Z", "message": "89.4 Cr pre-authorized" },
    { "type": "provider_queued", "time": "10:31:08Z", "message": "ICEYE order queued" },
    { "type": "job_completed",  "time": "11:04:32Z", "message": "87.3 Cr captured for 10.9 kmยฒ" }
  ]
}
GET /providers
GET /v1/providers

{ "providers": [{ "id": "iceye", "status": "operational", "uptime_30d": 99.2, "tasking": true, "byok": true }] }
POST /embeddings
Retrieve 64-dimensional satellite embeddings from Google DeepMind's AlphaEarth Foundations. Embeddings capture land cover, vegetation, and built environment features at 10m resolution. Coverage: global, 2017-2025.
POST /v1/embeddings
{
  "bbox": [11.34, 44.49, 11.36, 44.50],  // max ~100 kmยฒ
  "year": 2024,                              // 2017-2025
  "resolution": 10                           // meters (10m native)
}

// Response
{
  "bbox": [11.34, 44.49, 11.36, 44.50],
  "year": 2024,
  "resolution": 10,
  "dimensions": 64,
  "pixel_count": 22,
  "pixels": [
    { "lon": 11.3405, "lat": 44.4995, "embedding": [0.12, -0.34, ...] },
    // ... 64 floats per pixel
  ]
}
Use cases: similarity search, land cover classification, change detection, feature extraction for ML pipelines.

AlphaEarth embeddings are served by the platform โ€” no credentials required from your side. Max bbox area: ~100 kmยฒ. Rate limit: 10 requests/minute.

LGND.ai โ€” Similarity Search & Change Detection
LGND.ai provides embedding-based similarity search, chip-level search, and AI change detection on satellite and aerial imagery. Free tier covers California NAIP (2020-2022) and France Sentinel-2 (2024-2025). All endpoints are fully integrated into the EO Router API.
POST /lgnd/search
Find similar image chips by location.
POST /v1/lgnd/search
{
  "latitude": 37.77,
  "longitude": -122.42,
  "top_k": 5,
  "start_date": "2022-01-01",
  "end_date": "2022-12-31"
}

// Response
{
  "query": { "latitude": 37.77, "longitude": -122.42, "top_k": 5 },
  "results": [
    { "chip_id": "naip_ca_2022_abc123", "score": 0.94, "metadata": {...} }
  ],
  "distance_metric": "cosine"
}
POST /lgnd/search-by-chip
Find visually similar chips by chip ID from previous results.
POST /v1/lgnd/search-by-chip
{
  "chip_id": "naip_ca_2022_abc123",
  "top_k": 10
}
POST /lgnd/changes
Detect changes using text descriptions of past/current states. Returns chip pairs (before + after) with scores.
POST /v1/lgnd/changes
{
  "geometry": { "type": "Polygon", "coordinates": [[...]] },
  "positive_texts_past": ["empty field"],
  "positive_texts_current": ["new construction"],
  "negative_texts_past": ["forest"],
  "negative_texts_current": ["forest"],
  "positive_chip_ids_past": ["chip_id_example"],
  "positive_chip_ids_current": ["chip_id_example"],
  "negative_chip_ids_past": [],
  "negative_chip_ids_current": [],
  "top_k": 10
}

// Response โ€” changed chip pairs
{
  "results": [
    { "chip_past": "s2_2024_a1", "chip_current": "s2_2025_a1", "cell_id": "cell_42", "score": 0.87 }
  ],
  "distance_metric": "cosine"
}
POST /lgnd/filter
Filter chips within a GeoJSON geometry. Returns chip IDs ordered by capture date.
POST /v1/lgnd/filter
{
  "geometry": { "type": "Polygon", "coordinates": [[...]] },
  "start_date": "2022-01-01",
  "limit": 100
}

// Response โ€” chip IDs ordered by capture date
{
  "chips": ["naip_ca_2022_abc123", "naip_ca_2022_abc124"],
  "total": 2
}
GET /lgnd/collections
List available LGND collections (imagery sources, date ranges, coverage areas).

LGND is fully integrated โ€” authenticate with your EO Router API key, no separate LGND token needed. Rate limit: 10 requests/minute. Free tier: read-only collections only.

Balance
Your account balance is in EUR. Amounts are pre-authorized on order confirm and captured on delivery against actual clipped area.
GET /v1/credits/balance
// โ†’ { "balance_eur": 348.60, "reserved_eur": 89.40, "available_eur": 259.20 }

GET /v1/credits/transactions?limit=20

// Cost formula:
cost_eur = price_eur_per_km2 * clipped_area_km2 * 1.025  // 2.5% platform fee

// BYOK routing fee:
routing_fee = max(area_km2 * 0.10, 0.50)  // EUR
Routing modes
ModeDescription
best_availableComposite: 40% spatial, 20% freshness, 20% resolution, 10% cloud, 10% price. Default.
cheapest_matchingLowest EUR cost. All constraints still applied.
freshest_matchingMost recent acquisition first.
highest_resolutionFinest GSD first. Price ignored.
sar_fallback_if_cloudOptical if cloud โ‰ค threshold; SAR otherwise.
open_data_firstFree/open providers ranked above commercial.
multi_provider_compareTop 3 per provider for side-by-side comparison.
SAR metadata
All SAR scenes expose normalized fields regardless of provider. Usable in CQL2-JSON filters.
PropertyTypeDescription
sar:frequency_bandstringX (ICEYE, Capella, Umbra) ยท C (Sentinel-1)
sar:product_typestringGRD ยท SLC ยท SICD ยท SIDD ยท CPHD
sar:polarizationsarray["HH"], ["VV"], ["VV","VH"]
sar:looks_rangeintegerNumber of looks in range direction
eorouter:sar.orbit_directionstringascending or descending
eorouter:sar.look_directionstringleft or right
eorouter:sar.mode_namestringSpotlight, Stripmap, ScanSAR, Dwellโ€ฆ
eorouter:sar.nesz_dbnumberNoise Equivalent Sigma Zero (lower = better)
eorouter:sar.coherence_availablebooleanInterferometric product available
STAC compliance
The /v1/search endpoint passes the STAC API validator. Any STAC-aware client works with no custom code.
from pystac_client import Client

catalog = Client.open(
    "https://api.eorouter.io/v1",
    headers={"Authorization": f"Bearer {os.environ['EOR_API_KEY']}"}
)
items = catalog.search(intersects=aoi, datetime="2026-03-01/2026-03-21").get_items()
Extensions: EO (eo:cloud_cover), SAR (sar:frequency_band), View (view:off_nadir), Processing (processing:level), EO Router (eorouter: namespace).
Processing options
ParameterDefaultDescription
formatCOGCOG ยท GeoTIFF ยท NITF ยท SLC ยท SICD ยท SIDD
clip_to_aoitrueClip to AOI polygon โ€” reduces cost
reproject_epsgnullReproject to EPSG; null = provider default
harmonize_srfalsePlanet: normalize to surface reflectance
radiometric_correctionsigma0SAR: beta0 ยท sigma0 ยท gamma0
compressdeflateCOG: deflate ยท lzw ยท lerc ยท none

COG deliveries include auto-generated overview levels (256ร—256 tiles, levels 2โ€“5ร—). GDAL, QGIS and rio-cogeo read them natively.

ITAR Compliance
Maxar WorldView-3 (WV-3) and WorldView-4 (WV-4) sub-50cm imagery is classified as defense articles under 15 USC ยง2778 (Arms Export Control Act / ITAR).
RestrictionDetails
Affected collectionsmaxar_wv03, maxar_wv04
RequirementUser must have country_code: "US" in their profile
Error403 itar_restriction for non-US users
ScopeOrdering only โ€” search and browse are unrestricted
// Set your country code
PATCH /v1/auth/me
{ "country_code": "US" }

// Non-US users ordering Maxar WV-3 will get:
// 403 { "code": "itar_restriction", "message": "...ITAR-controlled..." }
Error Codes
All errors return a JSON body with code and message fields.
CodeHTTPWhen
itar_restriction403Non-US user ordering Maxar WV-3 / WV-4
insufficient_balance402Available EUR balance is less than order cost
email_taken409Signup with an email that already exists
invalid_credentials401Login with wrong email or password
wrong_password401Password change with incorrect current password
not_found404Resource does not exist or belongs to another user
invalid_geometry422Non-Polygon geometry or missing AOI
license_not_accepted400Order placed without license_accepted: true
order_not_cancellable409Cancel attempt on completed/delivering order
stripe_not_configured501Stripe payment not configured on server
oauth_not_configured501OAuth provider not configured on server
Tutorial: Change detection pipeline
from eorouter import EoRouterClient
client = EoRouterClient(api_key="...")

before = client.search(aoi=aoi, date_from="2026-01-01", date_to="2026-01-31",
                     max_cloud_cover=15, limit=1)[0]
after  = client.search(aoi=aoi, date_from="2026-03-01", date_to="2026-03-21",
                     max_cloud_cover=15, limit=1)[0]

order = client.orders.create(
    scene_ids=[before.scene_id, after.scene_id],
    processing={"format":"COG", "clip_to_aoi":True, "aoi":aoi},
    delivery={"type":"s3", "destination_id":"dest_prod"},
    confirm=True)

order.wait(timeout_minutes=90)
print([i["s3_key"] for i in order.manifest()["items"]])
Tutorial: SAR tasking workflow
opps = client.tasking.feasibility(
    aoi=aoi,
    time_window={"from":"2026-03-22T00:00:00Z", "to":"2026-03-27T23:59:59Z"},
    requirements={"sensor_type":"sar", "min_gsd_m":3.0, "orbit_direction":"ascending"},
    providers=["iceye", "capella"], priority="standard"
)
best = min(opps, key=lambda o: o["estimated_eur"])
task = client.tasking.order(opportunity_id=best["opportunity_id"], confirm=True)
# Webhooks: tasking.confirmed โ†’ tasking.acquired โ†’ order.completed
Tutorial: AOI monitoring with auto-order
sub = client.subscriptions.create(
    name="genoa-port-sar", aoi=port_aoi,
    filter={"sensor_types":["sar"], "max_gsd_m":3.0},
    providers=["iceye", "cdse"],  # cdse = Sentinel-1, free
    auto_order=True,
    auto_order_config={
        "delivery_destination_id": "dest_prod",
        "processing": {"format":"COG", "clip_to_aoi":True},
        "max_eur_per_match": 300, "daily_eur_cap": 600,
    })
Tutorial: Agent workflow via MCP
// Claude Desktop (MCP connected) conversation:

User: "Find best SAR over Suez Canal last 72h, ascending orbit,
       cheapest, order it."

Claude calls:
  eo_search(aoi=suez, sensor_types=["sar"], days=3, routing="cheapest_matching")
  eo_get_balance()
  eo_order_archive(scene_ids=["iceye_..."], confirm=False)  // estimate first

Claude: "Found ICEYE Stripmap (3m, ascending, 8h ago) โ€” โ‚ฌ312.00.
         Your balance is โ‚ฌ1,240. Shall I place the order?"

User: "Yes, confirm it."

Claude calls:  // MCP clients can set allow_orders in their implementation
  eo_order_archive(scene_ids=["iceye_..."], confirm=True)
  eo_get_order_status(order_id="ord_...")

Claude: "Order placed. ICEYE scene queued for delivery to
         s3://your-bucket/... โ€” โ‚ฌ312.00 charged."

The full MCP tool schema is at GET https://api.eo-router.com/mcp/schema.

AI Chat (Beta)
BETA
EO Router provides an optional hosted AI assistant powered by Claude. Send natural language queries and the AI will search imagery, check feasibility, query balances, and more using the same tools available via MCP.
Disabled by default. Enable in Dashboard โ†’ Settings โ†’ AI Chat (Beta). No additional API keys required โ€” the AI service is provided by EO Router.

Rate limit: 5 requests per minute. AI calls use Claude Haiku to minimize latency. This is a beta feature โ€” responses may be imperfect.

POST /chat
Send a natural language query to EO Router's AI assistant.
POST /v1/chat
{
  "message": "Find Sentinel-2 images over Paris from last week with less than 20% cloud cover",
  "conversation_id": null,       // optional, for multi-turn
  "allow_orders": false          // see Order Safety below
}

// Response
{
  "response": "I found 8 Sentinel-2 scenes over Paris from the last 7 days...",
  "tool_calls": [
    { "tool": "eo_search", "result_summary": "8 scenes found" }
  ],
  "conversation_id": "conv_abc123",
  "beta": true
}
Order Safety
By default, the AI can only provide cost estimates โ€” it cannot place real orders or spend credits. When the AI estimates an order, the response includes _requires_approval: true.
To let the AI place a confirmed order, set allow_orders: true in your request. Use this only after your app has obtained explicit user approval (e.g. a confirmation dialog).
// Step 1: User asks to order โ€” AI returns estimate (allow_orders=false, default)
POST /v1/chat
{ "message": "Order that Sentinel-2 scene over Paris" }
// โ†’ AI calls eo_order_archive(confirm=false), returns estimate + _requires_approval

// Step 2: Your app shows the estimate, user clicks "Confirm"

// Step 3: Re-send with allow_orders=true
POST /v1/chat
{ "message": "Yes, confirm the order", "allow_orders": true }
// โ†’ AI places the order, charges EUR balance
Error Codes
CodeHTTPWhen
chat_disabled403AI Chat not enabled for your account
chat_unavailable503AI Chat service temporarily unavailable
chat_error500Unexpected AI processing error