§API · v1

The Rootfast API

A small, JSON-only HTTP API over USDA PLANTS data. No keys, no rate limits in v1, no terms beyond attribution. Try every example below — it hits the live service.

Base URL · https://api.rootfast.org


§1Service health & version

GET

/healthz

Returns dataset version and applied schema migration. Useful as a liveness check.

Request

curl 'https://api.rootfast.org/healthz'

Response · 200

{
  "ok": true,
  "dataset_version": "0.0.1",
  "schema_version": "001"
}

Try it → https://api.rootfast.org/healthz

§2One plant, in full

GET

/v1/plants/{usda_symbol}

Returns a plant record with growth habit, duration, native status by region, every USDA characteristic, and all Montana counties where the taxon is recorded. Resolves synonyms transparently.

Request

curl 'https://api.rootfast.org/v1/plants/PIPO'

Response · 200

{
  "plant": {
    "usda_symbol": "PIPO",
    "scientific_name": "Pinus ponderosa Lawson & C. Lawson",
    "scientific_name_canonical": "Pinus ponderosa",
    "common_name": "ponderosa pine",
    "family": "Pinaceae Spreng. ex Rudolphi",
    "gbif_taxon_key": 5285053,
    "gbif_match_type": "EXACT",
    "rank": "Species",
    "...": "..."
  },
  "growth_habits": ["Tree"],
  "durations": ["Perennial"],
  "native_status": [
    { "region_code": "L48", "status": "Native" },
    { "region_code": "CAN", "status": "Native" }
  ],
  "characteristics": { "drought_tolerance": "High", "...": "..." },
  "counties": [
    { "county_fips": "30031", "county_name": "Gallatin", "state_code": "MT", "nativity": "Native" }
  ]
}

Try it → https://api.rootfast.org/v1/plants/PIPO

§3Flat catalogue

GET

/v1/plants?{filters}&format={json|csv|ndjson}

Every taxon in the catalogue as a flat list, one row per plant — no county aggregation. Accepts the filter parameters under §F (except `nativity`, which requires a distribution join). Use `format=csv` or `format=ndjson` to download in bulk; the response is sent as an attachment with a `rootfast-plants.csv` filename. JSON pagination defaults to 5000 (max 10000); bulk downloads return up to 50000 in a single call.

Request

curl 'https://api.rootfast.org/v1/plants?family=Pinaceae&limit=2'

Response · 200

{
  "count": 2,
  "total": 45,
  "paging": { "limit": 2, "offset": 0, "has_more": true },
  "filters": { "family": "Pinaceae" },
  "plants": [
    {
      "usda_symbol": "ABAM",
      "scientific_name": "Abies amabilis (Douglas ex Loudon) Douglas ex Forbes",
      "common_name": "Pacific silver fir",
      "family": "Pinaceae Spreng. ex Rudolphi",
      "genus": "Abies",
      "rank": "Species",
      "gbif_taxon_key": 5285101,
      "...": "..."
    }
  ]
}

Try it → https://api.rootfast.org/v1/plants?family=Pinaceae&limit=2

§4Plant finder

POST

/v1/plants/search

Cross-cutting search that takes a geographic scope (counties or states) plus filter criteria and returns plants ranked by how many of the requested counties they appear in. Useful for "I live near these places — what fits my conditions?" queries that don't fit a single county or state. Accepts every filter param from §F inside a `filters` object. Default limit 200, max 1000.

Request

curl -X POST 'https://api.rootfast.org/v1/plants/search' \
  -H 'content-type: application/json' \
  -d '{
  "counties": ["30031", "30057", "30077"],
  "nativity": "Native",
  "filters": {
    "growth_habit": "Tree",
    "drought_tolerance": "High"
  },
  "limit": 5,
  "include_counties": true
}'

Response · 200

{
  "scope": { "counties_requested": 3, "states_requested": null },
  "filters": { "growth_habit": "Tree", "drought_tolerance": "High" },
  "nativity": "Native",
  "count": 5,
  "limit": 5,
  "plants": [
    {
      "usda_symbol": "ARTR2",
      "scientific_name": "Artemisia tridentata Nutt.",
      "common_name": "big sagebrush",
      "family": "Asteraceae Bercht. & J. Presl",
      "matching_county_count": 3,
      "requested_county_count": 3,
      "match_score": 1.0,
      "nativity_rollup": "Native",
      "matching_counties": ["30031", "30057", "30077"]
    }
  ]
}

§5Plant synonyms

GET

/v1/plants/{usda_symbol}/synonyms

List every synonym known for the plant, drawn from two sources: USDA's Synonyms tab (`usda_synonyms_tab` — names without their own plant record) and any sibling rows in the plants table whose `accepted_plant_id` points back to this taxon (`plants_table`). The endpoint accepts either an accepted symbol or a synonym symbol — synonyms transparently resolve to the canonical plant and the response reports whether resolution happened via `resolved_from_synonym`.

Request

curl 'https://api.rootfast.org/v1/plants/EQAR/synonyms'

Response · 200

{
  "accepted": {
    "usda_symbol": "EQAR",
    "scientific_name": "Equisetum arvense L.",
    "scientific_name_canonical": "Equisetum arvense",
    "common_name": "field horsetail",
    "family": "Equisetaceae Michx. ex DC.",
    "gbif_taxon_key": 7924597
  },
  "queried_symbol": "EQAR",
  "resolved_from_synonym": false,
  "count": 5,
  "synonyms": [
    { "usda_symbol": "EQARA", "scientific_name": "Equisetum arvense L. var. alpestre Wahlenb.",  "source": "usda_synonyms_tab" },
    { "usda_symbol": "EQARB", "scientific_name": "Equisetum arvense L. var. boreale (Bong.) Rupr.", "source": "usda_synonyms_tab" },
    { "usda_symbol": "EQCA",  "scientific_name": "Equisetum calderi B. Boivin",                    "source": "usda_synonyms_tab" }
  ]
}

Try it → https://api.rootfast.org/v1/plants/EQAR/synonyms

§6Reverse lookup by GBIF

GET

/v1/plants/by-gbif/{taxon_key}

Fetch the plant detail document by its GBIF Backbone taxon_key instead of by USDA symbol. Useful for joining rootfast to any dataset that already speaks GBIF (iNaturalist, ITIS, eBird, BHL, etc.). Response shape is identical to `/v1/plants/{usda_symbol}`. When multiple USDA plants map to the same GBIF key (synonyms), the accepted entry wins.

Request

curl 'https://api.rootfast.org/v1/plants/by-gbif/5285053'

Response · 200

{
  "plant": {
    "usda_symbol": "PIPO",
    "scientific_name": "Pinus ponderosa Lawson & C. Lawson",
    "common_name": "ponderosa pine",
    "gbif_taxon_key": 5285053,
    "gbif_match_type": "EXACT",
    "...": "..."
  },
  "photo": { "url": "...", "thumb_url": "...", "license": "cc-by-nc" },
  "growth_habits": ["Tree"],
  "durations": ["Perennial"],
  "characteristics": { "...": "..." },
  "counties": [ { "county_fips": "30031", "county_name": "Gallatin", "state_code": "MT", "nativity": "Native" } ]
}

Try it → https://api.rootfast.org/v1/plants/by-gbif/5285053

§7A random plant

GET

/v1/plants/random

Returns one random photographed species from the catalogue. Good for "plant of the day" widgets and seed examples. Cached at the edge for 60 s so it rotates often but cheaply.

Request

curl 'https://api.rootfast.org/v1/plants/random'

Response · 200

{
  "plant": {
    "usda_symbol": "EPSA",
    "scientific_name": "Epilobium saximontanum Hausskn.",
    "common_name": "Rocky Mountain willowherb",
    "family": "Onagraceae Juss.",
    "photo_thumb_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/.../square.jpg",
    "photo_url":       "https://inaturalist-open-data.s3.amazonaws.com/photos/.../medium.jpg"
  }
}

Try it → https://api.rootfast.org/v1/plants/random

§8All counties, paginated

GET

/v1/counties?state={code}&limit={n}&offset={n}

Flat list of every county in the catalogue, optionally filtered by state. Default limit is 500, max 5000. Each row carries the FIPS code, state code, centroid lat/lon for mapping.

Request

curl 'https://api.rootfast.org/v1/counties?state=RI'

Response · 200

{
  "count": 5,
  "total": 5,
  "limit": 500,
  "offset": 0,
  "has_more": false,
  "counties": [
    {
      "county_fips": "44001",
      "county_name": "Bristol",
      "state_code": "RI",
      "centroid_lat": 41.7,
      "centroid_lon": -71.28
    }
  ]
}

Try it → https://api.rootfast.org/v1/counties?state=RI

§9Plants in a county

GET

/v1/counties/{county_fips}/plants?{filters}

All taxa recorded in the given county, with nativity. FIPS is the 5-digit Census code (Gallatin County, MT is 30031). Accepts the filter parameters under §F.

Request

curl 'https://api.rootfast.org/v1/counties/30031/plants'

Response · 200

{
  "county": {
    "county_fips": "30031",
    "county_name": "Gallatin",
    "state_code": "MT"
  },
  "count": 1362,
  "plants": [
    {
      "usda_symbol": "ABLA",
      "scientific_name": "Abies lasiocarpa (Hook.) Nutt.",
      "common_name": "subalpine fir",
      "family": "Pinaceae Spreng. ex Rudolphi",
      "nativity": "Native"
    }
  ]
}

Try it → https://api.rootfast.org/v1/counties/30031/plants

§10List of states

GET

/v1/states

Every state with ingested data, plus county and taxon counts. Today this returns Montana only — nationwide ingest is on the roadmap (see /about).

Request

curl 'https://api.rootfast.org/v1/states'

Response · 200

{
  "count": 1,
  "states": [
    {
      "state_code": "MT",
      "state_name": "Montana",
      "state_fips": "30",
      "country": "US",
      "county_count": 56,
      "taxa_count": 3215
    }
  ]
}

Try it → https://api.rootfast.org/v1/states

§11State header

GET

/v1/states/{state_code}

Single-state header with county and taxon counts. State code is the two-letter postal abbreviation (case-insensitive).

Request

curl 'https://api.rootfast.org/v1/states/MT'

Response · 200

{
  "state": {
    "state_code": "MT",
    "state_name": "Montana",
    "state_fips": "30",
    "country": "US",
    "county_count": 56,
    "taxa_count": 3215
  }
}

Try it → https://api.rootfast.org/v1/states/MT

§12Plants in a state

GET

/v1/states/{state_code}/plants?limit&offset&{filters}

Every taxon in the state, aggregated across counties. county_count is how many counties the taxon appears in; native_counties / introduced_counties / both_counties give the breakdown. nativity is the state-level rollup: "Native" if no introduced records, "Introduced" if no native records, "Both" if mixed or any county records "Both". Paginated (default limit 5000, max 10000) and accepts the filter parameters under §F.

Request

curl 'https://api.rootfast.org/v1/states/MT/plants'

Response · 200

{
  "state": {
    "state_code": "MT",
    "state_name": "Montana",
    "state_fips": "30",
    "country": "US"
  },
  "counts": { "taxa": 3215, "counties": 56 },
  "paging": { "limit": 5000, "offset": 0, "count": 3215, "has_more": false },
  "plants": [
    {
      "usda_symbol": "PIPO",
      "scientific_name": "Pinus ponderosa Lawson & C. Lawson",
      "common_name": "ponderosa pine",
      "family": "Pinaceae Spreng. ex Rudolphi",
      "photo_thumb_url": null,
      "county_count": 22,
      "native_counties": 22,
      "introduced_counties": 0,
      "both_counties": 0,
      "nativity": "Native"
    }
  ]
}

Try it → https://api.rootfast.org/v1/states/MT/plants

§13Counties in a state

GET

/v1/states/{state_code}/counties

Lightweight listing of every county in the state with its taxa count. Useful for state-overview pages and county pickers.

Request

curl 'https://api.rootfast.org/v1/states/MT/counties'

Response · 200

{
  "state": { "state_code": "MT", "state_name": "Montana", "state_fips": "30", "country": "US" },
  "count": 56,
  "counties": [
    {
      "county_fips": "30001",
      "county_name": "Beaverhead",
      "state_code": "MT",
      "centroid_lat": 45.07,
      "centroid_lon": -112.86,
      "taxa_count": 755
    }
  ]
}

Try it → https://api.rootfast.org/v1/states/MT/counties

§BBulk download

Take the data.

The list endpoints accept ?format=csv or ?format=ndjson for archival / spreadsheet / pipeline use. The response is sent as an attachment with a sensible filename; Content-Type is set to text/csv or application/x-ndjson as appropriate. Bulk downloads default to no pagination cap (50 000 rows max per call); apply filters under §F to scope the result.

Endpoints that support bulk

  • · /v1/plants — flat catalogue dump
  • · /v1/counties — every US county (also accepts format=geojson — see below)
  • · /v1/states/{code}/plants — one state's taxa
  • · /v1/counties/{fips}/plants — one county's taxa

GeoJSON output

/v1/counties?format=geojson returns a standard GeoJSON FeatureCollection. Each feature is a Point at the county centroid (we don't store full polygons yet); properties carry the FIPS code, county name, and state. Drop the URL straight into Leaflet, Mapbox GL, deck.gl, QGIS, or Mapshaper. Content-Type is application/geo+json.

# Every Rhode Island county as GeoJSON
curl 'https://api.rootfast.org/v1/counties?state=RI&format=geojson'

# In Leaflet:
fetch('https://api.rootfast.org/v1/counties?state=RI&format=geojson')
  .then(r => r.json())
  .then(fc => L.geoJSON(fc).addTo(map));

Examples

# Whole catalogue as CSV
curl -O 'https://api.rootfast.org/v1/plants?format=csv'

# All MT plants as NDJSON (one JSON object per line)
curl 'https://api.rootfast.org/v1/states/MT/plants?format=ndjson' \
  > mt-plants.ndjson

# Drought-tolerant native trees in Gallatin County as CSV
curl 'https://api.rootfast.org/v1/counties/30031/plants?\
growth_habit=Tree&nativity=Native&drought_tolerance=High&format=csv'

# Every US county as CSV (FIPS, name, state, centroid)
curl 'https://api.rootfast.org/v1/counties?format=csv&limit=5000'

All data is CC0 / public domain — use it freely. A citation back to rootfast.org is appreciated but not required.

§FFilter parameters

Narrow the list.

The list endpoints /v1/states/{code}/plants and /v1/counties/{fips}/plants accept query parameters that filter the result set. Multiple parameters AND together. Enum parameters accept comma-separated lists (OR within the parameter). Invalid values return 400 bad_request.

Plant-level

family
Prefix match against the USDA family string. Example: ?family=Pinaceae.
genus
Exact match. Example: ?genus=Pinus.
rank
One of Species,Subspecies,Variety,Form.
growth_habit
One of Tree,Shrub,Subshrub,Vine,Forb/herb,Forb,Graminoid,Nonvascular,Lichenous.
duration
One of Annual,Biennial,Perennial.
nativity
One of Native,Introduced,Both. Filters at the county-record level.

Characteristics — enums

drought_tolerance
High,Medium,Low,None
shade_tolerance
Intolerant,Intermediate,Tolerant
moisture_use
High,Medium,Low,None
growth_rate
Slow,Moderate,Rapid
toxicity
None,Slight,Moderate,Severe
lifespan
Short,Moderate,Long
commercial_availability
No Known Source,Routinely Available,Field Collections Only,Contracting Only
fire_tolerance
High,Medium,Low,None
salinity_tolerance
High,Medium,Low,None
nitrogen_fixation
High,Medium,Low,None

Characteristics — booleans

fire_resistant
Accepts 1/0, true/false, or yes/no.
palatable_human
Same.
nursery_stock_product
Same.
berry_nut_seed_product
Same.
lumber_product
Same.

Characteristics — colors (exact, case-insensitive)

flower_color
e.g. Yellow
foliage_color
e.g. Green
fruit_color
e.g. Red

Characteristics — numeric ranges

min_height_ft / max_height_ft
Mature height in feet.
min_ph / max_ph
Soil pH tolerance.
min_precipitation_in / max_precipitation_in
Inches of annual precipitation.
min_frost_free_days
Minimum required frost-free days.
min_temperature_f / max_temperature_f
Minimum tolerable temperature in °F.
min_root_depth_in
Minimum root depth in inches.

Examples

# Drought-tolerant native trees in Gallatin County, MT
GET /v1/counties/30031/plants?growth_habit=Tree&nativity=Native&drought_tolerance=High

# Mature shrubs 3–10 ft, full-sun-tolerant, found in Rhode Island
GET /v1/states/RI/plants?growth_habit=Shrub&shade_tolerance=Intolerant&min_height_ft=3&max_height_ft=10

# Pinaceae statewide in Montana
GET /v1/states/MT/plants?family=Pinaceae

# Trees or shrubs with high or medium drought tolerance
GET /v1/states/AL/plants?growth_habit=Tree,Shrub&drought_tolerance=High,Medium

§EErrors

Errors are structured.

Every error returns a JSON body of { "error": { "code": "<machine-readable>", "message": "<human-readable>" } }. The HTTP status reflects the failure category and the code string is stable so clients can branch on it.

400
bad_request — Malformed query (e.g. invalid limit).
404
not_found — Unknown plant symbol, county FIPS, state code, or route.
405
method_not_allowed — Only GET and HEAD are supported.
500
internal_error — Server-side fault. Rare; please include the URL when reporting.

§CCaching

Cache aggressively.

The dataset only changes when we re-import a SQLite chunk (rarely; never silently). Every response includes a Cache-Control header tuned to that reality. s-maxage applies at Cloudflare's edge; max-age is the browser-side hint.

/healthz
no-store — always fresh.
/v1/plants/{symbol}
max-age=3600, s-maxage=86400
/v1/plants/random
max-age=0, s-maxage=60 — rotates ~every minute.
/v1/counties[/...]
max-age=600–3600, s-maxage=86400
/v1/states[/...]
max-age=600, s-maxage=86400
/v1/search
max-age=60, s-maxage=300

ETag + 304 Not Modified

Every cacheable response carries a weak ETag header derived from the body content. Send it back as If-None-Match on a follow-up request and the API will respond with 304 Not Modified and no body if nothing changed. Browsers handle this automatically; library clients should opt in by storing the ETag and including it on subsequent fetches.

# Conditional GET — second request is empty + 304 if unchanged
ETAG=$(curl -sI 'https://api.rootfast.org/v1/plants/PIPO' \
        | awk '/^etag:/ {print $2}' | tr -d '\r')

curl -sI -H "If-None-Match: $ETAG" \
     'https://api.rootfast.org/v1/plants/PIPO'
# HTTP/2 304
# etag: W/"abcdef1234567890"
# cache-control: public, max-age=3600, s-maxage=86400

§PPlanned

On the roadmap.

Items below are intentionally not implemented yet. They will land as the API matures and as real usage tells us they're needed. If one of these is blocking you, send a note via contact.

OpenAPI 3.0 spec

A machine-readable description of every endpoint, parameter, response shape, and error code — served at /v1/openapi.json. Lets you generate typed clients in any language with openapi-generator, drop the spec into Postman / Insomnia / Bruno for "try it" UIs, validate responses in CI, or feed it to LLM tool-calling frameworks so AI agents can use the API directly. Will land alongside an optional /v1/docs route serving an interactive Swagger UI / Scalar page.

Deferred until the endpoint surface stabilizes — moving endpoints around now would just churn the spec.

API keys & rate limits

The API is open and uncapped today. If a real abuse problem appears, we'll add Cloudflare-level per-IP rate limits with an optional key-issuance flow for higher quotas. Keys, if they land, will be free for non-commercial use; commercial use is already allowed under CC0 with no key needed.

No timeline — added only if abuse forces it.

§LLicense & attribution

Use it freely. Cite the sources.

The Rootfast dataset is derived from public-domain federal data. The original source records are in the public domain. The merged dataset and this API are published as a public good. No API key is required and there is no commercial gating planned.

  • · USDA PLANTS — public domain
  • · GBIF Backbone Taxonomy — CC0
  • · Rootfast code — MIT
  • · Rootfast data — CC0

If you build something with this — a tool, a garden plan, a research paper — we would love to hear about it.