Skip to content

Introduction to the FastFuels API

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

The FastFuels API is a RESTful web service that turns a polygon you draw on a map into the high-resolution, three-dimensional fuel inputs that physics-based wildfire models — QUIC-Fire, FIRETEC, FDS — need to run. You send it JSON over HTTPS describing where you are working and what fuel data you want; it fetches the national datasets, runs the fuel science, and hands back voxelized canopy, surface fuels, topography, and the rest, packaged for a fire model. It is the managed, multi-user, cloud-hosted form of the open-source FastFuels fuel-modeling stack, reachable from any HTTP client in any language.

This page explains the API itself: what it is, why FastFuels is offered as a hosted service in addition to the open-source library, and how it works as a system. It is the conceptual frame the rest of the Explanation section builds on; the resource pages (Domains, Features, Inventories) each cover one part of the model introduced here. For procedures, see the QUIC-Fire tutorial, which runs the full pipeline, and the how-to guides, which cover individual tasks. For the field-by-field reference, see the live OpenAPI documentation the service publishes at /docs.

The fuel science is an open-source Python library, fastfuels-core: the crown models, the allometry, the rasterization and voxelization routines all live there, and in principle you could pip install it and run it on your laptop. The API exists because turning that science into a usable landscape fuelbed requires four things that a local script handles poorly:

  • Managed access to national data. A fuelbed is assembled from large, awkward source datasets — LANDFIRE surface fuels, 3DEP elevation, TreeMap forest-plot imputations, NAIP-derived canopy height, OpenStreetMap roads and water. These are national in extent, distributed in formats and projections that differ from one another, and far too large to download whole. The API holds them server-side and does the fetch-clip-reproject work for you: you name a source, and it returns the slice of that dataset aligned to your area, in your domain’s coordinate system. You never provision the data or the geospatial toolchain to read it.

  • Compute delegation. Expanding a TreeMap plot into individual trees, detecting crowns from a height surface, voxelizing a stand into a 3D canopy, rasterizing surface fuels — these are heavy, and they spike. The API offloads them to specialized cloud workers that scale up under load and idle when there’s none, so a large job doesn’t tie up your machine and you don’t install GDAL, rasterio, and a fuel-science stack to run it.

  • A standard schema and reproducibility. Before FastFuels v2, building 3D fuel inputs meant a bespoke pipeline per research group: no shared schema, inconsistent methods, and results that were hard to reproduce. The API is the authoritative interface to a single national 3D-fuel schema. Every resource records its full provenance — which source, which version, which parameters — so a result can be examined, traced, and recreated exactly, as scientific work requires.

  • Shared, multi-user, programmatic access. One backend sits behind both the web application and the API, so a domain built in the browser is the same resource your script reads back. API keys make access programmatic and attributable, and because the contract is plain HTTP and JSON, you reach it from Python, R, JavaScript, a shell — or the browser — without a FastFuels-specific runtime.

Under the hood the API is a FastAPI service speaking JSON over HTTPS. It publishes interactive, machine-readable documentation it generates from its own code: a Swagger UI at /docs, a ReDoc view at /redoc, and the raw schema at /openapi.json. This generated documentation is the canonical field-by-field reference for every endpoint and schema.

The organizing idea is resource orientation. Every entity the API manages — a domain, a feature, an inventory, a grid, an export — is a resource with a stable, server-generated ID and the same five standard operations:

OperationHTTP verbSuccessReturns
CreatePOST201the full new resource
GetGET .../{id}200the full resource
ListGET ...200a paginated, sortable collection
UpdatePATCH .../{id}200the updated resource
DeleteDELETE .../{id}204empty

Updates use PATCH: you send only the fields you want to change. The conventions are uniform across every resource — snake_case field names, created_on / modified_on timestamps, the same pagination and sorting on every list — so once you have learned one resource, the others behave the same way. Creating a domain, for instance, is a single POST carrying a GeoJSON FeatureCollection and an api-key header:

POST /domains
api-key: <your-api-key>
Content-Type: application/json
{ "type": "FeatureCollection", "features": [ … ], "name": "Blue Mountain" }

The 201 response contains the created domain: its server-assigned id, the projected CRS the server computed, and the working extent. Every operation follows this pattern — you send JSON and receive the full resource, including an id to use in later requests.

Resources form a hierarchy. The domain is the parent container for everything built over an area of ground, and its children live under domain-scoped URLs:

/domains/{domain_id}
├─ /features roads, water, uploaded layersets
├─ /inventories trees (TreeMap, CHM, uploaded)
├─ /pointclouds uploaded lidar / photogrammetry
└─ /grids rasterized & voxelized fuelbed
/exports packaged fire-model inputs (top-level; outlive their domain)

The URL nesting reflects ownership: a grid for a domain is addressed at /domains/{domain_id}/grids/{grid_id}, and every child document also stores its own domain_id, so its parent is identifiable without parsing the URL. Exports are an exception — they are created under a domain, but their lifecycle is top-level, so an export survives the deletion of the domain it was built in. About domains covers the resource hierarchy in full, and the other resource pages build on it.

Every request carries an api-key header. You mint keys in the web applicationpersonal keys attributed to your account, or application keys attributed to an application for shared, multi-user work — each with a read or read-write scope and an expiration you set. A read-scoped key can GET but not modify; a write-scoped key can create, update, and delete. The mechanics of creating and managing keys are covered in the web-app guide API Keys; here it is enough to know that the key in the header is what identifies you and bounds what you may do.

Operations come in two kinds, because the work behind them differs greatly in cost.

Synchronous calls do their work within the request and return the finished resource. Creating a domain is synchronous: the server validates and reprojects your polygon and hands back the completed domain in the same 201. Uploading a layerset is synchronous too — it is validated and stored in the one call.

Asynchronous calls start a long-running job and return immediately, before the work is done. Anything that fetches a national dataset, expands a tree list, detects crowns, voxelizes a stand, or assembles an export is asynchronous: the POST returns a resource with status: "pending", a background worker picks it up, and you poll the resource until it finishes. A create response for an async grid looks like this — note the empty georeference, which the worker fills in only on completion:

{
"id": "{grid_id}",
"domain_id": "{domain_id}",
"status": "pending", // job has not started
"progress": null,
"georeference": null, // populated when status becomes "completed"
"source": { "name": "landfire", "product": "fbfm40", "version": "2024" }
}

Every async resource shares the same lifecycle and the same status fields:

pending ──▶ running ──▶ completed
└───▶ failed
  • status moves pending → running → completed (or failed).
  • progress carries a percent and a human-readable message ("Fetching LANDFIRE fbfm40 v2024…") while it runs.
  • error is populated on failure with a code, a message, and often a suggestion for how to fix it.

You watch a job by re-GET-ting the resource until its status settles:

GET /domains/{domain_id}/grids/{grid_id} → status: "running", progress: 25%
GET /domains/{domain_id}/grids/{grid_id} → status: "completed", georeference: { … }

There is no separate job or operation object to track. The resource itself carries the status, progress, and error fields, so you poll the resource you created to follow its progress.

Reading data back: metadata, partitions, and signed URLs

Section titled “Reading data back: metadata, partitions, and signed URLs”

A resource’s metadata — its status, CRS, bounding box, provenance, the names of its bands or columns — is small, and a plain GET returns it. A resource’s data — millions of trees, a multi-band raster of cells — can be enormous, so it is never inlined into that response. It is served separately, in pieces:

  • A data/metadata endpoint returns the shape of the dataset — total row or feature count, how it is partitioned — by reading only a file footer, so it is cheap. It is how you find out, for example, whether a fetched road feature actually contains any geometry before you rely on it.
  • A data/{partition} endpoint (or, for a grid, data/{band}/{chunk}) returns one partition at a time. You stream a whole dataset by reading the partition count from the metadata and looping over the chunks.

Large files move through signed URLs rather than the API itself. To bring your own data in — a GeoTIFF, a NetCDF, a tree list — the API hands you a time-limited URL you PUT the file to directly, and a worker processes it once it lands. To take a finished export out, the export resource carries a signed_url you download from. The API brokers the transfer; the bytes flow straight to and from cloud storage, so neither huge uploads nor huge downloads pass through the request path.

Calling the API: raw HTTP and the Python SDK

Section titled “Calling the API: raw HTTP and the Python SDK”

Because the API is plain HTTP and JSON, you can use any HTTP client. The how-to guides give every example in two forms — a curl command and a Python snippet using the requests library — neither of which requires anything FastFuels-specific:

import requests
resp = requests.post(
"https://<api-host>/domains",
headers={"api-key": "<your-api-key>"},
json={"type": "FeatureCollection", "features": [...], "name": "Blue Mountain"},
)
domain_id = resp.json()["id"] # carry this into the next call

For Python users there is also a dedicated client, the FastFuels Python SDK (pip install fastfuels-sdk). It is a convenience layer over the same HTTP API: it mirrors the resource model in Python objects, holds your key, and absorbs the boilerplate of polling async jobs to completion and paging through partitioned data. It provides no capability beyond the HTTP API; it reduces the boilerplate of the common workflows.

The API follows a small set of design principles drawn from standard resource-oriented API design. They explain patterns that recur across every resource:

  • Self-documenting resources. A resource carries everything needed to understand how it was made — its source, its modifications, its bands — so you can reconstruct how it was built from its own representation.
  • Explicit relationships. Resources reference each other by ID, you choose which grids combine and the role each plays, and constraints are validated by the server.
  • Reproducibility. The stored schema keeps every parameter needed to reproduce a result — source, version, transformations, seed. Re-running the same inputs produces the same output, which is why a random step such as the TreeMap expansion records a seed.
  • Composable primitives. The API provides primitives you combine into your own workflow. A domain holds many grids and inventories, you assemble exports yourself, and operations such as resample and blend can be chained, so simple cases stay straightforward and complex workflows remain achievable.
  • Unified schemas. Surface fuels, canopy, topography, and barriers all use the same Grid type, distinguished by validated content; every grid operation therefore works on every grid.

Together these produce the API’s consistency: the same status fields on every async resource, a source record on every resource, a single grid type, and PATCH for metadata updates.

The whole stack is open source, and the pieces map onto what this page describes:

  • the API service itself — FastFuels-API-v2 — the FastAPI service and the cloud workers behind it;
  • the fuel science the workers run — fastfuels-core — the core algorithms driving FastFuels;
  • the Python SDKfastfuels-sdk-python — the client described above.

Because the source code is open, you can inspect exactly how a result was produced, verify it, report issues, or run the stack yourself. The hosted service provides the managed data, compute, and shared access; the open-source code lets you audit and reproduce what it returns.

  • About domains — the root of the resource model, and the right next page: every other resource is created inside a domain and inherits its frame.
  • About features and About inventories — the vector and the discrete-object branches of the model, with their own companions for modifications and CHM tree detection.
  • Generate QUIC-Fire simulation inputs — the end-to-end pipeline, where everything above is put to work in order.
  • The how-to guides — the concrete procedures (create a domain, fetch a grid, build an inventory) in curl and Python.
  • The live OpenAPI documentation at the service’s /docs — the canonical, field-by-field reference for every endpoint and schema.