Crops API
Reference for the public, read-only REST API powering crops.every.farm: conventions, every endpoint under /api/v1 including farm-conditions matching at /recommend, the snowflake model, example integrations, and roadmap.
Public REST API powering crops.every.farm. All endpoints return JSON, are unauthenticated, read-only, and served by Nuxt server routes on Vercel.
Base URL:
https://crops.every.farm/api/v1
Every endpoint wraps its payload in a { "data": ... } envelope. List endpoints add a meta (or pagination) sibling for paging.
#Conventions
- Versioning — the
/v1prefix is permanent. Breaking changes will ship under a new major version. - Region — most endpoints accept
?region=.lake_erieandfinger_lakesare populated;/metalistsavailable_regionsfrom theregionstable. For/api/v1/recommend, region is an optional preset — the endpoint is designed to take a farm's own conditions instead. - Filtering by list — comma-separated values:
?category=fruit,nut. - Browsers — the API sends no CORS headers, so call it from a server rather than from a page on another origin.
- Errors — standard HTTP status codes with
{ "statusCode", "statusMessage" }.400= bad input,404= unknown slug,500= upstream/database failure. - Caching —
/meta(24 h),/regions,/data-sourcesand/crops/:slugwith its sub-resources (1 h) are cached at the Vercel edge per full URL, so query parameters such as?region=and?detail=are honoured and each combination is cached separately./crops,/crops/compare,/buyers,/audit-log,/recommend,/systems/evaluate,/interactions,/interactions/guildsand/regions/presetsare served from origin on every request. See Caching & Performance for the full matrix.
#Endpoints at a glance
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/meta |
Self-describing index — enums, axes, available endpoints |
| GET, POST | /api/v1/recommend |
Rank crops against a farm's conditions — per-criterion verdicts with evidence status, and an FAO land-evaluation class per crop |
| GET, POST | /api/v1/systems/evaluate |
Evaluate a crop system (guild, intercrop, alley, understory, rotation) on a farm: each crop's FAO class, the system class, interactions by land quality, measured land equivalent ratio |
| GET | /api/v1/interactions |
Crop-to-crop interactions (companions, antagonists, rotation effects), every row cited |
| GET | /api/v1/interactions/guilds |
Guilds: crops that go together around a chosen crop |
| GET | /api/v1/regions |
Active regions for the LocationPicker |
| GET | /api/v1/regions/presets |
Climate presets that /recommend accepts as region, including ones not in the picker |
| GET | /api/v1/crops |
Paginated, filterable crop list |
| GET | /api/v1/crops/:slug |
Full crop detail (economics, soil, risks, snowflake, prices) |
| GET | /api/v1/crops/compare?slugs=… |
Side-by-side compare (2–6 crops) |
| GET | /api/v1/crops/:slug/snowflake |
Just the 5-axis suitability score |
| GET | /api/v1/crops/:slug/fit |
One crop's fit across the region presets |
| GET | /api/v1/crops/:slug/cultivars |
Named cultivars of a crop, with sources |
| GET | /api/v1/crops/:slug/sources |
Per-crop audit trail and source attribution |
| GET | /api/v1/data-sources |
Registry of every upstream data source |
| GET | /api/v1/buyers |
Filterable list of every active buyer |
| GET | /api/v1/audit-log |
Workspace-wide change log with filters |
#GET /api/v1/meta
Returns a self-describing manifest of the API: every accepted enum value, the snowflake schema, and the live endpoint catalog. Call this first when integrating — it's the source of truth for valid filter values.
{
"data": {
"default_region": "lake_erie",
"available_regions": ["finger_lakes", "lake_erie"],
"categories": ["fruit", "vegetable", "grain", "nut", "herb", "fiber", "other"],
"lifecycles": ["annual", "perennial", "biennial"],
"price_trends": ["increasing", "stable", "declining"],
"sort_options": ["name_asc", "net_return_desc", "gross_revenue_desc", "total_score_desc", "years_to_production_asc"],
"snowflake": { "axes": ["market_fit", "climate_fit", "infrastructure_fit", "financial_return", "risk_profile"], "max_per_axis": 6, "max_total": 30 },
"list_filters": { "search": "string (ilike match on common_name)", "category": "csv of category enums", "zone": "integer USDA hardiness zone", "min_snowflake_total": "integer 0-30", "min_axis_<axis>": "integer 0-6 per axis", "fields": "csv; sparse response projection", "include": "csv of optional includes: price_history, last_audited_at", "...": "(see live /meta response for full catalog)" },
"list_response_fields": ["id", "slug", "common_name", "snowflake", "updated_at", "last_audited_at", "price_history", "…"],
"endpoints": { "list": "GET /api/v1/crops", "detail": "GET /api/v1/crops/:slug", "compare": "GET /api/v1/crops/compare?slugs=slug1,slug2", "snowflake": "GET /api/v1/crops/:slug/snowflake", "sources": "GET /api/v1/crops/:slug/sources", "audit_log": "GET /api/v1/audit-log", "data_sources": "GET /api/v1/data-sources", "regions": "GET /api/v1/regions", "buyers": "GET /api/v1/buyers", "meta": "GET /api/v1/meta" }
}
}list_filters and list_response_fields are machine-readable so downstream tooling (precision-polyculture pipelines, screener UIs, AI agents) can discover the full filter and projection surface of /api/v1/crops without reading the handler source. Hit the endpoint directly for the live catalog.
#GET|POST /api/v1/recommend
Rank the catalog against a farm's conditions. This is the endpoint built for agents: the farm's context (fields, soils, topography, infrastructure) lives with the caller; send it as parameters and get back crops worth trialling, each with a per-criterion breakdown and the evidence status of every crop attribute read.
POST accepts the same fields as a JSON body, which is easier for arrays.
#Conditions
All optional — every condition you supply becomes one criterion per crop; supply more to get a finer ranking. Two criteria are always evaluated whatever you send: risk_load (the share of high-severity risks on record) and buyers (buyers on record). They count toward the score but never cap the tier.
| Group | Parameter | Meaning |
|---|---|---|
| preset | region |
Fills zone, gdd, frost_free_days, annual_precip_in from a named region: lake_erie (lake plain: zones 6a–7a, 2,923 GDD, 185 frost-free days, 41.1 in), chautauqua_uplands (Allegheny Plateau around Jamestown: 6a, 2,364 GDD, 158 days, 45.9 in), finger_lakes (Seneca, Cayuga, Keuka and Canandaigua lake slopes: 6a–6b, 2,418 GDD, 172 days, 30.2 in). Each is the most conservative of the region's NOAA stations. Values are NOAA 1991–2020 normals and the 2023 USDA hardiness map; /api/v1/meta lists them under recommend.region_presets. Explicit values override, so "Lake Erie, but a hillside with 150 frost-free days" is one call. |
| climate | zone |
USDA hardiness zone, 6 or 6a. Hard gate for crops that overwinter (perennials and fall-sown grains); n/a for spring- and summer-sown annuals, whose range is set by season length and heat instead. |
gdd |
Growing degree days, base 50 °F, seasonal accumulation | |
frost_free_days |
Length of the frost-free season | |
chill_hours |
Winter chilling hours (32–45 °F). Perennials only; n/a for annuals |
|
annual_precip_in |
Mean annual precipitation, inches | |
| soil / site | soil_texture |
Comma list of texture classes; the best one on the farm counts. Hard gate. |
drainage |
One NRCS drainage class. Hard gate. | |
soil_ph |
Soil pH. Stored crop ranges are extension lime targets, so a reading within 1.0 of the range is marginal (correctable with lime or sulfur) and only a reading more than 1.0 outside it is a gate |
|
air_drainage |
good · moderate · poor — how well cold air drains off the field. Scores spring-frost risk at bloom for perennials; a frost pocket under a frost-sensitive crop is unfit. frost_pocket=true is shorthand for poor |
|
sun_exposure |
full_sun · partial_shade · shade — for alley and understory positions in agroforestry designs |
|
slope_pct |
Scored against a crop's workable slope limit where recorded | |
aspect |
Accepted but not scored (reported in unsupported_conditions); use air_drainage for frost risk |
|
| infrastructure | irrigation |
true relaxes the rainfall criterion |
cold_storage |
true/false — refrigerated storage on farm |
|
labor_hours_per_acre |
Labor the farm can commit per acre per year | |
| economics | max_years_to_production |
Planning horizon |
max_establishment_cost_per_acre |
Capital ceiling, $/acre | |
min_net_return_per_acre |
Target net return, $/acre | |
| strategy | categories, exclude_categories, lifecycle |
Narrow the candidate set |
exclude_crops (alias already_growing) |
Slugs the farm already grows — diversification excludes them | |
| output | include_unfit |
true returns crops that failed a hard gate, with the reason (default false) |
include_criteria |
false omits the per-criterion breakdown (default true) |
|
limit |
1–100, default 25 |
Vocabulary for soil_texture and drainage is published under recommend.vocab in /api/v1/meta. Unknown values are reported in warnings and ignored rather than failing the request.
#Response
{
"data": {
"conditions": { "zone": 6, "frost_free_days": 180, "soil_texture": ["silt_loam"], "drainage": "moderately_well_drained", "soil_ph": 6.4 },
"unsupported_conditions": [],
"excluded_unfit": 23,
"results": [
{
"slug": "cabbage",
"common_name": "Cabbage",
"category": "vegetable",
"lifecycle": "biennial",
"score": 100,
"fit": "strong",
"gates_failed": [],
"coverage": { "evaluated": 11, "unknown": 0, "not_applicable": 0, "cited_share": 0.45, "contested": [] },
"gaps": [],
"criteria": [
{
"key": "drainage", "label": "Drainage", "verdict": "fit", "weight": 3,
"farm": "moderately_well_drained", "crop": "ideal",
"note": "moderately well drained ground is rated ideal for this crop.",
"cited": true, "reads": ["crop_drainage_preferences"]
}
],
"fao": {
"profile": "fao1983-max-limitation", "combination": "maximum_limitation",
"class": "S2", "subclass": ["oxygen"], "unrated": [], "management_level": "intermediate",
"ratings": [
{ "land_quality": "oxygen", "rating": "s2", "criteria": ["drainage"], "basis": "catalog_rating" },
{ "land_quality": "nutrient_availability", "rating": "s1", "criteria": ["soil_ph"], "basis": "graded_requirement", "requirement_id": "…", "note": "soil_ph against optimal/absolute limits" }
]
},
"evidence_url": "/api/v1/crops/cabbage/sources",
"detail_url": "/api/v1/crops/cabbage"
}
]
},
"meta": { "crops_considered": 81, "generated_at": "…", "engine_version": "2026-10-23.1" }
}#How to read it
verdictis one offit·marginal·unfit·unknown·n/a.unknownis never scored as unfit — it means the catalog has not recorded that attribute for that crop. It is counted incoverage.unknownand the attribute is listed ingaps, so an agent can tell a poor fit from a poorly-researched crop.fittiers:strong(score ≥ 75, ≥ 60 % of asked criteria evaluable, and no criterionunfit),possible(≥ 50),poor(below, or a hard gate failed),unknown(nothing evaluable). Coverage and known problems cap the tier: a crop scoring high on two evaluated criteria with five unknown ispossible, notstrong, and so is a peach orchard on a frost pocket.citedon each criterion, andcited_shareper crop, say how much of the recommendation rests on a supporting source citation (primary, corroborating or derived). Followdetail_urlfor the quoted citations;evidence_urlis the crop's change log.contested: trueon a criterion, and the keys incoverage.contested, mean a source on record contradicts the stored value. A contradicting citation never counts towardcited. Tell the grower the sources disagree and show both before relying on that criterion.faois the FAO land-evaluation class (FAO 1976 Framework; Soils Bulletin 52, 1983):S1highly,S2moderately,S3marginally suitable,Nnot suitable, withsubclassnaming the limiting land qualities. Each physical criterion is rated on the land quality it assesses (s1–s3, n). pH and annual rainfall are rated against the crop's graded requirements where they exist (mostly FAO ECOCROP optimal and absolute limits,basis: "graded_requirement"), the rest from the criterion's verdict (ideals1,suitables2,marginals3,unfitn; pH inside the lime band is s2, correctable by liming). The default combination is maximum limitation: the class is the worst rating, and any n is N. Sendprofile=fao1983-multiplicativefor the multiplicative rule. Economics, labour, storage and years to production are not land qualities, so they stay inscore. A crop can score 100 and still be S2: the score lets strengths offset weaknesses, the FAO class does not.- Score is agronomic fit unless you send economics. With climate and soil alone, broad-tolerance commodity crops (buckwheat, oats, dry bean) score 100 — correctly, they can grow there. To propose diversification, also send
min_net_return_per_acre,max_establishment_cost_per_acre,labor_hours_per_acre,categories, andexclude_crops.
#Errors
400— no farm conditions supplied, or a non-objectPOSTbody.
#GET /api/v1/interactions
Cited pairwise crop interactions, the building block for companion, guild, alley and rotation design. Every interaction carries the source quote that documents it; there are no uncited rows.
| Parameter | Meaning |
|---|---|
crops |
Comma list of crop slugs (required unless crop or all is given) |
all |
true returns every interaction in the catalog, for graph views |
mode |
within (default): only interactions where both crops are in crops ("what happens if I put these together?"). any: every interaction touching any listed crop ("what goes well or badly with these?") |
crop |
One slug; shorthand for crops=<slug>&mode=any |
type |
Comma list of interaction types: allelopathic, shared_pest_host, alley_overstory, understory, nitrogen_source, rotation_benefit, rotation_conflict, pest_suppression, pollination_partner, nurse, competition |
effect |
Comma list of positive, negative, neutral |
Each interaction returns a, b (slugs and names), interaction, effect, magnitude, evidence_strength, mechanism, spatial_pattern, applies_when, and sources (publisher, URL, verbatim quote). In within mode the response also carries pairs — total, documented and the list of undocumented pairs — because an undocumented pair is a research gap, not a clean bill. A pair that is documented for several reasons (two shared pests, say) keeps every mechanism in mechanism, separated by "Also:". Direction matters: (a, b, allelopathic) means a affects b. Symmetric types (shared_pest_host, competition) are stored once. neutral records a documented tolerance, for example a crop observed to grow normally within black walnut's root zone. Negative interactions sort first.
GET /api/v1/interactions?crops=black-walnut,tomato,sweet-corn,pumpkin#GET /api/v1/interactions/guilds
Mutual-benefit clusters ("guilds"): maximal sets of catalog crops in which every pair has a documented benefit of growing them together — nurse, nitrogen, alley, understory, pest suppression or pollination. Rotation benefits are sequences, so they are returned separately as chains.
| Parameter | Meaning |
|---|---|
mode |
strict (default): every pair needs a documented positive interaction. loose: a documented tolerance (for example a crop unaffected by walnut juglone) also counts as compatible; each guild still needs at least one positive pair |
exclude_negative |
true (default) drops guilds with any documented negative between members (shared pest, rotation conflict, competition); false returns them with the negatives listed |
crop |
Only results that include this crop, plus partners: every crop it has a documented grow-together relation with |
Returns guilds (members, the compatible pairs with their interaction kinds and source counts, and any negatives), rotation_chains (sequences where every step is a documented rotation benefit, with any later-conflict flagged) and trial_candidates: pairs with no documented relation whose positive documentation would complete a guild, because both already have documented benefits with the same partners. Those are the intercropping trials the literature is missing.
GET /api/v1/interactions/guilds
GET /api/v1/interactions/guilds?mode=loose&exclude_negative=false
GET /api/v1/interactions/guilds?crop=pawpaw#GET|POST /api/v1/systems/evaluate
Evaluates a crop system, meaning several crops grown together or in sequence, on a farm, following FAO Soils Bulletin 52 §6.3.5 and §8.4.3.
| Parameter | Meaning |
|---|---|
crops |
2–8 crop slugs (required) |
arrangement |
optional: row_intercrop, strip_intercrop, mixed_intercrop, relay, alley, understory, multistrata, rotation, double |
| farm conditions | exactly as /api/v1/recommend (region, zone, gdd, frost_free_days, annual_precip_in, soil_texture, drainage, soil_ph, slope_pct, air_drainage, sun_exposure, …) |
profile |
evaluation profile, as for /recommend |
share.<slug> |
optional land share of each crop (normalised to 1; default equal shares) |
gross.<slug>, cost.<slug> |
optional: the farm's own gross revenue and cost per acre for that crop grown alone (default: the catalog's cited Lake Erie figures) |
wage, discount_rate, horizon_years |
optional economics overrides (defaults $16.00/h, 0.05, the longest perennial lifespan or 20 years, between 5 and 40) |
The response holds:
components: each crop's score, tier and FAO class on this farm.system:class_first_approximation: the least suitable crop's class, as SB52 §8.4.3 prescribes.class: that class after interaction adjustments.limiting: which crops and land qualities set the class.
interactions: every documented interaction among the crops.- Each one carries the land quality it changes (
land_quality), whose land it changes (affected), and the direction (improves,worsensorno_change). relationsays whether the crops grow together or in sequence.undocumented_pairslists the pairs with no evidence either way.- Only an adjustment the evidence supports numerically is applied: a juglone-type allelopathy makes the sensitive crop not suitable. Other interactions state a direction but not a severity, so they are reported without changing a class.
- Each one carries the land quality it changes (
ler: the measured land equivalent ratio.measured: the median of published trials that grew exactly these crops, with each trial listed.subset: only trials of some of the crops exist. They are listed but not extrapolated.none: no trial exists.used_in_economicsis then 1.0, meaning no land advantage is assumed.
economics_defaults: a 5% discount rate, and the New York minimum wage outside New York City, Long Island and Westchester ($16.00 an hour, per the NY Department of Labor).economics: per-acre economics of the system against the best of its crops grown alone.per_acre.revenue_mix= Σ partial LER × each crop's sole gross revenue;cost_mix= Σ land share × each crop's sole cost;net_mix,best_soleandadvantage_vs_best_solecompare the two.monetary_advantage_index(Willey 1979) is returned when the LER is measured.- Each crop's partial LER is the trials' median partial LER, else the measured total LER split by land share, else its land share (LER 1.0, no advantage assumed).
components[].partial_ler_basissays which. - Cost is the published budget's own gross minus net, else its operating cost, else its input cost. Labour is shown in hours and at the wage but is not subtracted, because the budgets differ on whether they already include it.
npv(when a crop is perennial): establishment cost × share in year 0, no revenue from a perennial until its years to full production, discounted over the horizon, against the best sole crop's NPV.completeis false andmissingnames the crops without cited gross and cost figures; sendgross.<slug>andcost.<slug>to fill them.assumptionslists every assumption in words.
land_use_type: the matching catalog or literature land use type, when one exists.
GET /api/v1/systems/evaluate?crops=field-corn-dent,dry-bean,pumpkin®ion=lake_erie&soil_texture=silt_loam&drainage=moderately_well_drained&soil_ph=6.4
GET /api/v1/systems/evaluate?crops=black-walnut,tomato,pawpaw®ion=lake_erie&arrangement=alleyThe first returns S2 (limited by drainage), with a measured LER from Iowa State's Three Sisters trial. In the second, tomato becomes N because black walnut's juglone suppresses it, while pawpaw, which tolerates juglone, stays S1.
#GET /api/v1/crops/:slug/fit
One crop evaluated by the matching engine against every region preset, so a caller can answer "where in the study area does this crop fit, and what limits it?" without ranking the whole catalog. Any other /recommend farm parameter (soil_texture, drainage, soil_ph, air_drainage…) may be passed and is merged into every preset. Each entry carries the preset's conditions, fit, score, gates_failed, coverage and the per-criterion verdicts.
GET /api/v1/crops/peach/fit
GET /api/v1/crops/peach/fit?soil_texture=clay_loam&drainage=somewhat_poorly_drained#GET /api/v1/crops/:slug/cultivars
Named cultivars of a crop. A non-null attribute (hardiness zone, GDD, chill, days to maturity, disease resistances) overrides the species value for that cultivar; null means "same as the species". sources maps each set attribute to the quote that states it, and sources._row documents the cultivar itself.
GET /api/v1/crops/table-grape/cultivars#GET /api/v1/regions
Lists every active region the platform supports. Drives the LocationPicker, useUserLocation.knownRegions, and any external consumer that needs to know what regions exist. The frontend falls back to a hardcoded list if this endpoint fails. Cached at the edge for 1 hour because regions change approximately never.
#Query parameters
None.
#Response
{
"data": {
"regions": [
{
"regionKey": "lake_erie",
"label": "Lake Erie Concord Grape Belt",
"description": "The grape-growing region along Lake Erie spanning Chautauqua County NY and Erie County PA.",
"country": "United States",
"adminRegion": "NY / PA",
"hardinessZone": "6a",
"lat": 42.35,
"lng": -79.32
}
],
"total": 2
}
}#GET /api/v1/regions/presets
The climate presets that /api/v1/recommend accepts as region. Includes presets that are not in the site's location picker, such as chautauqua_uplands. Served from origin.
#Query parameters
None.
#Response
{
"data": [
{
"name": "lake_erie",
"display_name": "Lake Erie Concord Grape Belt",
"description": "The grape-growing region along Lake Erie spanning Chautauqua County NY and Erie County PA. …",
"states": ["NY", "PA"],
"zones": ["6a", "6b", "7a"],
"gdd": 2923,
"frost_free_days": 185,
"annual_precip_in": 41.09,
"ui_region": true,
"latitude": 42.35,
"longitude": -79.32
}
]
}ui_region is true for presets that also appear in the site's location picker.
#GET /api/v1/crops
The screener. Paginated list of every crop in the database with snowflake totals and headline economics.
#Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
search |
string | — | ilike match against common_name |
category |
csv | — | Any of categories from /meta |
lifecycle |
csv | — | Any of lifecycles from /meta |
revenue_min / revenue_max |
number | — | Bounds on gross_revenue_per_acre (USD) |
net_return_min / net_return_max |
number | — | Bounds on net_return_per_acre (USD) |
price_trend |
csv | — | Any of price_trends |
insurance |
bool | — | true = only crops with USDA crop insurance |
years_to_production_max |
number | — | Useful to exclude long-establishment perennials |
region |
string | lake_erie |
Snowflake region (does not filter the crop list itself) |
sort |
enum | total_score_desc |
One of sort_options from /meta. Unrecognized values fall back to the default rather than erroring. |
page |
int | 1 |
1-indexed |
limit |
int | 20 |
Capped at 100 |
#Extended filters
In addition to the base filters above, the list endpoint accepts the following for finer-grained querying. All are additive and AND-combined:
| Param | Type | Example | Notes |
|---|---|---|---|
zone |
int | zone=6 |
USDA hardiness zone. Returns crops where hardiness_zone_min ≤ zone ≤ hardiness_zone_max. |
min_snowflake_total |
int 0–30 | min_snowflake_total=22 |
Lower bound on the regional snowflake total. |
min_axis_<axis> |
int 0–6 | min_axis_market_fit=5&min_axis_climate_fit=5 |
Lower bound per axis. One param per axis; combined AND. Valid axes: market_fit, climate_fit, infrastructure_fit, financial_return, risk_profile. |
max_years_to_production |
int | max_years_to_production=3 |
Alias for years_to_production_max — pick whichever name reads better in your caller. |
#Response shaping with fields= and include=
The default response returns the object shape shown below — one object per crop with a standard set of catalog columns plus a snowflake rollup. Two parameters let you trim or extend that shape:
fields=slug,net_return_per_acre,soil_ph_min,soil_ph_max— sparse projection. Only the listed columns are returned on each crop (plusidandslug, which are always present). Unknown field names are silently dropped. The full allowlist is returned by/api/v1/metaaslist_response_fields, and includes catalog columns that aren't in the default shape —soil_ph_min/soil_ph_max,water_req_inches,frost_free_days_min,gdd_min,typical_yield_per_acre,yield_unit,labor_hours_per_acre,establishment_cost_per_acre,economics_year,subcategory,description,hardiness_zone_min/hardiness_zone_max,updated_at.include=price_history,last_audited_at— optional includes.price_historyattaches the per-year price array inline under each crop;last_audited_atattaches a per-cropmax(data_audit_log.created_at)so pipelines can decide whether to re-pull without scanning the full audit log.
Example combining both: ?fields=slug,snowflake,updated_at&include=last_audited_at returns a minimal object per crop containing only the snowflake, the crop's updated_at, and the latest audit timestamp.
#Response
{
"data": [
{
"id": "…uuid…",
"common_name": "Black Walnut",
"slug": "black-walnut",
"scientific_name": "Juglans nigra",
"category": "nut",
"lifecycle": "perennial",
"image_url": "https://…",
"gross_revenue_per_acre": 1850,
"net_return_per_acre": 920,
"total_input_cost_per_acre": 930,
"price_trend_3yr": "increasing",
"crop_insurance_available": false,
"years_to_full_production": 12,
"data_completeness": 78,
"snowflake": {
"total_score": 22,
"axes": { "market_fit": 5, "climate_fit": 5, "infrastructure_fit": 4, "financial_return": 4, "risk_profile": 4 }
}
}
],
"meta": { "page": 1, "limit": 20, "total": 81, "total_pages": 5 }
}#GET /api/v1/crops/:slug
Full dossier for a single crop. The public crop detail page reads the same tables.
#Path parameter
slug— kebab-case crop slug (e.g.black-walnut,elderberry,hops).
#Query parameters
region— snowflake region (defaultlake_erie).
#Response shape
data
├── crop ← row from `crops` table (everything except joined relations)
├── economics ← single `crop_economics` row, or null
├── snowflake
│ ├── total_score ← 0–30
│ ├── max_score ← always 30
│ ├── region
│ └── axes[] ← ordered: market_fit, climate_fit, infrastructure_fit, financial_return, risk_profile
│ ├── axis
│ ├── value ← 0–6
│ ├── max_value ← always 6
│ └── checks[] ← the 6 underlying checks per axis with score + rationale
├── soil_preferences[]
├── drainage_preferences[]
├── market_channels[]
├── risks[]
├── equipment[]
├── storage[]
├── price_history[] ← per-year wholesale price points, sorted ascending
├── cultivars[] ← named cultivars
└── interactions[] ← this crop's interactions with other cropscrop, economics and each child row also carry a sources object keyed by column (_row for the whole row): publisher, url, quote, role, confidence, note and date accessed.
Returns 404 if the slug doesn't exist.
#GET /api/v1/crops/compare
Side-by-side comparison for 2–6 crops. Preserves the order of slugs you pass in (so the UI's column order is stable).
#Query parameters
slugs— required, comma-separated. Min 2, max 6. Unknown slugs are silently dropped.region— snowflake region (defaultlake_erie).
#Response
{
"data": {
"crops": [
{
"slug": "sweet-corn",
"common_name": "Sweet Corn",
"scientific_name": "Zea mays",
"category": "vegetable",
"lifecycle": "annual",
"image_url": "…",
"hardiness_zone_min": 3,
"hardiness_zone_max": 11,
"years_to_full_production": 1,
"labor_hours_per_acre": 22,
"economics": {
"gross_revenue_per_acre": 2400,
"net_return_per_acre": 780,
"establishment_cost_per_acre": 0,
"annual_operating_cost_per_acre": 1620,
"price_trend_3yr": "stable",
"crop_insurance_available": true
},
"snowflake": { "total_score": 24, "axes": { "market_fit": 5, "climate_fit": 5, "infrastructure_fit": 5, "financial_return": 4, "risk_profile": 5 } }
}
]
}
}#Errors
400— fewer than 2 slugs or more than 6.404— none of the requested slugs matched.
#GET /api/v1/crops/:slug/snowflake
Lightweight version of the detail endpoint that returns only the 5-axis suitability score. Use this when you want to render snowflakes in a list without paying for the full crop payload.
#Query parameters
region— snowflake region (defaultlake_erie).detail—trueto include the per-check breakdown under each axis. Defaults tofalse(justaxis,value,max_value).
#Response (without detail)
{
"data": {
"crop_id": "…uuid…",
"slug": "hops",
"common_name": "Hops",
"region": "lake_erie",
"total_score": 21,
"max_score": 30,
"axes": [
{ "axis": "market_fit", "value": 4, "max_value": 6 },
{ "axis": "climate_fit", "value": 5, "max_value": 6 },
{ "axis": "infrastructure_fit", "value": 3, "max_value": 6 },
{ "axis": "financial_return", "value": 5, "max_value": 6 },
{ "axis": "risk_profile", "value": 4, "max_value": 6 }
]
}
}#GET /api/v1/crops/:slug/sources
Full data-provenance trail for one crop — every field that has ever been written, with the source name, source URL, source date, and last-updated timestamp. This is what powers the "Sources" section on each crop page.
#Query parameters
table— filter to a single underlying table (e.g.crop_risks,crop_economics).limit— max audit entries returned (default100, max500).
#Response
data
├── crop ← { id, common_name, slug }
├── total_changes ← integer
├── source_summary[] ← grouped by table
│ ├── table_name ← raw, e.g. `crop_economics`
│ ├── display_name ← human label, e.g. "Economics & Pricing"
│ └── sources[] ← { source_name, source_url, source_date, last_updated, change_count }
└── audit_log[] ← raw rows from `data_audit_log`, newest first#GET /api/v1/buyers
Filterable list of every active buyer in the catalog. Powers the public /buyers browse page and is available as a REST endpoint for external consumers.
#Query parameters
| Param | Type | Example | Notes |
|---|---|---|---|
search |
string | search=walnut |
ilike match against the buyer name |
type |
csv | type=processor,distributor |
Filter by buyer type. Current values: cooperative, distributor, processor, retailer, wholesaler |
state |
csv | state=NY,PA |
Filter by US state code |
accepts_contracts |
bool | accepts_contracts=true |
Only buyers that offer contracts |
accepts_spot |
bool | accepts_spot=true |
Only buyers that offer spot purchases |
crop |
csv | crop=black-walnut,hops |
Buyers who purchase ANY of the listed crop slugs |
include |
csv | include=crops |
Inline the list of crops each buyer purchases, with price per unit when available |
limit |
int | limit=50 |
Cap (default 200, max 500) |
#Response
{
"data": [
{
"id": "…uuid…",
"slug": "hammons-products-company",
"name": "Hammons Products Company",
"buyer_type": "processor",
"description": "Family-owned buyer and processor of wild American black walnuts…",
"city": "Stockton",
"state": "MO",
"website": "https://black-walnuts.com",
"contact_email": null,
"contact_phone": "+1-888-429-6887",
"accepts_contracts": false,
"accepts_spot": true,
"has_location": false,
"is_research_only": true,
"source_name": "Hammons Products Company – About Us",
"source_url": "https://black-walnuts.com/about-us/",
"source_date": "2026-04-14",
"updated_at": "2026-04-14T22:06:17.160138+00:00",
"crops": [
{ "crop_id": "…", "slug": "black-walnut", "common_name": "Black Walnut", "price_per_unit": null, "price_unit": null }
]
}
],
"meta": { "total": 48 }
}- Only
is_active = truebuyers are ever returned. Deactivated rows are invisible to the public surface. has_locationis a derived boolean reflecting whether the buyer has been geocoded. Buyers without coordinates still appear in this list — the proximity-search RPCnearby_buyersis the one that filters them out.is_research_onlyflags buyers compiled from public web research (not directly contacted). Surface this in your UI if you're presenting the data to end users.
#GET /api/v1/data-sources
Registry of every upstream data source the platform pulls from — USDA NASS, university extensions, peer-reviewed journals, market reports, etc. Useful for citing data programmatically.
#Query parameters
| Param | Notes |
|---|---|
type |
academic, university_extension, government, industry, extension, industry_association, peer_reviewed |
region |
Source covers this region (e.g. lake_erie, national) |
coverage |
Source covers this data category (e.g. economics, climate) |
#Response
{
"data": {
"sources": [
{ "id": "…", "name": "USDA NASS Quick Stats", "source_type": "government", "url": "https://quickstats.nass.usda.gov", "regions": ["national", "lake_erie"], "coverage": ["economics", "yield"], "last_accessed": "2026-04-01" }
],
"total": 251
}
}#GET /api/v1/audit-log
Workspace-wide change feed. Every insert, update, and delete to a crop-related table is logged here with the source citation that triggered it. Useful for AI agents that need to verify data freshness or build dashboards.
#Query parameters
| Param | Notes |
|---|---|
crop |
partial (ilike) match on crop_name |
table |
exact table name (e.g. crop_risks) |
action |
insert, update, or delete |
task_run |
filter by a specific ingestion run ID |
region |
exact region match |
since |
ISO 8601 date — only entries on or after this timestamp |
limit |
default 50, max 500 |
offset |
for pagination |
#Response
{
"data": {
"entries": [
{
"id": "…",
"created_at": "2026-04-15T18:42:00Z",
"crop_name": "Black Walnut",
"table_name": "crop_economics",
"record_id": "…uuid…",
"action": "update",
"field_name": "gross_revenue_per_acre",
"old_value": "1700",
"new_value": "1850",
"change_summary": "Annual economics refresh — PSU Extension 2026 budget",
"source_type": "automated_daily",
"source_name": "PSU Extension",
"source_url": "https://extension.psu.edu/…",
"source_date": "2026-03-12",
"task_run_id": "…",
"task_day_type": "economics_refresh",
"region": "lake_erie"
}
],
"pagination": { "total": 8021, "limit": 50, "offset": 0, "has_more": true }
}
}#Snowflake scoring model
Every crop has a regional snowflake score out of 30, broken into 5 axes of 6 points each. Each axis is the sum of 6 binary/scaled checks (e.g. "price trend is positive over 3 years", "crop insurance available", "established processing infrastructure within region").
| Axis | What it captures |
|---|---|
market_fit |
Demand strength, buyer concentration, pricing power |
climate_fit |
Hardiness-zone match, water needs, heat/cold tolerance |
infrastructure_fit |
Equipment availability, storage, processing within region |
financial_return |
Net return per acre, payback period, capital intensity |
risk_profile |
Pest/disease pressure, weather risk, regulatory exposure |
Use /api/v1/crops/:slug/snowflake?detail=true to see the individual checks and the rationale string we wrote when assigning each score.
Note: the snowflake is a regional score — one value per crop per region. For a specific farm, use
/api/v1/recommend, which evaluates the same kind of criteria against the farm's own conditions and reports per-criterion evidence. The snowflake remains for the crops.every.farm UI and is no longer being expanded to new regions; the UI will move to the matching engine with region presets.
#Example integrations
#Top 5 nuts by net return
GET /api/v1/crops?category=nut&sort=net_return_desc&limit=5#Compare three perennials
GET /api/v1/crops/compare?slugs=elderberry,hops,black-walnut#Quote a snowflake on an external site
GET /api/v1/crops/elderberry/snowflake#Show users where a number came from
GET /api/v1/crops/black-walnut/sources?table=crop_economics#High-confidence fruit candidates (precision-polyculture)
GET /api/v1/crops?category=fruit&min_snowflake_total=22&min_axis_market_fit=5#All crops compatible with USDA zone 6
GET /api/v1/crops?zone=6&limit=100#Short-payback perennials
GET /api/v1/crops?lifecycle=perennial&max_years_to_production=3#Sparse-field pipeline pull
Only the columns needed for per-pixel scoring, with cache-invalidation metadata:
GET /api/v1/crops?fields=slug,net_return_per_acre,soil_ph_min,soil_ph_max,hardiness_zone_min,hardiness_zone_max,snowflake&include=last_audited_at&limit=100#Find buyers for a specific crop
GET /api/v1/buyers?crop=black-walnut&include=crops#All processors that accept spot purchases
GET /api/v1/buyers?type=processor&accepts_spot=true#Recently shipped
Changes since the initial API doc. Listed newest first so callers can see at a glance whether they need to refresh their integration.
#2026-10
/api/v1/recommend(GET and POST). Farm-conditions matching: send zone, GDD, frost-free days, chill, rainfall, soil texture, drainage, pH, irrigation, cold storage, labor, capital and horizon; get ranked crops with a per-criterion verdict, a reason in grower language, and whether the crop attribute behind each verdict is cited.unknownis reported as unknown, never scored as unfit.region=works as a preset you can override per field. Described underrecommendin/api/v1/meta.- Second region:
finger_lakes. Colder and shorter-season than Lake Erie. First set on 1988 Federal Register AVA baselines; rebased on 2026-10-23 to NOAA 1991–2020 normals (2,418 GDD base 50 °F, 172 frost-free days, 30.2 in, zones 6a–6b).available_regionsin/metais now read from theregionstable. - Snowflake rescored and re-cited. 1,170 citations that pointed at crops.every.farm itself were replaced with citations to the primary sources of each score's inputs; three checks that scored on row counts (so thorough research lowered a score) now read severity, specialisation and viability columns.
- USDA GRIN anchors:
grin_taxon_idon crops (GRIN Taxonomy nomen number, cited) andgrin_accession/grin_accession_id/grin_taxon_idon cultivars (NPGS accession, cited). Returned by/crops/:slugand/crops/:slug/cultivars. Prototype covers apple, table and juice grape, and both cherries. - Per-acre system economics (2026-10-26):
/api/v1/systems/evaluatereturnseconomics: revenue, cost and net per acre of the mix against the best sole crop, the monetary advantage index, and an NPV for systems with perennials, each crop's figures cited from the catalog or sent by the farm (share.,gross.,cost.,wage,discount_rate,horizon_years). The public/interactionspage is now a full-screen map with guild, combination and trial panels, and evaluates any combination on a farm. - FAO land evaluation (2026-10-23):
/recommendresults carry anfaoclass (S1–N) with the limiting land qualities, and/api/v1/systems/evaluateevaluates guilds, intercrops, alleys and rotations. Behind them: 27 FAO land qualities, about 1,540 cited graded crop requirements, 158 land use types, and 212 published intercrop trials with their land equivalent ratios. - More interactions and the traits that let crops share land (2026-10-22): about 1,470 cited interactions, now including measured water and nitrogen sharing in intercrops, vineyard-floor and young-vineyard intercrops for the Lake Erie Grape Belt, nut-tree alley trials with yield by distance from the tree row, and the first grow-together partners for Brussels sprouts, turnip, table beet, quinoa, kale and cauliflower.
/crops/:slugnow returns rooting depth for 55 crops (was 8), mature canopy height for 71 (was 17) and wind tolerance for 37 (was 7), each cited (mostly FAO Irrigation and Drainage Paper 56). Each new interaction states the site conditions it was observed under. The guild finder has four five-crop guilds in strict mode. - Positive intercropping evidence: interactions grew to about 1,230 cited rows (field intercrops, living mulches and wind strips, orchard and vineyard alleys, trap crops, pollinator partners), and conflicting findings are recorded as
contradictingsources on the row they dispute. Mutual-benefit guilds of four crops now exist (the largest loose guild has five)./interactions?all=trueand/crops/:slugpage through the table, so every row and every source is returned; before 2026-10-14 the list stopped at 1,000 rows. - Guild finder (
/interactionspage andGET /api/v1/interactions/guilds): mutual-benefit crop clusters, a crop's best partners, rotation chains, and the undocumented pairs whose trials would complete a guild. - Interactions grew from 184 to about 925 cited rows (rotation conflicts and benefits, shared pests and diseases across vegetables, field crops, fruit and nuts, nitrogen credits, cover-crop allelopathy, alley and understory pairings, trap crops and intercrop competition).
/recommendnow scores aneighbourscriterion from them whenalready_growingis sent: an allelopathic suppressor among the farm's crops makes a candidate unfit, other documented concerns marginal, documented benefits fit; undocumented pairs are not counted against a crop. The public/interactionspage gained a combination checker (/interactions?crops=a,b,c). GET /api/v1/crops/:slug/fit: one crop against every region preset, shown on crop pages as "Fit by region".- Public UI for the matcher and the interactions:
/match(farm-conditions form, ranked results with per-criterion verdicts and a crop × criterion matrix; conditions in the URL) and/interactions(network of every cited interaction). Crop pages gained a climate-envelope and site block, a Cultivars section and a Grows With / Avoid Near section.GET /api/v1/regions/presetslists every recommend preset with its cited climate baselines;/api/v1/regionsnow carries them too underclimate. /api/v1/interactionsand/api/v1/crops/:slug/cultivars: 176 cited crop interactions (black walnut juglone sensitivity and tolerance; pests shared across grapes, berries and tree fruit such as spotted wing drosophila, Japanese beetle, plum curculio, clubroot and spotted lanternfly; rotation benefits and conflicts; alley and understory pairings) and 123 cited cultivars across apple, aronia, blueberry, chestnut, elderberry, hazelnut, honeyberry, pawpaw, raspberry, strawberry, sweet and tart cherry, table grape and juice grape.- New crop:
juice-grape(Concord / Niagara), the anchor crop of the Lake Erie Grape Belt, with economics from the Cornell Concord budget, soils, drainage, risks, equipment, market channels, storage and five cultivars, all cited. exclude_crops/already_growingfixed for multi-word slugs (they were never excluded), and each candidate now listswith_farm_crops: documented interactions with the crops the farm already grows.- Site criteria in
/recommend:air_drainage/frost_pocket(spring frost at bloom for perennials) andsun_exposure(alley and understory positions). A crop with anyunfitcriterion is now capped atpossible. - Contested evidence: a citation that contradicts a stored value no longer counts toward
cited; the criterion carriescontested: trueinstead. - Region presets cited to NOAA 1991–2020 normals and the 2023 USDA hardiness map.
lake_erieis now zones 6a–7a with 2,923 GDD and 185 frost-free days (previously the 2012 map's 5b–6b and an uncited 2,500 / 180). New presetchautauqua_uplandscovers the Allegheny Plateau south of the escarpment. - New schema for site and polyculture data:
crops.max_slope_pct,frost_sensitivity_bloom,prefers_air_drainage,shade_tolerance,canopy_height_ft,root_depth_in,wind_exposure_tolerance;cultivars;crop_interactions. All nullable;/recommendreportsunsupported_conditionsuntil they are researched. See Data Model & Schema.
#2026-04
- Pagination and filtering on
/api/v1/cropsand/api/v1/crops/compareactually honor query params. Previously, Vercel's path-level ISR cache was stripping the query string, so every variant of those URLs returned the same frozen payload. Those endpoints now serve from origin on every request. If your integration was working around this by fetching crops one slug at a time, you can drop that workaround. sort=total_score_descpaginates correctly. Previously the sort applied after pagination (so “top 5” was really the alphabetically-first 5 re-ordered among themselves). It now fetches the full filtered set, sorts globally, and slices the requested page.- Deterministic pagination order. All paginated queries carry a secondary ordering on the primary key, so rows tied on the user-selected sort column don't shift between pages or appear twice across requests.
- New filters on
/api/v1/crops:zone(USDA hardiness-zone match),min_snowflake_total,min_axis_<axis>(one per snowflake axis), andmax_years_to_production(alias foryears_to_production_max). fields=sparse projection. Request only the columns you need. The allowlist exposes catalog columns not in the default shape —soil_ph_min/soil_ph_max,water_req_inches,frost_free_days_min,gdd_min,typical_yield_per_acre,yield_unit,labor_hours_per_acre,establishment_cost_per_acre,economics_year,subcategory,description,hardiness_zone_min/hardiness_zone_max,updated_at.include=optional extensions.price_historyinlines the per-year price array;last_audited_atattaches a per-cropmax(data_audit_log.created_at)so downstream pipelines can cache-invalidate without scanning the full audit log./api/v1/metais self-describing. Newlist_filtersandlist_response_fieldskeys let tooling discover the full filter and projection surface without reading handler source./api/v1/regionsendpoint. Data-driven region catalog. Adding a row to theregionstable automatically surfaces the region in the picker without a code change.
#Roadmap
- Cultivar-level matching:
/recommendevaluatingcultivarsrows as overrides of the species envelope. - Companion / guild / alley recommendations from
crop_interactions. - The crops.every.farm UI moving from the regional snowflake to
/recommendwith region presets. - Additional regions as presets (Pacific Northwest and Southern Appalachia are next).
- Issued API keys for production integrations and per-key usage controls.
- Webhooks on audit-log entries for downstream consumers.
Updated
Something missing or out of date? Tell us — the docs are updated with every release.