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_HERE

Key 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.

POST

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegeryesAnalysis 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

NDVI < 0.20PoorBare soil / severely stressed
0.20 to 0.40FairSparse / stressed vegetation
0.40 to 0.60ModerateModerate vegetation
0.60 to 0.80GoodHealthy vegetation
NDVI > 0.80ExcellentDense healthy vegetation
GET

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegernoAnalysis 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.

GET

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegernoAnalysis 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.

POST

/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

ParameterTypeRequiredDescription
fieldsarrayyesArray of field objects (max 20). Each: { latitude, longitude, radius_meters }
period_daysintegernoHistory window in days (7-90). Default: 30

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).

GET

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegernoAnalysis 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.

GET

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegernoAnalysis 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.

GET

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegernoAnalysis radius in metres (100-10000). Default: 500
window_daysintegernoComparison 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.

GET

/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

ParameterTypeRequiredDescription
lat_minfloatyesSouth edge (-90 to 90)
lat_maxfloatyesNorth edge (-90 to 90)
lon_minfloatyesWest edge (-180 to 180)
lon_maxfloatyesEast edge (-180 to 180)

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.

POST

/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

ParameterTypeRequiredDescription
fieldsarrayyesArray of field objects (max 50). Each: { latitude, longitude, radius_meters }

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.

POST

/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

ParameterTypeRequiredDescription
latitudefloatyesDecimal degrees (-90 to 90)
longitudefloatyesDecimal degrees (-180 to 180)
radius_metersintegernoAnalysis radius in metres (100-10000). Default: 500
date_beforedateyesEarlier reference date (ISO 8601, must be in the past)
date_afterdateyesLater 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.

GET

/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

ParameterTypeRequiredDescription
lat_minfloatyesSouth edge (-90 to 90)
lat_maxfloatyesNorth edge (-90 to 90)
lon_minfloatyesWest edge (-180 to 180)
lon_maxfloatyesEast edge (-180 to 180)

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).

POST

/v1/keys/generate

No auth required

Creates 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

ParameterTypeRequiredDescription
emailstringyesEmail address. Used to identify the key owner.
namestringnoHuman-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
}
GET

/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
}

Rate Limits

Free$0 / month500 calls / month

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

Python

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']}")

JavaScript / TypeScript

const API_KEY = 'agrosat_live_YOUR_KEY_HERE';
const BASE_URL = 'https://api.agrosat.dev';

async function analyzeField(latitude, longitude, radiusMeters = 500) {
  const res = await fetch(`${BASE_URL}/v1/field/analyze`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
    body: JSON.stringify({ latitude, longitude, radius_meters: radiusMeters }),
  });
  return res.json();
}

async function fieldTimeseries(latitude, longitude, radiusMeters = 500) {
  const params = new URLSearchParams({ latitude, longitude, radius_meters: radiusMeters });
  const res = await fetch(`${BASE_URL}/v1/field/timeseries?${params}`, {
    headers: { 'X-API-Key': API_KEY },
  });
  return res.json();
}

const result = await analyzeField(-6.7924, 39.2083);
console.log(result.data.ndvi, result.data.health_label);
// 0.62 "Good"

const history = await fieldTimeseries(-8.2394, 39.0033);
history.data.data_points.forEach(p =>
  console.log(p.date, p.ndvi.toFixed(4), p.health_label)
);
Ready to integrate?

Get a free API key and make your first call in minutes.

Get free API key