← Back to Google Earth Engine docs
Google Earth Engine — Tool Reference
Historic satellite imagery and planetary data via Google Earth Engine: Landsat archive back to 1982, Sentinel-2 composites, night lights, land cover, rainfall, temperature, terrain, and population, rendered over any area of interest.
Your AI assistant discovers and invokes these tools through the
MCP Server's meta tools —
it calls execute_tool with the tool name and arguments below.
# get_statistics read
Compute AOI statistics from Earth Engine: NDVI/NDWI series, rainfall totals, temperature means, population counts, land-cover fractions.
Full description
Compute numeric statistics over an AOI from curated Earth Engine data. Returns a chronological series (monthly by default) for ndvi, ndwi, precip_total, and t2m_mean; a single number for pop_sum; and percentage-by-class for class_fractions. ndvi/ndwi default to Sentinel-2 and accept dataset='landsat' or 'landsat-historic' for older years; class_fractions defaults to dynamic-world and accepts 'worldcover'. Ideal for questions like "how much rain fell on this watershed each month", "when was this field greenest", "how many people live inside this outline", or "what share of this district is built-up". Pass country='France' to cover a whole nation without any polygon payload. For a sub-national area, pass aoi_ref (the stored handle the geo and eo toolkits return) rather than inline geometry: it is resolved server-side and clipped exactly like an inline aoi, and refs expire after about 48 hours. Large AOIs are sampled at a coarser effective resolution (reported in the result) to keep computation bounded; population sums always count at native resolution. Use render_image for pictures; this tool returns numbers.
| Parameter | Type | Required | Description |
|---|---|---|---|
aoi | object | null | No | GeoJSON Polygon/MultiPolygon geometry (WGS84). Accepts the geo toolkit's `geojson` output. Provide exactly one of aoi, aoi_ref, bbox, or country (country and bbox may be combined; nothing else may). |
aoi_ref | string | null | No | Stored AOI handle from the geo or eo toolkit (the `aoi_ref`/`merged_aoi_ref`/`changed_aoi_ref` those tools return), resolved server-side so the geometry never crosses the conversation. Preferred over inline aoi for anything bigger than a simple box, and the only way to cover a sub-national boundary without pasting its polygon. Refs expire after about 48 hours; re-derive one with geo's lookup_place or make_aoi. Cannot be combined with aoi, bbox or country. |
bbox | array<number> | null | No | [west, south, east, north] WGS84 degrees. Alternative to aoi or aoi_ref. |
country | string | null | No | Country name resolved server-side to its national boundary (US DoS LSIB), so statistics cover exactly that country with no polygon payload: e.g. 'France', 'Korea, South', or aliases like 'USA' or 'Myanmar'. 'United States' is the lower 48; Alaska and Hawaii are separate entries. Combine with bbox to restrict to the part of the country inside the bbox. Alternative to aoi or aoi_ref. |
variable | "ndvi" | "ndwi" | "precip_total" | "t2m_mean" | "pop_sum" | "class_fractions" | Yes | What to compute over the AOI: ndvi/ndwi (vegetation/water index series from Sentinel-2), precip_total (CHIRPS rainfall sum per interval, mm), t2m_mean (ERA5-Land mean air temperature per interval, deg C), pop_sum (GHSL resident count, single value), class_fractions (Dynamic World land-cover percentages). |
time_from | string | null | No | Window start (YYYY-MM-DD). Not needed for pop_sum. |
time_to | string | null | No | Window end (YYYY-MM-DD), inclusive. Not needed for pop_sum. |
interval | "P1D" | "P1M" | No | Series interval: P1D (daily) or P1M (monthly, default). Default: "P1M" |
dataset | string | null | No | Dataset alias override where a variable supports several: ndvi/ndwi accept sentinel-2 (default), landsat, or landsat-historic; class_fractions accepts dynamic-world (default) or worldcover. Omit for the default. |
Parameter schema (JSON)
{
"description": "Parameters for get_statistics.",
"properties": {
"aoi": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "GeoJSON Polygon/MultiPolygon geometry (WGS84). Accepts the geo toolkit's `geojson` output. Provide exactly one of aoi, aoi_ref, bbox, or country (country and bbox may be combined; nothing else may).",
"title": "Aoi"
},
"aoi_ref": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Stored AOI handle from the geo or eo toolkit (the `aoi_ref`/`merged_aoi_ref`/`changed_aoi_ref` those tools return), resolved server-side so the geometry never crosses the conversation. Preferred over inline aoi for anything bigger than a simple box, and the only way to cover a sub-national boundary without pasting its polygon. Refs expire after about 48 hours; re-derive one with geo's lookup_place or make_aoi. Cannot be combined with aoi, bbox or country.",
"title": "Aoi Ref"
},
"bbox": {
"anyOf": [
{
"items": {
"type": "number"
},
"maxItems": 4,
"minItems": 4,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "[west, south, east, north] WGS84 degrees. Alternative to aoi or aoi_ref.",
"title": "Bbox"
},
"country": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Country name resolved server-side to its national boundary (US DoS LSIB), so statistics cover exactly that country with no polygon payload: e.g. 'France', 'Korea, South', or aliases like 'USA' or 'Myanmar'. 'United States' is the lower 48; Alaska and Hawaii are separate entries. Combine with bbox to restrict to the part of the country inside the bbox. Alternative to aoi or aoi_ref.",
"title": "Country"
},
"variable": {
"description": "What to compute over the AOI: ndvi/ndwi (vegetation/water index series from Sentinel-2), precip_total (CHIRPS rainfall sum per interval, mm), t2m_mean (ERA5-Land mean air temperature per interval, deg C), pop_sum (GHSL resident count, single value), class_fractions (Dynamic World land-cover percentages).",
"enum": [
"ndvi",
"ndwi",
"precip_total",
"t2m_mean",
"pop_sum",
"class_fractions"
],
"title": "Variable",
"type": "string"
},
"time_from": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Window start (YYYY-MM-DD). Not needed for pop_sum.",
"title": "Time From"
},
"time_to": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Window end (YYYY-MM-DD), inclusive. Not needed for pop_sum.",
"title": "Time To"
},
"interval": {
"default": "P1M",
"description": "Series interval: P1D (daily) or P1M (monthly, default).",
"enum": [
"P1D",
"P1M"
],
"title": "Interval",
"type": "string"
},
"dataset": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Dataset alias override where a variable supports several: ndvi/ndwi accept sentinel-2 (default), landsat, or landsat-historic; class_fractions accepts dynamic-world (default) or worldcover. Omit for the default.",
"title": "Dataset"
}
},
"required": [
"variable"
],
"title": "GetStatisticsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"IntervalStat": {
"description": "Statistics for one interval.",
"properties": {
"interval_from": {
"description": "Interval start (inclusive).",
"title": "Interval From",
"type": "string"
},
"interval_to": {
"description": "Interval end (exclusive).",
"title": "Interval To",
"type": "string"
},
"mean": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Mean over the AOI.",
"title": "Mean"
},
"min": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Minimum over the AOI.",
"title": "Min"
},
"max": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Maximum over the AOI.",
"title": "Max"
},
"sum": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Area-mean total over the interval (e.g. mm of rainfall averaged across the AOI), for total-type variables.",
"title": "Sum"
}
},
"required": [
"interval_from",
"interval_to"
],
"title": "IntervalStat",
"type": "object"
}
},
"description": "Result of get_statistics.",
"properties": {
"success": {
"description": "Whether the operation succeeded",
"title": "Success",
"type": "boolean"
},
"variable": {
"description": "Variable computed.",
"title": "Variable",
"type": "string"
},
"dataset": {
"description": "Dataset alias used.",
"title": "Dataset",
"type": "string"
},
"interval": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Series interval, when a series.",
"title": "Interval"
},
"effective_resolution_m": {
"description": "Sampling resolution actually used. Large AOIs are sampled coarser than native to keep computation bounded.",
"title": "Effective Resolution M",
"type": "number"
},
"intervals": {
"description": "Per-interval statistics, chronological.",
"items": {
"$ref": "#/$defs/IntervalStat"
},
"title": "Intervals",
"type": "array"
},
"value": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Single value, for non-series variables (pop_sum).",
"title": "Value"
},
"fractions": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Class name -> percent of CLASSIFIED pixels in the AOI (cloud-masked or uncovered pixels are excluded from the denominator), for class_fractions.",
"title": "Fractions"
},
"complete": {
"default": true,
"description": "False when the series was cut short (time budget or a provider failure); intervals then cover only a prefix of the requested range.",
"title": "Complete",
"type": "boolean"
},
"intervals_requested": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Interval count the window implied, for series.",
"title": "Intervals Requested"
},
"intervals_processed": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Intervals actually computed (incl. empty), for series.",
"title": "Intervals Processed"
},
"time_from": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Effective window start used.",
"title": "Time From"
},
"time_to": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Effective window end used (inclusive).",
"title": "Time To"
},
"data_epoch": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "For static datasets: the epoch the value describes.",
"title": "Data Epoch"
},
"attribution": {
"description": "Required attribution for this data.",
"title": "Attribution",
"type": "string"
},
"license": {
"description": "Data license.",
"title": "License",
"type": "string"
},
"source": {
"description": "Authoritative catalog entry URL.",
"title": "Source",
"type": "string"
},
"redistribution": {
"description": "Redistribution terms.",
"title": "Redistribution",
"type": "string"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Extra guidance, when relevant.",
"title": "Note"
}
},
"required": [
"success",
"variable",
"dataset",
"effective_resolution_m",
"attribution",
"license",
"source",
"redistribution"
],
"title": "GetStatisticsResult",
"type": "object"
} # list_datasets read
List the curated Earth Engine datasets this toolkit can render.
Full description
List the curated Earth Engine datasets available to render_image. Call this before choosing a `dataset` value: it is the authoritative inventory (do not guess dataset names). Each entry states native resolution, valid time range, whether a time window is required, and the attribution that accompanies its imagery.
No parameters.
Parameter schema (JSON)
{
"description": "Parameters for list_datasets (none).",
"properties": {},
"title": "ListDatasetsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"DatasetInfo": {
"description": "One curated dataset.",
"properties": {
"alias": {
"description": "Value to pass as `dataset`.",
"title": "Alias",
"type": "string"
},
"label": {
"description": "Human-readable name.",
"title": "Label",
"type": "string"
},
"description": {
"description": "What it shows and when to use it.",
"title": "Description",
"type": "string"
},
"native_resolution_m": {
"description": "Native meters per pixel.",
"title": "Native Resolution M",
"type": "number"
},
"time_from": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Earliest data, when temporal.",
"title": "Time From"
},
"time_to": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Latest data; null means ongoing or static.",
"title": "Time To"
},
"temporal": {
"description": "'window' (needs time range) or 'static'.",
"title": "Temporal",
"type": "string"
},
"sensors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "The satellite instrument-era records this dataset can draw on, in order of first availability. These are instrument eras, not spacecraft: Landsat 7 appears twice, once for ETM+ before its Scan Line Corrector failed on 2003-05-31 and once after, because the two are selected and described separately. Which of them a given render draws on depends on its window; render_image reports that per window. Null for datasets that are not multi-sensor.",
"title": "Sensors"
},
"supports_composites": {
"description": "Whether composite latest/median/greenest applies.",
"title": "Supports Composites",
"type": "boolean"
},
"license": {
"description": "Data license.",
"title": "License",
"type": "string"
},
"source": {
"description": "Authoritative catalog entry URL.",
"title": "Source",
"type": "string"
},
"redistribution": {
"description": "Redistribution terms for rendered derivatives.",
"title": "Redistribution",
"type": "string"
},
"attribution": {
"description": "Required attribution text.",
"title": "Attribution",
"type": "string"
}
},
"required": [
"alias",
"label",
"description",
"native_resolution_m",
"temporal",
"supports_composites",
"license",
"source",
"redistribution",
"attribution"
],
"title": "DatasetInfo",
"type": "object"
}
},
"description": "Result of list_datasets.",
"properties": {
"success": {
"description": "Whether the operation succeeded",
"title": "Success",
"type": "boolean"
},
"datasets": {
"description": "Curated datasets.",
"items": {
"$ref": "#/$defs/DatasetInfo"
},
"title": "Datasets",
"type": "array"
}
},
"required": [
"success",
"datasets"
],
"title": "ListDatasetsResult",
"type": "object"
} # render_image write
Render Earth Engine imagery (Landsat archive, night lights, land cover, climate) of an AOI and return a displayable URL; draw country or AOI boundaries on it; compare mode stitches 2-4 labeled time windows into one image.
Full description
Render a curated Earth Engine dataset over an AOI to a PNG URL.
Composites scenes from the time window (median by default; latest or
greenest for optical datasets), renders the dataset's standard
visualization, and uploads the PNG to temporary storage. Embed the
returned image_url in a markdown image tag to show it. Pass
country='France' to clip to a national border without any polygon
payload. For a sub-national area, pass aoi_ref (the stored handle the
geo and eo toolkits return) rather than inline geometry: it is resolved
server-side and clips exactly like an inline aoi, and refs expire after
about 48 hours. Pass windows=[[from, to], ...] (up to 6) for pixel-aligned
time-series or multi-frame views of the same AOI in one call; check
each frame's status in the result. Call list_datasets first when
unsure which dataset fits; static datasets (worldcover, elevation,
population) need no time window.
Pass overlays=[{country|aoi_ref|geojson, stroke?, stroke_width?, fill?,
spotlight?, label?}, ...] to DRAW boundaries on the imagery. Drawing and
clipping are independent: overlays=[{country: 'Egypt', stroke: '#ffffff'}]
with a bbox draws the Egyptian border across the window while northern
Sudan stays visible, and adding country='Egypt' at the top level clips as
well. spotlight dims everything outside the shape. The same overlays are
burned identically onto every compare panel and every windows frame, so a
boundary stays put while the years change around it. Graphics are clipped
to the rendered extent; an overlay lying outside it is reported in the
note. An overlay taken from aoi_ref or geojson sets overlay_attribution
(ODbL); a country overlay is public-domain LSIB, named in the note.
Pass compare={panels: [{time_from, time_to, label?}, ...]} (2-4 panels)
instead of windows/time_from/time_to for a before/after comparison:
every panel renders the same AOI at identical dimensions, image_url
becomes one stitched PNG with a labeled band per panel (2 side by side,
3-4 in a 2x2 grid), and the result drives an interactive wipe slider on
MCP Apps hosts. Panels are listed in temporal order with per-panel URLs;
per-panel width is capped at 1024px. A comparison is all-or-nothing: if
any panel fails to render, the whole call fails. compare is not valid
for static datasets.
For Landsat, dataset='landsat-all' spans 1982 to the present in one
dataset, so a 1985-vs-2025 comparison is a single compare call. Inside
that range it covers a window end to end, using gap-free sensors for each
part one reaches and falling back to Landsat 7's post-2003-05-31 SLC-off
scenes (and their diagonal data gaps) only for what none reaches --
2012-05-06 to 2013-03-17, the only such span -- naming the dates in the
note when it does. Preferring gap-free sensors composites fewer scenes
than landsat-historic over 1999-2012, so use landsat-historic when scene
count matters more than gaps. The `sensors` field on the result, on each
frame and on each panel names the instrument eras that window would draw
on, worked out from the requested dates before anything is built -- so on
a skipped or failed frame it says what WOULD have been used, not what was.
A window that runs past either end of a dataset's range is accepted rather
than rejected (only one lying wholly outside is refused), composites the
part that exists, and the note names the dates that contributed nothing --
so check the note before reading a result as covering the full period you
asked for. This applies to every dataset, not only the Landsat ones.
Choosing a toolkit: use eo-render_image for the freshest imagery (hours
old) or PlanetScope resolution, and this tool for Earth Engine datasets
(Landsat archive, night lights, land cover, climate, terrain). Both
toolkits support compare for the wipe-slider before/after view, and both
take the same overlays shape. | Parameter | Type | Required | Description |
|---|---|---|---|
aoi | object | null | No | GeoJSON Polygon/MultiPolygon geometry (WGS84). Accepts the geo toolkit's `geojson` output. Provide exactly one of aoi, aoi_ref, bbox, or country (country and bbox may be combined; nothing else may). |
aoi_ref | string | null | No | Stored AOI handle from the geo or eo toolkit (the `aoi_ref`/`merged_aoi_ref`/`changed_aoi_ref` those tools return), resolved server-side so the geometry never crosses the conversation, then clipped exactly like an inline aoi. Preferred over inline aoi for anything bigger than a simple box, and the only way to frame a sub-national boundary without pasting its polygon. Refs expire after about 48 hours; re-derive one with geo's lookup_place or make_aoi. Cannot be combined with aoi, bbox or country. |
bbox | array<number> | null | No | [west, south, east, north] WGS84 degrees. Alternative to aoi or aoi_ref. |
country | string | null | No | Country name resolved server-side to its national boundary (US DoS LSIB) and clipped exactly, with no polygon payload: e.g. 'France', 'Korea, South', or aliases like 'USA' or 'Myanmar'. 'United States' is the lower 48; Alaska and Hawaii are separate entries. Combine with bbox to frame a window while still clipping to the border. This CLIPS; to draw the border instead (or as well), pass the same name in overlays. Alternative to aoi or aoi_ref. |
dataset | string | No | Dataset alias from list_datasets, e.g. sentinel-2 (default), landsat (Landsat 8/9, 2013-03 onward), landsat-historic (Landsat 4/5/7, 1982 to 2024-01), landsat-all (Landsat 4-9, 1982-present, one dataset for multi-decade comparisons), night-lights, dynamic-world, rainfall, elevation. Default: "sentinel-2" |
time_from | string | null | No | Window start (YYYY-MM-DD). Required unless the dataset is static. |
time_to | string | null | No | Window end (YYYY-MM-DD), inclusive. Required unless static. |
windows | array<array<any>> | null | No | Up to 6 [time_from, time_to] YYYY-MM-DD pairs rendered as pixel-aligned frames of the same AOI in ONE call: mini time series and multi-frame views. All frames share the dataset, composite, dimensions, and stretch. Keep to 4 or fewer for optical datasets (sentinel-2, landsat); 6 works for coarse temporal ones (night-lights, rainfall, temperature). Not valid for static datasets, alongside time_from/time_to, or with compare; cross-dataset comparisons need one call per dataset. windows is for multi-frame series and returns plain PNG URLs; for a before/after comparison view (interactive wipe slider on MCP Apps hosts), use compare instead. |
compare | CompareSpec | null | No | Render 2-4 time windows of the same AOI as pixel-aligned, labeled panels in ONE call (before/after comparisons). All panels share the AOI, dataset, composite, and dimensions; per-panel width is capped at 1024px in compare mode. image_url becomes one stitched PNG and the result carries an interactive wipe slider on MCP Apps hosts. Mutually exclusive with windows and time_from/time_to, and not valid for static datasets. Use compare for before/after presentation; windows is for multi-frame series that return plain PNG URLs. |
overlays | array<OverlaySpec> | null | No | Up to 16 boundaries drawn ON the imagery: outline, optional translucent fill, optional spotlight (dim everything outside), optional label. Each takes its geometry from exactly one of aoi_ref, geojson, or country — so `[{country: 'Egypt', stroke: '#ffffff'}]` draws a national border with no polygon payload. Drawing does NOT clip: use the top-level country/aoi_ref for that, and combine them freely. The same overlays are burned identically onto every panel of a compare and every frame of a windows render, so a boundary stays put while the years change around it. Graphics are clipped to the rendered extent and a note reports any overlay that lies outside it. |
composite | "median" | "latest" | "greenest" | No | How optical scenes in the window combine: median (cloud-robust, default), latest (most recent pixels), greenest (peak NDVI). Default: "median" |
width | integer | No | Output width in pixels (64-1536); height follows the AOI aspect ratio. Width beyond the AOI's native pixel count at the dataset's resolution adds no detail; the server caps excessive values. Capped at 1024px per panel in compare mode. Default: 512 |
Parameter schema (JSON)
{
"$defs": {
"ComparePanel": {
"description": "One time window of a compare-mode render.",
"properties": {
"time_from": {
"description": "Panel window start (YYYY-MM-DD).",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"title": "Time From",
"type": "string"
},
"time_to": {
"description": "Panel window end (YYYY-MM-DD), inclusive.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"title": "Time To",
"type": "string"
},
"label": {
"anyOf": [
{
"maxLength": 60,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Panel label burned into the stitched image and used as the panel title; defaults to the panel's time window (e.g. '2019-07-01 – 2019-07-31'). Burned text covers Latin (incl. umlauts/accents), Greek, and Cyrillic; CJK is not yet supported in the PNG and renders as tofu.",
"title": "Label"
}
},
"required": [
"time_from",
"time_to"
],
"title": "ComparePanel",
"type": "object",
"additionalProperties": false
},
"CompareSpec": {
"description": "Compare-mode request: 2-4 time windows of the same AOI.",
"properties": {
"panels": {
"description": "2-4 time windows rendered as pixel-aligned panels of the same AOI and stitched into one labeled comparison PNG.",
"items": {
"$ref": "#/$defs/ComparePanel"
},
"maxItems": 4,
"minItems": 2,
"title": "Panels",
"type": "array"
}
},
"required": [
"panels"
],
"title": "CompareSpec",
"type": "object",
"additionalProperties": false
},
"OverlaySpec": {
"description": "One boundary/fill/spotlight/label overlay burned onto the render.\n\nDrawing and CLIPPING are independent. An overlay never restricts what is\nrendered: pass `country='Egypt'` at the top level to clip the imagery to\nthat border, pass it here to DRAW that border, or do both. The common case\nis the first without the second — a window across a frontier with the\nneighbouring territory still visible and the line drawn through it.\n\nGraphics are clipped to the rendered extent (the image covers the window,\nand nothing outside it exists to draw on). For an overlay whose BOUNDING\nBOX lies outside it, the fill adds nothing and the shape contributes no lit\narea to the spotlight mask — but spotlighting still runs, so a spotlit\nshape that reaches nothing DARKENS the image rather than leaving it alone.\nA wide stroke can still paint at the edge. A note reports all of this. A\nshape whose box overlaps the extent is drawn as-is and gets no note,\nwhether or not any of it lands on visible pixels.\n\nEvery field is Optional with defaults applied in code: the MCP transport\nmaterializes schema defaults, so conditional logic compares against None\nand never against model_fields_set.",
"properties": {
"aoi_ref": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Stored AOI handle for this overlay's geometry (the geo or eo toolkit's `aoi_ref`/`merged_aoi_ref`). Preferred for anything bigger than a sketch — resolved server-side, never through model context. Provide exactly one of aoi_ref, geojson, or country.",
"title": "Aoi Ref"
},
"geojson": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Inline GeoJSON Polygon/MultiPolygon for a small shape (<= 2000 vertices). For larger boundaries pass an aoi_ref. Provide exactly one of aoi_ref, geojson, or country.",
"title": "Geojson"
},
"country": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Country name resolved server-side to its national boundary (US DoS LSIB) and DRAWN, with no polygon payload: e.g. 'Egypt', 'Korea, South', or aliases like 'USA'. Same name set as the top-level country parameter, and independent of it — this draws the border without clipping the imagery to it, so a window spanning a frontier keeps both sides visible. Only the part of the boundary inside the rendered extent is drawn; a country that does not reach the window at all draws nothing, is reported in the note, and — exactly like an off-extent aoi_ref or geojson overlay — lights no area, so a spotlight on it still dims the image. Provide exactly one of aoi_ref, geojson, or country.",
"title": "Country"
},
"stroke": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Outline color as hex (#RGB, #RRGGBB, or #RRGGBBAA); defaults to a high-contrast red. Set stroke_width to 0 for no outline.",
"title": "Stroke"
},
"stroke_width": {
"anyOf": [
{
"maximum": 12,
"minimum": 0,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Outline width in pixels (0-12); 0 draws no outline. Defaults to 2.",
"title": "Stroke Width"
},
"fill": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Fill color as hex; 6 digits are opaque, pass 8 digits (#RRGGBBAA) for a translucent wash. No fill by default.",
"title": "Fill"
},
"spotlight": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "When true, dim everything OUTSIDE this shape (to ~40% brightness) to spotlight it. Multiple spotlight overlays union. If every spotlit shape's bounding box lies outside the rendered extent, nothing is lit, the image may come back fully dimmed, and a note says so. Default false.",
"title": "Spotlight"
},
"label": {
"anyOf": [
{
"maxLength": 60,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Short text burned once, centered on the centroid of the shape's largest ring (white with a dark halo) — for a concave shape that anchor can lie outside the shape, and if the anchor falls outside the canvas the text is clipped (possibly still partly visible) and a note says so. Max 60 characters. No label by default. Burned text covers Latin (incl. umlauts/accents), Greek, and Cyrillic; CJK is not yet supported in the PNG and renders as tofu.",
"title": "Label"
}
},
"title": "OverlaySpec",
"type": "object",
"additionalProperties": false
}
},
"description": "Parameters for render_image.",
"properties": {
"aoi": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "GeoJSON Polygon/MultiPolygon geometry (WGS84). Accepts the geo toolkit's `geojson` output. Provide exactly one of aoi, aoi_ref, bbox, or country (country and bbox may be combined; nothing else may).",
"title": "Aoi"
},
"aoi_ref": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Stored AOI handle from the geo or eo toolkit (the `aoi_ref`/`merged_aoi_ref`/`changed_aoi_ref` those tools return), resolved server-side so the geometry never crosses the conversation, then clipped exactly like an inline aoi. Preferred over inline aoi for anything bigger than a simple box, and the only way to frame a sub-national boundary without pasting its polygon. Refs expire after about 48 hours; re-derive one with geo's lookup_place or make_aoi. Cannot be combined with aoi, bbox or country.",
"title": "Aoi Ref"
},
"bbox": {
"anyOf": [
{
"items": {
"type": "number"
},
"maxItems": 4,
"minItems": 4,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "[west, south, east, north] WGS84 degrees. Alternative to aoi or aoi_ref.",
"title": "Bbox"
},
"country": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Country name resolved server-side to its national boundary (US DoS LSIB) and clipped exactly, with no polygon payload: e.g. 'France', 'Korea, South', or aliases like 'USA' or 'Myanmar'. 'United States' is the lower 48; Alaska and Hawaii are separate entries. Combine with bbox to frame a window while still clipping to the border. This CLIPS; to draw the border instead (or as well), pass the same name in overlays. Alternative to aoi or aoi_ref.",
"title": "Country"
},
"dataset": {
"default": "sentinel-2",
"description": "Dataset alias from list_datasets, e.g. sentinel-2 (default), landsat (Landsat 8/9, 2013-03 onward), landsat-historic (Landsat 4/5/7, 1982 to 2024-01), landsat-all (Landsat 4-9, 1982-present, one dataset for multi-decade comparisons), night-lights, dynamic-world, rainfall, elevation.",
"title": "Dataset",
"type": "string"
},
"time_from": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Window start (YYYY-MM-DD). Required unless the dataset is static.",
"title": "Time From"
},
"time_to": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Window end (YYYY-MM-DD), inclusive. Required unless static.",
"title": "Time To"
},
"windows": {
"anyOf": [
{
"items": {
"maxItems": 2,
"minItems": 2,
"prefixItems": [
{
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"type": "string"
},
{
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"type": "string"
}
],
"type": "array"
},
"maxItems": 6,
"minItems": 1,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Up to 6 [time_from, time_to] YYYY-MM-DD pairs rendered as pixel-aligned frames of the same AOI in ONE call: mini time series and multi-frame views. All frames share the dataset, composite, dimensions, and stretch. Keep to 4 or fewer for optical datasets (sentinel-2, landsat); 6 works for coarse temporal ones (night-lights, rainfall, temperature). Not valid for static datasets, alongside time_from/time_to, or with compare; cross-dataset comparisons need one call per dataset. windows is for multi-frame series and returns plain PNG URLs; for a before/after comparison view (interactive wipe slider on MCP Apps hosts), use compare instead.",
"title": "Windows"
},
"compare": {
"anyOf": [
{
"$ref": "#/$defs/CompareSpec"
},
{
"type": "null"
}
],
"default": null,
"description": "Render 2-4 time windows of the same AOI as pixel-aligned, labeled panels in ONE call (before/after comparisons). All panels share the AOI, dataset, composite, and dimensions; per-panel width is capped at 1024px in compare mode. image_url becomes one stitched PNG and the result carries an interactive wipe slider on MCP Apps hosts. Mutually exclusive with windows and time_from/time_to, and not valid for static datasets. Use compare for before/after presentation; windows is for multi-frame series that return plain PNG URLs."
},
"overlays": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/OverlaySpec"
},
"maxItems": 16,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Up to 16 boundaries drawn ON the imagery: outline, optional translucent fill, optional spotlight (dim everything outside), optional label. Each takes its geometry from exactly one of aoi_ref, geojson, or country — so `[{country: 'Egypt', stroke: '#ffffff'}]` draws a national border with no polygon payload. Drawing does NOT clip: use the top-level country/aoi_ref for that, and combine them freely. The same overlays are burned identically onto every panel of a compare and every frame of a windows render, so a boundary stays put while the years change around it. Graphics are clipped to the rendered extent and a note reports any overlay that lies outside it.",
"title": "Overlays"
},
"composite": {
"default": "median",
"description": "How optical scenes in the window combine: median (cloud-robust, default), latest (most recent pixels), greenest (peak NDVI).",
"enum": [
"median",
"latest",
"greenest"
],
"title": "Composite",
"type": "string"
},
"width": {
"default": 512,
"description": "Output width in pixels (64-1536); height follows the AOI aspect ratio. Width beyond the AOI's native pixel count at the dataset's resolution adds no detail; the server caps excessive values. Capped at 1024px per panel in compare mode.",
"maximum": 1536,
"minimum": 64,
"title": "Width",
"type": "integer"
}
},
"title": "RenderImageParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"ComparePanelResult": {
"description": "Outcome for one panel of a compare-mode render.",
"properties": {
"label": {
"description": "Panel label (also burned into the stitched image).",
"title": "Label",
"type": "string"
},
"time_from": {
"description": "Start of this panel's window (YYYY-MM-DD).",
"title": "Time From",
"type": "string"
},
"time_to": {
"description": "End of this panel's window (YYYY-MM-DD), inclusive.",
"title": "Time To",
"type": "string"
},
"image_url": {
"description": "Short PNG URL for this panel alone (no label band). Valid for about a day, then the image is deleted.",
"title": "Image Url",
"type": "string"
},
"width": {
"description": "Panel width in pixels (shared by all panels).",
"title": "Width",
"type": "integer"
},
"height": {
"description": "Panel height in pixels (shared by all panels).",
"title": "Height",
"type": "integer"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Per-panel guidance: blank-IMAGERY detection, in the same wording every other image in the response uses (with overlays requested it says the imagery is blank rather than the image, because the graphics drawn over it are still visible), plus any sensor and coverage caveats for this panel's window.",
"title": "Note"
},
"sensors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "The satellite instrument-era records this dataset would draw on for this window, in order of first availability. Derived from the requested dates and the collections' published date ranges alone, before anything is built or fetched: it is not evidence that a render happened, that scenes exist over this AOI, or that any listed record contributed a pixel. A record may cover only the part of the window its own availability reaches, and there is no ranking among those listed. Landsat 7 appears twice for a window spanning 2003-05-31, once for ETM+ before its Scan Line Corrector failed and once after. Null for datasets that are not multi-sensor. A comparison is all-or-nothing, so every panel listed did render; what remains unconfirmed is whether each listed record had scenes here.",
"title": "Sensors"
}
},
"required": [
"label",
"time_from",
"time_to",
"image_url",
"width",
"height"
],
"title": "ComparePanelResult",
"type": "object"
},
"RenderFrame": {
"description": "Outcome for one requested window of a render.",
"properties": {
"time_from": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Requested window start (null for static datasets).",
"title": "Time From"
},
"time_to": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Requested window end, inclusive.",
"title": "Time To"
},
"status": {
"description": "rendered: image_url is present; failed: this window errored (reason in note) — including a window whose imagery rendered but whose requested overlays could not be drawn, which is never uploaded and reported as rendered; skipped: nothing was produced for it and nothing was written for it — either the request's wall clock ran out before it started or finished (the shared render deadline, or the invocation's own remaining runtime), or its upload was refused for want of free capacity while an earlier request was still in flight. The note says which.",
"enum": [
"rendered",
"failed",
"skipped"
],
"title": "Status",
"type": "string"
},
"image_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "PNG URL for this frame, when rendered.",
"title": "Image Url"
},
"expires_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO-8601 UTC deletion time for this frame's image.",
"title": "Expires At"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Per-frame guidance: blank-IMAGERY detection (with overlays requested the note says so explicitly, because graphics drawn over blank imagery are still visible), or the reason this frame failed or was skipped.",
"title": "Note"
},
"sensors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "The satellite instrument-era records this dataset would draw on for this window, in order of first availability. Derived from the requested dates and the collections' published date ranges alone, before anything is built or fetched: it is not evidence that a render happened, that scenes exist over this AOI, or that any listed record contributed a pixel. A record may cover only the part of the window its own availability reaches, and there is no ranking among those listed. Landsat 7 appears twice for a window spanning 2003-05-31, once for ETM+ before its Scan Line Corrector failed and once after. Null for datasets that are not multi-sensor. Present for every requested window, including ones whose status is skipped or failed and for which nothing was built at all; read `status` for what actually happened.",
"title": "Sensors"
}
},
"required": [
"status"
],
"title": "RenderFrame",
"type": "object"
}
},
"description": "Result of render_image.",
"properties": {
"success": {
"description": "Whether the operation succeeded",
"title": "Success",
"type": "boolean"
},
"image_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Short PNG URL (the first rendered frame when windows was used). Embed as a markdown image or share as a link. Valid for about a day, then the image is deleted.",
"title": "Image Url"
},
"expires_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO-8601 UTC time after which the image is deleted.",
"title": "Expires At"
},
"width": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Rendered width in pixels (shared by all frames).",
"title": "Width"
},
"height": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Rendered height in pixels (shared by all frames).",
"title": "Height"
},
"dataset": {
"description": "Dataset alias rendered.",
"title": "Dataset",
"type": "string"
},
"sensors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "The satellite instrument-era records this dataset would draw on for this window, in order of first availability. Derived from the requested dates and the collections' published date ranges alone, before anything is built or fetched: it is not evidence that a render happened, that scenes exist over this AOI, or that any listed record contributed a pixel. A record may cover only the part of the window its own availability reaches, and there is no ranking among those listed. Landsat 7 appears twice for a window spanning 2003-05-31, once for ETM+ before its Scan Line Corrector failed and once after. Null for datasets that are not multi-sensor. In compare mode see panels[].sensors instead; with windows this is the first RENDERED frame's, and frames[].sensors carries every requested window's, including skipped and failed ones.",
"title": "Sensors"
},
"composite": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Composite method used, when applicable.",
"title": "Composite"
},
"time_from": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Window start used (first rendered frame).",
"title": "Time From"
},
"time_to": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Window end used, inclusive (first rendered frame).",
"title": "Time To"
},
"attribution": {
"description": "Required attribution for this imagery.",
"title": "Attribution",
"type": "string"
},
"license": {
"description": "Data license for this imagery.",
"title": "License",
"type": "string"
},
"overlay_attribution": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Attribution required by the OVERLAY geometry, when any overlay took its shape from an aoi_ref or inline geojson (those may be OpenStreetMap-derived). Null otherwise — a country overlay draws the public-domain LSIB boundary, which the note already names.",
"title": "Overlay Attribution"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Extra guidance, when relevant.",
"title": "Note"
},
"complete": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "True when every requested window rendered.",
"title": "Complete"
},
"frames_requested": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Number of windows requested.",
"title": "Frames Requested"
},
"frames_rendered": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Number of windows that produced an image.",
"title": "Frames Rendered"
},
"frames": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/RenderFrame"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "One entry per requested window, in request order; check each status rather than assuming all rendered.",
"title": "Frames"
},
"comparison": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "True when this is a compare-mode render (see panels).",
"title": "Comparison"
},
"panels": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/ComparePanelResult"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Compare mode only: one entry per rendered panel, in temporal order (sorted by time_from).",
"title": "Panels"
},
"visual": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Standardized visual payload for widget-capable clients. For non-compare renders (single image and windows): {kind: images, items: [{url, title, description}]}. For compare: {kind: compare, items: [{url, title, description?}], stitched_url}.",
"title": "Visual"
}
},
"required": [
"success",
"dataset",
"attribution",
"license"
],
"title": "RenderImageResult",
"type": "object"
}