API Documentation
Sentinel-2 crop health data for any field, anywhere. Clean JSON, typed responses, 10m resolution.
Authentication
Every request must include your API key in the X-API-Key header.
X-API-Key: agrosat_live_YOUR_KEY_HEREKey format: agrosat_live_ + 32 hex characters. Keys are stored as SHA-256 hashes — we cannot retrieve a lost key. Generate a new key if needed.
Quick Start
Analyze a field near Dar es Salaam in one request:
curl -X POST https://api.agrosat.dev/v1/field/analyze \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"latitude": -6.7924,
"longitude": 39.2083,
"radius_meters": 500
}'Try It Live
Paste your API key and fire a real request directly from this page. Pre-filled with Dar es Salaam coordinates so you can run your first call in seconds.
Response will appear here...Default coordinates: Dar es Salaam coast, Tanzania (-6.7924, 39.2083). Any valid global coordinate works.
/v1/field/analyze
The core data endpoint. Fetches the most recent cloud-free Sentinel-2 image for the given coordinates and returns NDVI, NDWI, and a crop health score.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | yes | Analysis radius in metres. Typically 250-1000 for smallholder farms |
Example
curl -X POST https://api.agrosat.dev/v1/field/analyze \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"latitude": -6.7924,
"longitude": 39.2083,
"radius_meters": 500
}'Response 200
{
"data": {
"ndvi": 0.62,
"ndwi": 0.18,
"health_score": 74,
"health_label": "Good",
"cloud_cover_pct": 12,
"imagery_date": "2025-04-18",
"coordinates": { "latitude": -6.7924, "longitude": 39.2083 },
"radius_meters": 500,
"cached": false
},
"error": null
}Health label mapping
/v1/field/timeseries
Returns all cloud-free Sentinel-2 observations for a field over the last 30 days. One data point per satellite pass. With Sentinel-2A and 2B combined, expect 4-6 readings per month. When two tiles overlap on the same day, only the observation with lower cloud cover is returned.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | no | Analysis radius in metres (100-10000). Default: 500 |
Example
curl "https://api.agrosat.dev/v1/field/timeseries?latitude=-8.2394&longitude=39.0033&radius_meters=500" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"coordinates": { "latitude": -8.2394, "longitude": 39.0033 },
"radius_meters": 500,
"period_days": 30,
"data_points": [
{
"date": "2026-04-29",
"ndvi": 0.5614,
"ndwi": -0.4956,
"health_score": 70,
"health_label": "Moderate",
"cloud_cover_pct": 20.7
},
{
"date": "2026-05-04",
"ndvi": 0.3469,
"ndwi": -0.3069,
"health_score": 43,
"health_label": "Fair",
"cloud_cover_pct": 25.5
}
]
},
"error": null
}Results are cached for 24 hours per coordinate. New Sentinel-2 scenes arrive every ~5 days.
/v1/field/anomaly
Compare the current NDVI against the historical average for the same calendar month across the past 3 years. Detects whether a field is performing unusually relative to its own baseline — not just a poor reading, but an anomalous one. Historical baselines are cached for 30 days; the first call per location is slower (~3 extra satellite queries).
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | no | Analysis radius in metres (100-10000). Default: 500 |
Example
curl "https://api.agrosat.dev/v1/field/anomaly?latitude=-6.7924&longitude=39.2083&radius_meters=500" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"coordinates": { "latitude": -6.7924, "longitude": 39.2083 },
"radius_meters": 500,
"current_month": "2026-05",
"current_ndvi": 0.3469,
"historical_mean": 0.6102,
"historical_years": [2023, 2024, 2025],
"historical_sample_count": 7,
"deviation_pct": -43.1,
"anomaly": "Anomalous Low",
"signal": "NDVI is 43% below the historical May average. Likely drought or crop failure."
},
"error": null
}Counts as 1 call. Anomaly labels: Anomalous Low | Below Normal | Normal | Slightly Above Normal | Above Normal.
/v1/batch/timeseries
Return 30-day NDVI timeseries for up to 20 fields in a single request. Each field counts as one call. Per-field errors are returned inline — one missing field does not fail the whole batch.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| fields | array | yes | Array of field objects (max 20). Each: { latitude, longitude, radius_meters } |
| period_days | integer | no | History window in days (7-90). Default: 30 |
| stream | boolean | no | Set true to receive results as NDJSON — each field streams back as it completes instead of waiting for all. Default: false |
Example
curl -X POST https://api.agrosat.dev/v1/batch/timeseries \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"fields": [
{"latitude": -6.7924, "longitude": 39.2083, "radius_meters": 500},
{"latitude": -3.3869, "longitude": 36.683, "radius_meters": 500}
],
"period_days": 30
}'Response 200
{
"data": [
{
"index": 0,
"data": {
"coordinates": { "latitude": -6.7924, "longitude": 39.2083 },
"radius_meters": 500,
"period_days": 30,
"data_points": [
{ "date": "2026-04-29", "ndvi": 0.5614, "ndwi": -0.4956,
"health_score": 70, "health_label": "Moderate", "cloud_cover_pct": 20.7 },
{ "date": "2026-05-04", "ndvi": 0.3469, "ndwi": -0.3069,
"health_score": 43, "health_label": "Fair", "cloud_cover_pct": 25.5 }
]
},
"error": null
}
],
"error": null
}Counts as N calls. Maximum 20 fields (timeseries fetches are heavier than single-scene analysis). See the Streaming section for NDJSON format.
/v1/field/flood-risk
Analyse NDWI trends over the last 30 days to detect flooding or waterlogging. NDWI uses the green and NIR bands — positive values indicate water presence. A sharp upward spike signals a flood event; sustained positive NDWI signals waterlogging. Returns none / watch / alert with the NDWI readings that triggered it.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | no | Analysis radius in metres (100-10000). Default: 500 |
Example
curl "https://api.agrosat.dev/v1/field/flood-risk?latitude=-6.7924&longitude=39.2083&radius_meters=500" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"coordinates": { "latitude": -6.7924, "longitude": 39.2083 },
"radius_meters": 500,
"flood_risk": "watch",
"ndwi_current": 0.18,
"ndwi_baseline": -0.31,
"ndwi_delta": 0.49,
"observations": [
{ "date": "2026-04-29", "ndwi": -0.31, "cloud_cover_pct": 20.7 },
{ "date": "2026-05-04", "ndwi": 0.18, "cloud_cover_pct": 25.5 }
],
"signal": "NDWI rose from -0.310 to 0.180 (delta +0.490). Possible waterlogging — monitor closely."
},
"error": null
}Counts as 1 call. Results cached 24 hours. Risk thresholds: alert = NDWI > 0.3 or delta > 0.4; watch = NDWI > 0.1 or delta > 0.2.
/v1/field/growing-season
Detect crop phenological events from a 12-month NDVI curve. Analyses up to 365 days of cloud-free Sentinel-2 scenes to identify the planting date (NDVI crosses 0.20 rising), peak greenness date (grain fill), senescence start (NDWI drops 0.15 below peak), and estimated harvest date. First call per location fetches the full year of imagery — subsequent calls within 7 days are instant from cache.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | no | Analysis radius in metres (100-10000). Default: 500 |
Example
curl "https://api.agrosat.dev/v1/field/growing-season?latitude=-6.7924&longitude=39.2083&radius_meters=500" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"coordinates": { "latitude": -6.7924, "longitude": 39.2083 },
"radius_meters": 500,
"growth_stage": "Vegetative",
"planting_date": "2026-03-15",
"peak_date": "2026-05-10",
"peak_ndvi": 0.71,
"senescence_date": null,
"harvest_estimate": null,
"current_ndvi": 0.58,
"observations_used": 22,
"signal": "Growth stage: Vegetative. Planting detected around 2026-03-15. Peak greenness 2026-05-10 (NDVI 0.710)."
},
"error": null
}Counts as 1 call. Results cached 7 days. Growth stages: Bare / Fallow, Emerging, Vegetative, Reproductive, Ripening, Post-harvest.
/v1/field/seasonal-compare
Compare mean NDVI for the current window against the identical calendar window last year. Fetches both date ranges in parallel. Returns the year-on-year delta, percentage change, and a verdict: ahead (delta > +0.05), on_track, or behind (delta < -0.05). Useful for loan officers and insurers assessing whether this season is performing better or worse than the previous one.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | no | Analysis radius in metres (100-10000). Default: 500 |
| window_days | integer | no | Comparison window in days (30-180). Default: 90 |
Example
curl "https://api.agrosat.dev/v1/field/seasonal-compare?latitude=-6.7924&longitude=39.2083&window_days=90" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"coordinates": { "latitude": -6.7924, "longitude": 39.2083 },
"radius_meters": 500,
"window_days": 90,
"current_period": {
"start": "2026-02-18", "end": "2026-05-19",
"ndvi_mean": 0.52, "observation_count": 14
},
"last_year_period": {
"start": "2025-02-18", "end": "2025-05-19",
"ndvi_mean": 0.48, "observation_count": 11
},
"yoy_delta": 0.04,
"yoy_delta_pct": 8.3,
"verdict": "ahead",
"calls_consumed": 2,
"signal": "Current NDVI (0.52) is 8.3% above the same period last year (0.48). Crop performing better than previous season."
},
"error": null
}Counts as 2 calls. Results cached 24 hours. Verdict threshold: +/-0.05 NDVI.
/v1/region/timeseries
Return a 30-day NDVI timeseries aggregated across a 3x3 grid of sample points within a bounding box. Each date in the response aggregates NDVI from all sample points that had a cloud-free observation that day — useful for monitoring how an entire district trends over time.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat_min | float | yes | South edge (-90 to 90) |
| lat_max | float | yes | North edge (-90 to 90) |
| lon_min | float | yes | West edge (-180 to 180) |
| lon_max | float | yes | East edge (-180 to 180) |
| stream | boolean | no | Set true to stream each grid point as NDJSON — final line contains the aggregated timeseries. Default: false |
Example
curl "https://api.agrosat.dev/v1/region/timeseries?lat_min=-7.5&lat_max=-6.5&lon_min=38.5&lon_max=39.5" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"bbox": { "lat_min": -7.5, "lat_max": -6.5, "lon_min": 38.5, "lon_max": 39.5 },
"period_days": 30,
"grid_sample_count": 9,
"observations": [
{ "date": "2026-04-29", "ndvi_mean": 0.5201, "ndvi_min": 0.3814, "ndvi_max": 0.6890, "sample_count": 8 },
{ "date": "2026-05-04", "ndvi_mean": 0.4012, "ndvi_min": 0.2314, "ndvi_max": 0.5803, "sample_count": 9 }
],
"calls_consumed": 9
},
"error": null
}Counts as 9 calls. Bounding box must not exceed 1 degree in any dimension. See the Streaming section for NDJSON format.
/v1/batch/analyze
Analyze up to 50 fields in a single request. Fields are processed in parallel — wall time is roughly equal to the slowest individual fetch, not N times one fetch. Per-field errors are returned inline so one bad coordinate does not fail the whole batch.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| fields | array | yes | Array of field objects (max 50). Each: { latitude, longitude, radius_meters } |
| stream | boolean | no | Set true to receive results as NDJSON — each field streams back as it completes instead of waiting for the full batch. Default: false |
Example
curl -X POST https://api.agrosat.dev/v1/batch/analyze \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"fields": [
{"latitude": -6.7924, "longitude": 39.2083, "radius_meters": 500},
{"latitude": -3.3869, "longitude": 36.683, "radius_meters": 500}
]
}'Response 200
{
"data": [
{
"index": 0,
"data": {
"ndvi": 0.42, "ndwi": 0.05, "health_score": 52, "health_label": "Moderate",
"cloud_cover_pct": 15, "imagery_date": "2026-05-04",
"coordinates": {"latitude": -6.7924, "longitude": 39.2083},
"radius_meters": 500, "cached": false
},
"error": null
},
{
"index": 1,
"data": {
"ndvi": 0.64, "ndwi": 0.12, "health_score": 76, "health_label": "Good",
"cloud_cover_pct": 8, "imagery_date": "2026-05-04",
"coordinates": {"latitude": -3.3869, "longitude": 36.683},
"radius_meters": 500, "cached": true
},
"error": null
}
],
"error": null
}Counts as N calls (one per field). The full batch is rejected upfront if it would exceed your remaining monthly budget. See the Streaming section for NDJSON format.
/v1/field/compare
Compare NDVI and crop health between two dates for the same field. Searches a 15-day window around each target date for the best cloud-free Sentinel-2 scene. The actual imagery_date in each snapshot shows the true scene date, which may differ from the requested date.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | yes | Decimal degrees (-90 to 90) |
| longitude | float | yes | Decimal degrees (-180 to 180) |
| radius_meters | integer | no | Analysis radius in metres (100-10000). Default: 500 |
| date_before | date | yes | Earlier reference date (ISO 8601, must be in the past) |
| date_after | date | yes | Later comparison date (ISO 8601, must be in the past and after date_before) |
Example
curl -X POST https://api.agrosat.dev/v1/field/compare \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"latitude": -6.7924,
"longitude": 39.2083,
"radius_meters": 500,
"date_before": "2026-04-01",
"date_after": "2026-05-01"
}'Response 200
{
"data": {
"coordinates": {"latitude": -6.7924, "longitude": 39.2083},
"radius_meters": 500,
"date_before": "2026-04-01",
"date_after": "2026-05-01",
"before": {
"ndvi": 0.5614, "ndwi": -0.4956, "health_score": 70, "health_label": "Moderate",
"cloud_cover_pct": 20.7, "imagery_date": "2026-04-29"
},
"after": {
"ndvi": 0.3469, "ndwi": -0.3069, "health_score": 43, "health_label": "Fair",
"cloud_cover_pct": 25.5, "imagery_date": "2026-05-04"
},
"ndvi_delta": -0.2145,
"trend": "declining"
},
"error": null
}Counts as 2 calls. Trend: ndvi_delta > +0.05 = improving, < -0.05 = declining, else stable.
/v1/region/summary
Aggregate NDVI statistics across a bounding box using a 3x3 grid of sample points (9 total). Returns mean, min, max NDVI and a health distribution breakdown. Useful for portfolio-level risk views — an insurer monitoring all policyholders in a district at once.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat_min | float | yes | South edge (-90 to 90) |
| lat_max | float | yes | North edge (-90 to 90) |
| lon_min | float | yes | West edge (-180 to 180) |
| lon_max | float | yes | East edge (-180 to 180) |
| stream | boolean | no | Set true to stream each grid point as NDJSON as it completes — final line contains the aggregated summary. Default: false |
Example
curl "https://api.agrosat.dev/v1/region/summary?lat_min=-7.5&lat_max=-6.5&lon_min=38.5&lon_max=39.5" \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"bbox": {"lat_min": -7.5, "lat_max": -6.5, "lon_min": 38.5, "lon_max": 39.5},
"sample_count": 9,
"ndvi_mean": 0.4821,
"ndvi_min": 0.2314,
"ndvi_max": 0.6890,
"health_distribution": {
"Poor": 0, "Fair": 2, "Moderate": 5, "Good": 2, "Excellent": 0
},
"dominant_health": "Moderate",
"calls_consumed": 9
},
"error": null
}Counts as 9 calls. Bounding box must not exceed 1 degree in any dimension (~111km at the equator). See the Streaming section for NDJSON format.
/v1/keys/generate
No auth requiredCreates a free tier API key. No authentication required. A one-time claim link is sent to the provided email address — the plaintext key is revealed when that link is opened and shown only once.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| string | yes | Email address. Used to identify the key owner. | |
| name | string | no | Human-readable label for the key (e.g. "My AgriApp"). |
Example
curl -X POST https://api.agrosat.dev/v1/keys/generate \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "name": "My AgriApp"}'Response 200
{
"data": {
"api_key": "agrosat_live_abc123...",
"tier": "free",
"monthly_limit": 500
},
"error": null
}/v1/keys/usage
Returns current month call count and remaining limit for the authenticated key.
Example
curl https://api.agrosat.dev/v1/keys/usage \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE"Response 200
{
"data": {
"tier": "free",
"limit": 500,
"used": 47,
"resets_on": "2026-06-01"
},
"error": null
}Streaming
Four endpoints support streaming responses: POST /v1/batch/analyze, POST /v1/batch/timeseries, GET /v1/region/summary, and GET /v1/region/timeseries. Pass stream=true (query param for GET, body field for POST) and the server responds with application/x-ndjson — one JSON object per line, flushed as each result arrives. You do not wait for the full batch to complete.
Every line has a type field:
"data"One result item (field or grid point). Arrives as each completes — order is not guaranteed for batch requests."done"Final line. Always present. Includes total, succeeded, failed counts. For region endpoints, also includes the aggregated summary object.Streaming example — batch analyze (curl)
curl -X POST https://api.agrosat.dev/v1/batch/analyze \
-H "X-API-Key: agrosat_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
--no-buffer \
-d '{
"fields": [
{"latitude": -6.7924, "longitude": 39.2083, "radius_meters": 500},
{"latitude": -3.3869, "longitude": 36.683, "radius_meters": 500}
],
"stream": true
}'NDJSON output
# Each field streams back as it completes (NDJSON — one JSON object per line)
{"type":"data","index":1,"data":{"ndvi":0.64,"ndwi":0.12,"health_score":76,"health_label":"Good","cloud_cover_pct":8,"imagery_date":"2026-05-04","coordinates":{"latitude":-3.3869,"longitude":36.683},"radius_meters":500,"cached":true},"error":null}
{"type":"data","index":0,"data":{"ndvi":0.42,"ndwi":0.05,"health_score":52,"health_label":"Moderate","cloud_cover_pct":15,"imagery_date":"2026-05-04","coordinates":{"latitude":-6.7924,"longitude":39.2083},"radius_meters":500,"cached":false},"error":null}
{"type":"done","total":2,"succeeded":2,"failed":0}Code example
import httpx, json
API_KEY = "agrosat_live_YOUR_KEY_HERE"
BASE_URL = "https://api.agrosat.dev"
# Batch analyze — results arrive as each field completes
with httpx.stream(
"POST",
f"{BASE_URL}/v1/batch/analyze",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={
"fields": [
{"latitude": -6.7924, "longitude": 39.2083},
{"latitude": -3.3869, "longitude": 36.683},
],
"stream": True,
},
timeout=120,
) as r:
r.raise_for_status()
for line in r.iter_lines():
if not line:
continue
chunk = json.loads(line)
if chunk["type"] == "data":
if chunk.get("error"):
print(f"Field {chunk['index']}: ERROR {chunk['error']['code']}")
else:
d = chunk["data"]
print(f"Field {chunk['index']}: NDVI {d['ndvi']} {d['health_label']}")
elif chunk["type"] == "done":
print(f"Done — {chunk['succeeded']}/{chunk['total']} succeeded")Streaming does not change call counting — a batch of 10 fields costs 10 calls whether streamed or not. The pre-flight rate limit check still runs before any data is returned.
Rate Limits
Limits reset on the 1st of each calendar month. Exceeded limits return 429 RATE_LIMIT_EXCEEDED.
Error Codes
# Rate limit exceeded
{ "data": null, "error": { "code": "RATE_LIMIT_EXCEEDED",
"message": "Monthly limit of 500 calls reached. Upgrade at agrosat.dev/pricing" }}
# Invalid coordinates
{ "data": null, "error": { "code": "INVALID_COORDINATES",
"message": "Invalid coordinates. Latitude must be -90 to 90, longitude -180 to 180." }}
# Too much cloud cover
{ "data": null, "error": { "code": "CLOUD_COVER_TOO_HIGH",
"message": "Cloud cover 87% exceeds threshold. Try again in a few days." }}
# Bad API key
{ "data": null, "error": { "code": "INVALID_API_KEY",
"message": "API key not found or inactive." }}Code Examples
import requests
API_KEY = "agrosat_live_YOUR_KEY_HERE"
BASE_URL = "https://api.agrosat.dev"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
def analyze_field(lat: float, lon: float, radius: int = 500) -> dict:
resp = requests.post(
f"{BASE_URL}/v1/field/analyze",
headers=HEADERS,
json={"latitude": lat, "longitude": lon, "radius_meters": radius},
timeout=60,
)
resp.raise_for_status()
return resp.json()
def field_timeseries(lat: float, lon: float, radius: int = 500) -> dict:
resp = requests.get(
f"{BASE_URL}/v1/field/timeseries",
headers={"X-API-Key": API_KEY},
params={"latitude": lat, "longitude": lon, "radius_meters": radius},
timeout=60,
)
resp.raise_for_status()
return resp.json()
result = analyze_field(-6.7924, 39.2083)
print(f"NDVI: {result['data']['ndvi']:.3f} Health: {result['data']['health_label']}")
# NDVI: 0.620 Health: Good
history = field_timeseries(-8.2394, 39.0033)
for point in history["data"]["data_points"]:
print(f"{point['date']} NDVI: {point['ndvi']:.4f} {point['health_label']}")Get a free API key and make your first call in minutes.