Sentinel-2 at 10 metres, or Sentinel-1 radar straight through cloud, rendered on demand from the Copernicus archive. No account, no API key, no minimum. An unpaid request returns 402 with the exact price. An x402 client pays in USDC and gets the pixels or the numbers.
GET https://sentbot.fly.dev/v1/analyze?index=ndvi&lat=30.6&lon=31.1&km=30&date=2025-07-15
NDVI over a 30 km square of the Nile Delta, best scene from the 30 days before 15 July 2025. Plain curl is enough to see the price.
Free checks first, if you want them:
GET /v1/catalog/search?lat=30.6&lon=31.1&km=30
&from=2025-06-15&to=2025-07-15&maxCloud=30
→ 9 scenes, cloudiest 27 %, clearest 1 %
GET /v1/quote?product=analyze&index=ndvi
&lat=30.6&lon=31.1&km=30&date=2025-07-15
→ { "price": "$0.0300", "width": 512, "height": 512 }
GET /v1/preview?lat=30.6&lon=31.1&km=30&preset=ndvi
→ 384 px PNG, free
{
"x402Version": 2,
"resource": {
"url": "https://sentbot.fly.dev/v1/analyze?index=ndvi&lat=30.6&lon=31.1&km=30&date=2025-07-15",
"mimeType": "application/json"
},
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "30000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x1aabcf0cffb89f8ec05381cbd878fe00e7c14510",
"maxTimeoutSeconds": 120,
"extra": { "name": "USDC", "version": "2" }
}, {
"scheme": "exact",
"network": "eip155:137",
"amount": "30000",
"asset": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"payTo": "0x1aabcf0cffb89f8ec05381cbd878fe00e7c14510",
"maxTimeoutSeconds": 120,
"extra": { "name": "USDC", "version": "2" }
},
…3 more: Arbitrum, Avalanche, Sei]
}
Carried in the PAYMENT-REQUIRED header. The client signs a USDC transfer for exactly this amount. No gas, valid two minutes.
{
"product": "analyze",
"index": "ndvi",
"collection": "sentinel-2-l2a",
"areaKm2": 900,
"timeRange": { "from": "2025-06-15T00:00:00Z",
"to": "2025-07-15T23:59:59Z" },
"stats": {
"mean": 0.412, "min": -0.318, "max": 0.894,
"stDev": 0.241, "validFraction": 0.97,
"percentiles": { "p10": 0.081, "p50": 0.446, "p90": 0.703 },
"classes": { "water_or_shadow": 0.06, "bare": 0.21,
"sparse": 0.18, "moderate": 0.27, "dense": 0.28 },
"histogram": [ …40 bins ]
},
"image": { "format": "png", "width": 512, "height": 512,
"dataUrl": "data:image/png;base64,iVBORw0KGgo…" },
"priceUsd": 0.03
}
Settled only after the render succeeds, so a failure costs nothing. validFraction tells you how much of the square was cloud-free.
| endpoint | what you get | formula | 512² · S2 |
|---|---|---|---|
GET /v1/imagery |
PNG, JPEG or GeoTIFF. Presets: true-color, false-color, swir, agriculture, scene classes, and every index colour-mapped. Radar: vv, vh, rgb, water. A GeoTIFF of an index carries raw FLOAT32 values. | $0.01 × ceil(px / 512²) | $0.011024² $0.04 · 2048² $0.16 |
GET /v1/analyze?index={index} |
Mean, min, max, standard deviation, p5 to p95, a 40-bin histogram, class fractions (bare, sparse, moderate, dense for NDVI) and the colour-mapped PNG. Cloud, shadow and cirrus pixels are excluded. | imagery + $0.02 | $0.03 |
GET /v1/timeseries?index={index} |
The same statistics for every interval between two dates. NDVI every 10 days through a season, water fraction every 6 days through a flood. Up to 3 years and 60 intervals. | $0.01 × intervals | $0.1818 intervals · min $0.02 · max $0.25 |
GET /v1/change?index={index} |
Best scene near date A and near date B, subtracted per pixel. Loss, gain and stable fractions, before and after statistics, a red and green PNG. Radar mode maps floods through cloud. | 2 × imagery + $0.03 | $0.05 |
GET /v1/catalog/searchGET /v1/quoteGET /v1/preview |
Which scenes exist and how cloudy they are. The exact price of any request before you make it. A 384 px thumbnail of any place. | no charge | free |
Vegetation vigour. Bare soil around 0.1, crops 0.3 to 0.8, dense forest above 0.7.
Open water. Values above 0 are usually water, above 0.3 clearly water.
Canopy water content. Negative values mean dry vegetation or bare soil.
Burn severity. Healthy vegetation is high, freshly burned areas drop below 0.1.
Like NDVI but less saturated over dense canopy and less sensitive to atmosphere.
NDVI corrected for soil brightness. Useful over sparse crops and arid land.
Snow cover. Values above 0.4 are snow or ice.
Co-polarised radar backscatter (gamma0, terrain corrected). Water is very dark, urban is bright.
Cross-polarised radar backscatter. Sensitive to vegetation volume and structure.
1 where VV backscatter is below -18 dB (open water). The mean is the water fraction. Works through cloud.
orbit direction for change detection.# free: which scenes exist, and what would it cost curl "https://sentbot.fly.dev/v1/catalog/search?lat=30.6&lon=31.1&km=30&from=2025-07-01&to=2025-07-31&maxCloud=20" curl "https://sentbot.fly.dev/v1/quote?product=analyze&index=ndvi&lat=30.6&lon=31.1&km=30&date=2025-07-15" # paid: with no payment header you get the 402 above curl -i "https://sentbot.fly.dev/v1/analyze?index=ndvi&lat=30.6&lon=31.1&km=30&date=2025-07-15" # operators: the admin key skips the paywall curl -H "x-api-key: $ADMIN_API_KEY" "https://sentbot.fly.dev/v1/imagery?lat=25.118&lon=55.133&km=7&preset=true-color&width=1024" -o dubai.png
import { privateKeyToAccount } from "viem/accounts";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const client = new x402Client().register("eip155:*", new ExactEvmScheme(account)); // or "solana:*" with @x402/svm
const pay = wrapFetchWithPayment(fetch, client);
const res = await pay("https://sentbot.fly.dev/v1/analyze?index=ndvi&lat=30.6&lon=31.1&km=30&date=2025-07-15");
const a = await res.json();
console.log(a.stats.mean, a.stats.classes, a.stats.validFraction);
# pip install x402 eth-account httpx
from eth_account import Account
from x402.clients.httpx import x402HttpxClient
account = Account.from_key(PRIVATE_KEY)
async with x402HttpxClient(account=account) as client:
r = await client.get("https://sentbot.fly.dev/v1/change?index=nbr",
params={"bbox": "-8.7,39.4,-8.5,39.55",
"dateA": "2025-06-01", "dateB": "2025-08-20"})
print(r.json()["difference"]["lossFraction"])
/llms.txtone-screen summary/agent.mdfull guide: parameters, how to pay, how to read the numbers/openapi.jsonOpenAPI 3.1, every parameter and schema/.well-known/x402paid resources and starting prices/v1/productsindices, ranges, class thresholds, limitsPAYMENT-REQUIREDeach 402 carries a bazaar discovery schema and a JSON quote/v1/pricingthe pricing rules as data, including the multiplier in force/healthliveness, plus which subsystems are configured