Examples
Worked examples of how to consume /v1/point from a third- party app. mud2dust intentionally exposes primitives — every per-pixel signal we have plus two opinionated summary blocks (irrigation and traction). Pick the fields your app needs; ignore the rest.
/v1/point answers questions about a single point (?lat=&lon=) or averaged across a polygon (?geom= = URL-encoded GeoJSON Polygon / MultiPolygon). Apps with field boundaries, watersheds, utility service territories, or drone-flown areas pass them as polygons — same response shape, the values are means/modes over the area. ?bbox= is sugar for axis-aligned rectangles and is converted to a 4-vertex Polygon internally.
Examples below use the dev-region default point near Mesa, WA (Franklin County). Swap in any lat/lon, or pass a polygon over any AOI inside the OPERA RTC coverage envelope. See the API reference for the full schema, and the auth page for partner-app OAuth scopes.
Agronomist field-walk triage — which fields look hungry today?
- Who
- Crop consultants, agronomy services, retail-ag scouting teams.
- Question
- Across my client portfolio, which fields are flagging 'likely N-stressed' today (water + heat ruled out) so I can target the field walks that matter?
- Reads
n_stress_proxy.state, n_stress_proxy.headline, n_stress_proxy.confidence, n_stress_proxy.drivers_likely, n_stress_proxy.drivers_ruled_out
# Per-field polygon query — the n_stress_proxy block tells you the
# state without you needing to interpret raw NDRE.
curl "https://api.mud2dust.io/v1/point?geom=$(printf %s "$GEOM" | jq -sRr @uri)&use=corn"
# Map-wide triage: toggle on the "Nitrogen-stress proxy" layer at
# https://mud2dust.com/ — deep red pixels are the walk list. Same
# decision tree the API runs, applied per-pixel by the satellite
# pipeline, served as a categorical COG via titiler.// Each field has a polygon; classify them in one sweep.
const results = await Promise.all(
fields.map(async (f) => {
const url = new URL("https://api.mud2dust.io/v1/point");
url.searchParams.set("geom", JSON.stringify(f.geom));
url.searchParams.set("use", f.crop);
const r = await fetch(url).then((r) => r.json());
return { field: f.name, n: r.n_stress_proxy };
}),
);
// Walk list = states where N stress is the lead diagnosis.
const walkList = results.filter((r) => r.n.state === "likely_n_stressed");
walkList.forEach((r) => {
console.log(`${r.field}: ${r.n.headline}`);
});
// Don't ignore water/heat — they affect today's plan too.
const fixWaterFirst = results.filter((r) => r.n.state === "water_stress_first");
const tooHot = results.filter((r) => r.n.state === "heat_stress_first");Note: Honest about being a directional proxy — not a tissue test. The walk list narrows scout time; tissue-sample confirmation gives you the kg/ha shortfall to target with prescription. Lab-N + tissue-test contributions tighten the model substantially (see /explain/nitrogen/).
Field-boundary polygon — averaged answer over a whole block
- Who
- Any app that already has a field boundary (precision-ag platform, FMS, irrigation district, watershed group).
- Question
- Given my field's GeoJSON Polygon, what's the average VWC, soil texture, irrigation recommendation, and traction state across the whole block — not just at one click point?
- Reads
polygon.area_km2, polygon.sampled_points, polygon.source, signals.vwc_m3_m3 (mean over polygon), context.soil.clay_pct (mean), irrigation.summary, traction.state
# Define your field's GeoJSON Polygon (lon,lat — RFC 7946 order):
GEOM='{"type":"Polygon","coordinates":[[[-119.26,46.48],[-119.245,46.48],[-119.245,46.495],[-119.26,46.495],[-119.26,46.48]]]}'
# JSON-stringify, URL-encode, append:
curl "https://api.mud2dust.io/v1/point?geom=$(printf %s "$GEOM" | jq -sRr @uri)&use=apple_orchard"
# MultiPolygon works the same way for non-contiguous fields.
# bbox= is a convenience shorthand for axis-aligned rectangles.// fieldGeom is a GeoJSON Polygon or MultiPolygon you already
// have — from a shapefile import, drone-flight extent, etc.
const url = new URL("https://api.mud2dust.io/v1/point");
url.searchParams.set("geom", JSON.stringify(fieldGeom));
url.searchParams.set("use", "apple_orchard");
const r = await fetch(url).then((r) => r.json());
// Polygon-mode response carries the same shape as point mode plus a
// top-level polygon block with bbox + km² + how many in-polygon
// samples were averaged. Useful for showing confidence in the UI.
console.log(`Block: ${r.polygon.area_km2.toFixed(2)} km², ${r.polygon.sampled_points} pts`);
console.log("Block-average VWC:", r.signals.vwc_m3_m3);
console.log("Irrigate:", r.irrigation.summary);
console.log("Traction state:", r.traction.state);Note: Aggregation: continuous fields (VWC, NDVI, clay%, etc.) are arithmetic mean of in-polygon samples. CDL crop class is the mode — useful when a polygon spans multiple cover types. The forecast / irrigation / traction blocks compute on the averaged inputs, so they describe the field's average behavior, not any single pixel.
Irrigator — when do I turn on the water, and for how long?
- Who
- Pivot operator, drip-tape grower, agronomist setting a per-block prescription.
- Question
- Given my crop and my pivot's flow rate, how much longer can I wait before VWC drops past the stress threshold, and how many hours of pumping do I need to refill?
- Reads
irrigation.urgency, irrigation.summary, irrigation.duration_hours, irrigation.application_mm, forecast.target_date
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&use=alfalfa&rate_in_per_hour=0.18'const r = await fetch(
`/v1/point?lat=${lat}&lon=${lon}&use=alfalfa&rate_in_per_hour=0.18`
).then((r) => r.json());
if (r.irrigation.urgency === "imminent" || r.irrigation.urgency === "soon") {
showCard({
title: `Irrigate ${r.irrigation.urgency.replace("_", " ")}`,
detail: r.irrigation.summary,
runHours: r.irrigation.duration_hours,
applyInches: r.irrigation.application_in,
});
}Note: If the user has lab samples for the field, add them to the boundary first — they re-fit the SoilGrids prior locally and tighten the field-capacity estimate that drives application_mm.
Hiking / trail app — is this trail too muddy to walk on?
- Who
- Trail-condition layer in a hiking, trail-running, or e-bike app.
- Question
- Given current VWC and the soil's clay content, will mud cake to my shoes — and if so, when does it dry?
- Reads
traction.state, traction.surfaces[surface=shoes], forecast.target_date
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&target_vwc=0.30'const r = await fetch(
`/v1/point?lat=${lat}&lon=${lon}&target_vwc=0.30`
).then((r) => r.json());
const shoes = r.traction.surfaces.find((s) => s.surface === "shoes");
const boots = r.traction.surfaces.find((s) => s.surface === "boots");
return {
state: r.traction.state, // firm | tacky | sticky | slick | saturated
shoes: shoes.verdict, // ok | caution | not_recommended
boots: boots.verdict,
drysOn: r.forecast?.target_date ?? null, // when VWC crosses 0.30
note: shoes.note, // human-readable shoe-specific advice
};Note: If you don't trust our `traction` summary, you have everything you need to recompute it: `signals.clay_pct`, `signals.vwc_m3_m3`, and `context.soil.hydraulics.fc_m3_m3`.
Off-road / fleet — can my truck reach this site?
- Who
- Adventure-bike app, dirt-road navigator, oilfield service dispatcher, fire / utility truck routing.
- Question
- Will tires bog down or sling clay onto fenders if I drive in there today, and if not today, when?
- Reads
traction.surfaces[surface=motorbike], traction.surfaces[surface=truck], traction.recommended_tread_mm, forecast.target_date
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&target_vwc=0.22'const r = await fetch(
`/v1/point?lat=${lat}&lon=${lon}&target_vwc=0.22`
).then((r) => r.json());
const truck = r.traction.surfaces.find((s) => s.surface === "truck");
if (truck.verdict !== "ok") {
warn(`Drive avoidance: ${truck.note}. Firm-up by ${r.forecast?.target_date}.`);
} else if (r.traction.recommended_tread_mm > 6) {
hint("Mud-terrains preferred — current conditions tacky.");
}Equipment / harvest planner — when can I get the combine in?
- Who
- Custom harvester scheduling crews across multiple farms; ag dispatcher.
- Question
- When does VWC drop past my firm-ground threshold so the combine and grain cart don't compact or rut the field?
- Reads
forecast.days_to_target, forecast.target_date, traction.surfaces[surface=combine]
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&target_vwc=0.18&forecast_days=14'const r = await fetch(
`/v1/point?lat=${lat}&lon=${lon}&target_vwc=0.18&forecast_days=14`
).then((r) => r.json());
const combine = r.traction.surfaces.find((s) => s.surface === "combine");
return {
harvestableNow: combine.verdict === "ok",
combineNote: combine.note,
windowOpensIn: r.forecast?.days_to_target ?? null,
windowOpensOn: r.forecast?.target_date ?? null,
};Planting-window assistant — is my seedbed ready?
- Who
- Row-crop farmer (corn / soy / cotton / wheat); precision-ag platform.
- Question
- Given my crop, is rootzone moisture in the planting window — not too dry to germinate, not too wet to compact?
- Reads
now.vwc_m3_m3, now.pct_of_field_capacity, context.soil.hydraulics, forecast.target_date
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&use=corn&target_vwc=0.25'const r = await fetch(
`/v1/point?lat=${lat}&lon=${lon}&use=corn&target_vwc=0.25`
).then((r) => r.json());
const pct = r.now.pct_of_field_capacity; // 0–100, % of FC
let state: "wait_dry" | "go" | "wait_rain";
if (pct > 90) state = "wait_dry"; // saturated → compaction risk
else if (pct < 50) state = "wait_rain"; // too dry → poor germination
else state = "go";Concrete pour readiness — can I pour today?
- Who
- Concrete contractor pouring slabs, foundations, ag pads.
- Question
- Is the subgrade damp-but-not-saturated, is rain in the forecast, and will overnight ground temp stay above curing minimums?
- Reads
signals.lst_goes_celsius, signals.lst_hls_celsius, signals.precip_imerg_1d_mm, now.vwc_m3_m3, context.soil.hydraulics.fc_m3_m3
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532'const r = await fetch(`/v1/point?lat=${lat}&lon=${lon}`).then((r) => r.json());
const overnightTooCold = (r.signals.lst_goes_celsius ?? 99) < 4;
const subgradeSoaked =
r.now.vwc_m3_m3 != null &&
r.context.soil.hydraulics &&
r.now.vwc_m3_m3 > r.context.soil.hydraulics.fc_m3_m3 * 0.95;
const recentRainMm = r.signals.precip_imerg_1d_mm ?? 0;
const pourOk = !overnightTooCold && !subgradeSoaked && recentRainMm < 5;Note: We don't ship a `concrete` summary block — concrete spec varies by mix, slump, and inspector. Compose from primitives.
Energy / DR forecaster — what irrigation load is on the wire tomorrow?
- Who
- REA / public utility load forecaster, demand-response aggregator.
- Question
- Aggregating across a service-territory bbox, how many acre-inches of irrigation are likely to fire in the next 48–72 hours, and at which feeders?
- Reads
irrigation.urgency, irrigation.application_mm, irrigation.next_event_in_days, context.crop_or_use
# Sample many points across a feeder boundary, sum the application:
for pt in $(jq -r '.points[] | "\(.lat),\(.lon)"' feeder.json); do
lat=${pt%,*}; lon=${pt#*,}
curl -s "https://api.mud2dust.io/v1/point?lat=$lat&lon=$lon&use=corn"
done | jq -s 'map(.irrigation.application_mm // 0) | add'// Each pivot is ~50–130 acres; aggregate per feeder.
const points = await Promise.all(
feeder.samples.map((p) =>
fetch(`/v1/point?lat=${p.lat}&lon=${p.lon}&use=${p.use}`).then((r) =>
r.json(),
),
),
);
const imminentMm = points
.filter((r) => r.irrigation?.urgency === "imminent")
.reduce((acc, r) => acc + r.irrigation.application_mm, 0);
return { feeder: feeder.id, imminentMm, willPump: imminentMm > 0 };Note: Vector-tile MVT export of these signals (P5.x) will replace the per-point sampling loop.
What you don't see in this list
mud2dust ships two opinionated blocks (irrigation, traction) because their inputs (Kc, root depth, application rate; clay × VWC) are universal. We deliberately don't ship application-specific summaries for concrete pours, planting windows, or DR forecasts — those depend on the spec / agronomist / utility tariff in the consuming app.
The signals block exposes every per-layer pixel value we have at the point — VWC, σ⁰, NDVI/NDWI/NDRE, LST (HLS + GOES), precip (HRRR / MRMS / IMERG), monthly ET, SMAP L4, clay / sand / SOC / pH, DEM, local incidence angle, CDL crop class. Build whatever domain logic you want on top.