Versioning
Breaking changes get a new path prefix (/v2, /v3). Non-breaking additions land in the current version. Old versions stay live for ≥6 months after a successor ships, with a Sunset response header advertising the cutoff.
OpenAPI spec
The live spec for the running sensor-bridge is at https://api.mud2dust.com/v1/openapi.json (DNS for api.mud2dust.com lands when the sensor-bridge stack deploys; the spec is also served alongside development at the local dev port). An interactive explorer is queued for P1.7 iter 4.
Object types
Seven first-class shapes share auth, trust scoring, and privacy controls:
| Shape | Covers | Endpoint(s) |
|---|---|---|
| Observation | time-series point readings | POST /v1/observations |
| Profile | multi-depth time-series (Sentek 9-depth, lysimeters) | POST /v1/profiles |
| Sample | discrete lab results (gravimetric VWC, texture, OM, …) | POST /v1/samples |
| Event | single drone / aircraft / PhenoCam capture | POST /v1/uploads/initiate → complete |
| Collection | multi-flight drone survey, multi-pass aircraft campaign | POST /v1/collections |
| Annotation | geotagged agronomist note, citizen photo | POST /v1/annotations |
| Boundary | field / management zone / EC-mapped zone | POST /v1/boundaries |
Auth
OAuth 2.0 + PKCE with per-shape scopes (stations:write, observations:write, etc.) lands in P3.6. During development the placeholder is an X-Contributor-Id header.
What's live today (P1.5)
POST /v1/stations
body: { name, geom_internal: {type:"Point", coordinates:[lon,lat]},
vendor, observed_properties: [...], depths_cm?: [...], logger?: {...} }
→ 201 { id: "stn_…", geom_public, geom_public_mode, ... }
POST /v1/observations
body: [ { station_id, observed_property, value, unit, ts, depth_cm? }, ... ]
→ 202 { accepted, rejected, rejected_details: [{index, flags}] }Trust model
Every contribution has a sensor_class (research / professional / consumer / diy) and an operator_class (researcher / agronomist_supported / farmer / hobbyist / unknown). The product of those (× installation quality) yields a training_weight. Contributions ≥ 0.5 are eligible to retrain the public calibration model; everyone else is a correction-taker with full feature parity but excluded from retrain. Users never see their tier as a number — only contribution health indicators.
Privacy / coordinate fuzzing
Every shape stores both geom_internal (exact, used for model training) and geom_public (jittered or aggregated). Default mode is 5km jitter; opt-in to 10km, county- aggregated, exact, or fully private (excluded from public surfaces). Jitter is deterministic per (contributor, external_id) so coords stay stable across requests.