Modify a grid
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
You don’t have to rebuild a grid to change it. POST .../modifications
applies rules to a grid’s current stored data and writes the result back
under the same id — no re-fetching LANDFIRE, no new resource. Reach for it to
reclassify a fuel model, mask fuel under roads and water, or scale a band,
once the grid already exists.
The rules you submit are appended to the grid’s list and applied on top of
whatever it already holds. To instead apply the same rules as you first build
a grid, see
Mask a fuel grid with features
— same rule shape, applied at create time. For the concepts — conditions,
actions, buffer_m, target, and the create-time-vs-in-place distinction —
see About modifications.
Prerequisites
Section titled “Prerequisites”-
An API key: my-api-key.
-
A domain in a projected CRS: your-domain-id. See Create a domain.
-
A completed FBFM grid and a completed surface-fuel grid. Both come from Build a surface fuel grid from LANDFIRE — record your-fbfm40-grid-id (carries the
fbfmband) and your-grid-id (carriesfuel_load.*). -
Optional — a completed road and water feature in the same domain, for the spatial recipes. See Create road and water features from OpenStreetMap and record your-road-feature-id and your-water-feature-id.
Masking roads and water in one script:
import time
import requests
API_KEY = "my-api-key"DOMAIN_ID = "your-domain-id"# A completed surface-fuel grid (e.g. from grids/lookup/fbfm40).GRID_ID = "your-grid-id"ROAD_FEATURE_ID = "your-road-feature-id"WATER_FEATURE_ID = "your-water-feature-id"BASE = "https://api-v2-prod-nyvjyh5ywa-uw.a.run.app"HEADERS = {"api-key": API_KEY}
def poll(grid_id: str) -> dict: while True: r = requests.get( f"{BASE}/domains/{DOMAIN_ID}/grids/{grid_id}", headers=HEADERS ).json() if r["status"] in ("completed", "failed"): return r time.sleep(5)
before = requests.get( f"{BASE}/domains/{DOMAIN_ID}/grids/{GRID_ID}", headers=HEADERS).json()
# Mask dead surface fuel under roads OR water: one rule per feature (a union# needs multiple rules — conditions inside one rule are ANDed).zero_dead_fuel = [ {"band": "fuel_load.1hr", "modifier": "replace", "value": 0}, {"band": "fuel_load.10hr", "modifier": "replace", "value": 0}, {"band": "fuel_load.100hr", "modifier": "replace", "value": 0},]grid = requests.post( f"{BASE}/domains/{DOMAIN_ID}/grids/{GRID_ID}/modifications", headers=HEADERS, json={ "modifications": [ { "conditions": [ {"source": "feature", "operator": "intersects", "feature_id": ROAD_FEATURE_ID, "target": "cell"} ], "actions": zero_dead_fuel, }, { "conditions": [ {"source": "feature", "operator": "intersects", "feature_id": WATER_FEATURE_ID, "target": "cell"} ], "actions": zero_dead_fuel, }, ] },).json()print(grid["status"]) # -> pendingprint(before["checksum"], "->", grid["checksum"]) # rotated immediately
grid = poll(GRID_ID)print(grid["status"], len(grid["modifications"])) # -> completed 2The rest of this page walks the individual recipes.
Reclassify a fuel model
Section titled “Reclassify a fuel model”The simplest rule: match cells by band value, replace the value. Here every
GR1 (101) cell in the FBFM grid becomes GR2 (102) — one condition,
one action, no spatial filter, so it applies grid-wide.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-fbfm40-grid-id/modifications' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "modifications": [ { "conditions": [ { "band": "fbfm", "operator": "eq", "value": 101 } ], "actions": [ { "band": "fbfm", "modifier": "replace", "value": 102 } ] } ]}'{ "id": "your-fbfm40-grid-id", "domain_id": "your-domain-id", "name": "FBFM40 fuel model (LANDFIRE 2024)", "description": "", "status": "pending", "progress": null, "created_on": "2026-07-09T19:26:17.776315Z", "modified_on": "2026-07-09T19:26:24.495424", "checksum": "1cbc63df401b49589bd00a0507278a23", "source": { "description": "LANDFIRE FBFM40 fuel model codes (Scott-Burgan 40 classification)", "alignment": { "target": "domain", "resolution": null, "method": null }, "name": "landfire", "product": "fbfm40", "remove_non_burnable": null, "extent_buffer_cells": 0, "version": "2024" }, "modifications": [], "bands": [ { "key": "fbfm", "type": "categorical", "unit": null, "index": 0, "nodata": 32767 } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "x": 1, "y": 1 } }, "tags": []}The 200 comes back pending with a freshly rotated checksum — the edit
runs server-side. Note the modifications array is still empty in this
response; the submitted rule lands there once the job completes (see
Poll to completion). On this domain the rule
reclassifies all 21 GR1 cells, leaving zero 101 cells and 113 102 cells.
Restrict a change to an area
Section titled “Restrict a change to an area”Add a spatial condition and the rule narrows to the intersection — the change now applies only where both conditions hold. This reclassifies GR1→GR2 only inside a polygon (here, the western half of the domain). Conditions in one rule are ANDed, so a cell must be GR1 and inside the polygon.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-fbfm40-grid-id/modifications' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "modifications": [ { "conditions": [ { "band": "fbfm", "operator": "eq", "value": 101 }, { "source": "geometry", "operator": "within", "geometry": { "type": "Polygon", "coordinates": [ [ [-114.085846, 48.054491], [-114.075000, 48.054491], [-114.075000, 48.070509], [-114.085846, 48.070509], [-114.085846, 48.054491] ] ] }, "crs": { "type": "name", "properties": { "name": "EPSG:4326" } } } ], "actions": [ { "band": "fbfm", "modifier": "replace", "value": 102 } ] } ]}'{ "id": "your-fbfm40-grid-id", "domain_id": "your-domain-id", "name": "FBFM40 fuel model (LANDFIRE 2024)", "description": "", "status": "pending", "progress": null, "created_on": "2026-07-09T19:26:24.842921Z", "modified_on": "2026-07-09T19:27:24.975616", "checksum": "4fcde4f150224e7abdd2327014652cf1", "source": { "extent_buffer_cells": 0, "remove_non_burnable": null, "product": "fbfm40", "version": "2024", "name": "landfire", "description": "LANDFIRE FBFM40 fuel model codes (Scott-Burgan 40 classification)", "alignment": { "method": null, "resolution": null, "target": "domain" } }, "modifications": [], "bands": [ { "key": "fbfm", "type": "categorical", "unit": null, "index": 0, "nodata": 32767 } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "x": 1, "y": 1 } }, "tags": []}Now only 17 of the 21 GR1 cells reclassify — the ones inside the polygon —
leaving 4 GR1 cells to the east untouched. Dropping the fbfm condition would
reclassify everything in the polygon; dropping the geometry condition
reclassifies GR1 everywhere. Each condition narrows the last.
Mask fuel under roads or water
Section titled “Mask fuel under roads or water”Roads and open water carry no surface fuel. To zero the dead-fuel bands wherever a road or a water feature touches a cell, use two rules — one per feature. This is the single most important pattern on the page.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-grid-id/modifications' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "modifications": [ { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-road-feature-id", "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.10hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] }, { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-water-feature-id", "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.10hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] } ]}'{ "id": "your-grid-id", "domain_id": "your-domain-id", "name": "Surface fuel loads (FBFM40 lookup)", "description": "", "status": "pending", "progress": null, "created_on": "2026-07-09T19:27:25.325386Z", "modified_on": "2026-07-09T19:27:32.131931", "checksum": "891609fa22734553b55f3fe620ebd4e4", "source": { "source_grid_checksum": "c07b83217e914891b027d8b3f3aa7b5c", "source_band": "fbfm", "table": "fbfm40", "name": "lookup", "source_grid_id": "your-fbfm40-grid-id" }, "modifications": [], "bands": [ { "key": "fuel_load.1hr", "type": "continuous", "unit": "kg/m**2", "index": 0, "nodata": null }, { "key": "fuel_load.10hr", "type": "continuous", "unit": "kg/m**2", "index": 1, "nodata": null }, { "key": "fuel_load.100hr", "type": "continuous", "unit": "kg/m**2", "index": 2, "nodata": null }, { "key": "fuel_load.live_herb", "type": "continuous", "unit": "kg/m**2", "index": 3, "nodata": null }, { "key": "fuel_load.live_woody", "type": "continuous", "unit": "kg/m**2", "index": 4, "nodata": null }, { "key": "fuel_depth", "type": "continuous", "unit": "m", "index": 5, "nodata": null } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "x": 1, "y": 1 } }, "tags": []}Each rule’s condition is a feature reference with target: "cell" — match
every cell the polygon touches — and its actions zero
fuel_load.1hr/10hr/100hr. On this domain the two rules together zero
fuel_load.1hr in 286 additional cells (1,686 → 1,972 zero-valued cells of
3,127).
Combine spatial and attribute in one rule
Section titled “Combine spatial and attribute in one rule”The ANDing that makes the road-or-water mistake a trap is exactly what you want when the conditions should compound. This zeroes the heavy 100-hour load only where it is both already high and inside a road buffer — leaving light roadside fuel alone.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-grid-id/modifications' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "modifications": [ { "conditions": [ { "band": "fuel_load.100hr", "operator": "gt", "value": 0.5 }, { "source": "feature", "operator": "intersects", "feature_id": "your-road-feature-id", "buffer_m": 10, "target": "cell" } ], "actions": [ { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] } ]}'{ "id": "your-grid-id", "domain_id": "your-domain-id", "name": "Surface fuel loads (FBFM40 lookup)", "description": "", "status": "pending", "progress": null, "created_on": "2026-07-09T19:27:32.532317Z", "modified_on": "2026-07-09T19:28:23.133064", "checksum": "ac4592f3c52d44edaca86a2f14fe29a2", "source": { "table": "fbfm40", "name": "lookup", "source_grid_checksum": "c07b83217e914891b027d8b3f3aa7b5c", "source_grid_id": "your-fbfm40-grid-id", "source_band": "fbfm" }, "modifications": [], "bands": [ { "key": "fuel_load.1hr", "type": "continuous", "unit": "kg/m**2", "index": 0, "nodata": null }, { "key": "fuel_load.10hr", "type": "continuous", "unit": "kg/m**2", "index": 1, "nodata": null }, { "key": "fuel_load.100hr", "type": "continuous", "unit": "kg/m**2", "index": 2, "nodata": null }, { "key": "fuel_load.live_herb", "type": "continuous", "unit": "kg/m**2", "index": 3, "nodata": null }, { "key": "fuel_load.live_woody", "type": "continuous", "unit": "kg/m**2", "index": 4, "nodata": null }, { "key": "fuel_depth", "type": "continuous", "unit": "m", "index": 5, "nodata": null } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "y": 1, "x": 1 } }, "tags": []}Of the 195 cells whose fuel_load.100hr exceeds 0.5 kg/m**2, only the 87
that also fall within the 10 m road buffer are zeroed. The attribute condition
and the spatial condition each cut the selection down.
Poll to completion
Section titled “Poll to completion”The checksum rotates the instant you POST, but the data is written and the
modifications list is updated only when the job completes. Poll the grid:
curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-fbfm40-grid-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'Status walks pending → running → completed. When it lands, the submitted
rule appears in modifications and the edited bands are in storage:
{ "id": "your-fbfm40-grid-id", "domain_id": "your-domain-id", "name": "FBFM40 fuel model (LANDFIRE 2024)", "description": "", "status": "completed", "progress": { "percent": 100, "message": "Complete" }, "created_on": "2026-07-09T19:26:17.776315Z", "modified_on": "2026-07-09T19:26:26.563860Z", "checksum": "1cbc63df401b49589bd00a0507278a23", "source": { "version": "2024", "extent_buffer_cells": 0, "remove_non_burnable": null, "product": "fbfm40", "name": "landfire", "description": "LANDFIRE FBFM40 fuel model codes (Scott-Burgan 40 classification)", "alignment": { "target": "domain", "method": null, "resolution": null } }, "modifications": [ { "conditions": [ { "band": "fbfm", "operator": "eq", "value": 101 } ], "actions": [ { "band": "fbfm", "modifier": "replace", "value": 102 } ] } ], "bands": [ { "key": "fbfm", "type": "categorical", "unit": null, "index": 0, "nodata": 32767 } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "y": 1, "x": 1 } }, "tags": []}The rotated checksum is the staleness signal: any resource derived from this
grid — a resample, an FBFM lookup, an export — can compare checksums and tell
its input changed. To keep the pre-edit grid around,
branch it first.
Branch a scenario instead of overwriting
Section titled “Branch a scenario instead of overwriting”In-place modification edits the grid under its own id — the pre-edit state is gone. When you’d rather keep the original and explore a variant, duplicate first, then modify the copy.
POST .../duplicate makes a byte-for-byte clone: it copies the stored array
(it does not re-derive from LANDFIRE), carrying the grid’s source,
modifications, and checksum over verbatim.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-grid-id/duplicate' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Masked fuelscape (scenario B)", "tags": ["scenario-b"]}'{ "id": "id-of-the-duplicated-grid", "domain_id": "your-domain-id", "name": "Masked fuelscape (scenario B)", "description": "", "status": "pending", "progress": null, "created_on": "2026-07-09T19:35:31.254943", "modified_on": "2026-07-09T19:35:31.254943", "checksum": "891609fa22734553b55f3fe620ebd4e4", "source": { "table": "fbfm40", "name": "lookup", "source_grid_checksum": "c07b83217e914891b027d8b3f3aa7b5c", "source_grid_id": "your-fbfm40-grid-id", "source_band": "fbfm" }, "modifications": [ { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-road-feature-id", "buffer_m": null, "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.10hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] }, { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-water-feature-id", "buffer_m": null, "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.10hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] } ], "bands": [ { "key": "fuel_load.1hr", "type": "continuous", "unit": "kg/m**2", "index": 0, "nodata": null }, { "key": "fuel_load.10hr", "type": "continuous", "unit": "kg/m**2", "index": 1, "nodata": null }, { "key": "fuel_load.100hr", "type": "continuous", "unit": "kg/m**2", "index": 2, "nodata": null }, { "key": "fuel_load.live_herb", "type": "continuous", "unit": "kg/m**2", "index": 3, "nodata": null }, { "key": "fuel_load.live_woody", "type": "continuous", "unit": "kg/m**2", "index": 4, "nodata": null }, { "key": "fuel_depth", "type": "continuous", "unit": "m", "index": 5, "nodata": null } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "y": 1, "x": 1 } }, "tags": ["scenario-b"]}The 201 mints a new grid id — record it: id-of-the-duplicated-grid. The copy
starts pending while the background copy runs, but its checksum already
equals the source’s and its modifications are carried over intact. Poll it to
completed:
{ "id": "id-of-the-duplicated-grid", "domain_id": "your-domain-id", "name": "Masked fuelscape (scenario B)", "description": "", "status": "completed", "progress": null, "created_on": "2026-07-09T19:35:31.254943Z", "modified_on": "2026-07-09T19:35:31.619784Z", "checksum": "891609fa22734553b55f3fe620ebd4e4", "source": { "table": "fbfm40", "name": "lookup", "source_grid_checksum": "c07b83217e914891b027d8b3f3aa7b5c", "source_grid_id": "your-fbfm40-grid-id", "source_band": "fbfm" }, "modifications": [ { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-road-feature-id", "buffer_m": null, "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.10hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] }, { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-water-feature-id", "buffer_m": null, "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.10hr", "modifier": "replace", "value": 0 }, { "band": "fuel_load.100hr", "modifier": "replace", "value": 0 } ] } ], "bands": [ { "key": "fuel_load.1hr", "type": "continuous", "unit": "kg/m**2", "index": 0, "nodata": null }, { "key": "fuel_load.10hr", "type": "continuous", "unit": "kg/m**2", "index": 1, "nodata": null }, { "key": "fuel_load.100hr", "type": "continuous", "unit": "kg/m**2", "index": 2, "nodata": null }, { "key": "fuel_load.live_herb", "type": "continuous", "unit": "kg/m**2", "index": 3, "nodata": null }, { "key": "fuel_load.live_woody", "type": "continuous", "unit": "kg/m**2", "index": 4, "nodata": null }, { "key": "fuel_depth", "type": "continuous", "unit": "m", "index": 5, "nodata": null } ], "georeference": { "crs": "EPSG:32611", "transform": [ 29.749120343527053, 0.0, 717140.0, 0.0, -29.749120343523888, 5328279.198100268 ], "shape": [59, 53] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "y": 1, "x": 1 } }, "tags": ["scenario-b"]}Same checksum, same source, same modifications — only the id, name,
and tags differ. Now apply any recipe above to id-of-the-duplicated-grid and
only the copy changes; the original keeps the checksum shown here. Two
things to watch:
-
Using the copy before it’s
completed. The clone ispendinguntil the background copy finishes; its data endpoints422until then. Poll first.{"detail": "grids/id-of-the-duplicated-grid status is 'pending', expected 'completed'."} -
Duplicating a non-
completedgrid. The source must becompleted— there’s nothing stable to copy until it finishes building.{"detail": "grids/your-grid-id status is 'pending', expected 'completed'."}
If it fails
Section titled “If it fails”If processing fails, the grid lands in status: failed with an error
message, and its stored data is left unchanged — a failed in-place edit
never partially rewrites the grid. The rules you submitted stay queued on the
grid, so once you’ve addressed the cause you can POST the same (or a
corrected) modification again to retry.
Common pitfalls
Section titled “Common pitfalls”-
One rule for two features (AND vs OR). The number-one mistake. A road condition and a water condition in one rule match only cells inside both — almost none. Use one rule per feature, as in Mask fuel under roads or water.
-
Bare linestrings and
target: "centroid". Persisted OSM road features are widened to polygons, sotarget: "cell"catches every cell they touch with no buffer. But an inlineLineStringhas zero area, and the defaulttarget: "centroid"reduces each cell to its center point — a thin geometry slips between centroids and matches almost nothing. Usetarget: "cell"and a non-zerobuffer_mfor narrow shapes. -
Referencing a band the grid doesn’t have. The rule is validated against the grid’s bands up front — a missing band fails fast with a
422, and the grid is left untouched:{"detail": "Source grid your-grid-id is missing required bands: ['fbfm']. Available bands: ['fuel_depth', 'fuel_load.100hr', 'fuel_load.10hr', 'fuel_load.1hr', 'fuel_load.live_herb', 'fuel_load.live_woody']"} -
3D voxel grids are rejected. In-place modification is a 2D operation; a 3D canopy (voxel) grid returns a
422. Edit the source tree inventory and re-voxelize instead:{"detail": "Modifications are not supported for 3D voxel grids. Apply modifications to the source tree inventory and re-voxelize instead."}
Next steps
Section titled “Next steps”- Inspect the result — fetch and stream the grid data.
- Do the same to trees — modify an inventory.