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
Request
# 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.
In your app
// 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
Request
# 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.
In your app
// 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
Request
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&use=alfalfa&rate_in_per_hour=0.18'
In your app
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
Request
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&target_vwc=0.30'
In your app
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
Request
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&target_vwc=0.22'
In your app
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]
Request
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&target_vwc=0.18&forecast_days=14'
In your app
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
Request
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532&use=corn&target_vwc=0.25'
In your app
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
Request
curl 'https://api.mud2dust.io/v1/point?lat=46.4873&lon=-119.2532'
In your app
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
Request
# 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'
In your app
// 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.