Skip to content

Upload a GeoTIFF to create a grid

You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.

This guide creates a grid resource from a GeoTIFF you supply. Use it whenever you have a raster — fuels, topography, a land-cover classification, anything that fits a 2D georeferenced image — that you want to attach to a domain so the rest of the FastFuels API can operate on it.

The endpoint is POST /domains/{domain_id}/grids/upload/geotiff and the workflow is three calls:

  1. Create the grid — the API returns a 201 Created and a short-lived signed URL.
  2. Upload the file — PUT the GeoTIFF to that signed URL; GCS returns 200 OK.
  3. Poll the grid — GET it until status is completed (or failed).
  1. An API key. Create one in the FastFuels web app under your account settings. Every request below sends it in the api-key header — set it once here: my-api-key. It will propagate to every code block on the page.

  2. A domain. All grids hang off a domain. If you do not have one yet, see the Create a domain how-to guide, then come back here. Plug your domain’s id in once: your-domain-id.

  3. A GeoTIFF that meets these constraints:

    • Format: single- or multi-band GeoTIFF (.tif / .tiff).
    • CRS: must match the domain CRS exactly.
    • Extent: must cover the domain bbox. Pixels outside the bbox (or outside the bbox expanded by num_buffer_cells × native_pixel_size) are clipped on ingest.
    • Size: ≤ 1 GB.

Submit a POST to /domains/{{DOMAIN_ID}}/grids/upload/geotiff. The body declares the bands of the GeoTIFF you are about to upload: bands[i] maps 1-to-1 to GeoTIFF band i + 1. Each band’s key becomes the variable name on the resulting grid.

Two shapes cover most uploads:

A single band — categorical (a classification raster, e.g. fuel-model codes) or continuous (e.g. an elevation surface). unit is optional and typically omitted for categorical bands.

Create grid — single-band GeoTIFF
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/upload/geotiff' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"bands": [
{ "key": "fbfm", "type": "categorical" }
],
"name": "Custom FBFM40"
}'

Four fields on the response matter for the next steps:

  • grid.id — record this; you’ll need it to poll status.
  • upload.url — the signed URL you PUT your GeoTIFF to. Single use, valid for one hour.
  • upload.content_type — must be sent as the Content-Type header on the PUT. Always image/tiff.
  • upload.max_size_bytes — must be echoed as x-goog-content-length-range: 0,<max_size_bytes> on the PUT. GCS rejects the request with 400 Bad Request if this header is missing or doesn’t match.

PUT the file directly to the signed URL. The request does not go through the FastFuels API — it goes to Google Cloud Storage. Send no api-key header. Two request headers are required and both must match the values returned in step 1:

  • Content-Type: image/tiff — from upload.content_type.
  • x-goog-content-length-range: 0,<max_size_bytes> — from upload.max_size_bytes.
PUT GeoTIFF to signed URL
curl -X 'PUT' \
'paste-signed-url-from-201-response' \
-H 'Content-Type: image/tiff' \
-H 'x-goog-content-length-range: 0,1073741824' \
--data-binary '@/path/to/your.tif' \
-o /dev/null -w 'HTTP %{http_code}\n'

A successful upload returns an empty body with HTTP 200 OK. The uploader service is then triggered automatically.

Step 3 — Poll the grid until it is completed

Section titled “Step 3 — Poll the grid until it is completed”

The uploader opens the GeoTIFF, validates the CRS against the domain, clips to the domain extent, and writes the result into the grid’s storage. While that runs, GET the grid to watch its status.

GET grid status
curl -X 'GET' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-grid-id' \
-H 'accept: application/json' \
-H 'api-key: my-api-key'

Status walks pending → running → completed (or → failed). Only the last two are terminal — keep polling on the first two.

{
"id": "your-grid-id",
"domain_id": "your-domain-id",
"name": "Custom FBFM40",
"description": "",
"status": "pending",
"progress": null,
"created_on": "2026-05-19T16:32:41.567669Z",
"modified_on": "2026-05-19T16:32:41.567669Z",
"source": {
"bands": [{ "key": "fbfm", "unit": null, "type": "categorical" }],
"num_buffer_cells": 0,
"format": "geotiff",
"name": "upload",
"object_name": "grids/your-grid-id/upload.tif"
},
"modifications": [],
"bands": [{ "key": "fbfm", "type": "categorical", "unit": null, "index": 0 }],
"georeference": null,
"error": null,
"chunks": null,
"tags": []
}

Keep polling. The status sits at pending until the uploader picks up the file, then moves to running while it validates CRS, clips to the domain, and writes the result. Typical end-to-end time is a few seconds for small rasters, up to a minute or two for files near the 1 GB ceiling.

By default, the uploader keeps exactly the pixels that fall inside the domain bbox. If your raster’s extent is smaller than the domain bbox, the upload will fail validation; if it is larger, the extra pixels are clipped away silently.

Two knobs control how forgiving this is:

  • num_buffer_cells in the create request (default 0). Setting this to e.g. 2 keeps two native-resolution cells of padding around the domain on each side, which is useful when downstream operations (resampling, focal filters) are edge-sensitive.
  • The domain extent itself. If you want FastFuels to ingest a region larger than your domain, expand the domain — domains define what “in scope” means for every grid hanging off them.

The expression that must hold is:

GeoTIFF extent ⊇ domain bbox expanded by (num_buffer_cells × native_pixel_size)

Each entry in the bands array describes one raster band:

FieldRequiredDescription
keyyesDot-notation variable name (e.g. elevation, bulk_density.foliage). Becomes the variable name on the grid. Must be unique within the upload.
typeyes"continuous" for numeric measurements, "categorical" for codes/IDs.
unitoptionalUDUNITS-2 canonical ASCII with ** for exponents (e.g. kg/m**3, 1/m, %, m). Omit for categorical bands; required for continuous bands where the value carries a physical unit.
  • CRS_MISMATCH after step 2 succeeds. The PUT returns 200 even if the uploaded file’s CRS does not match the domain. The mismatch surfaces in step 3 as status: "failed". Reproject with gdalwarp -t_srs <domain_crs> and re-create the upload.
  • Duplicate key in the create request. Rejected with 422 Unprocessable Entity. Each band key must be unique within a single upload.
  • Wrong band order. bands[i] is GeoTIFF band i + 1. Re-check with gdalinfo your.tif before submitting.
  • Treating the upload URL as reusable. It is signed for a single PUT within one hour. If the file changes, request a fresh URL.