Reach Ingest Spec (v1)
Reach Ingest Spec — v1
Public, versioned schema for pushing reach (river run) data into H2OFlows. Intended for partner data providers such as American Whitewater (AW), state agencies, and outfitter networks. Designed so partners can adapt their existing schemas without breaking changes on either side.
Status: draft. Subject to revision until v1.0.0 lock.
Schema URL: https://h2oflows.app/specs/reach-ingest-v1.schema.json
Scope
In scope for v1:
- Reach geometry (LineString centerline, put-in → take-out)
- Map features: rapids, access points (put-in/take-out/parking/campsite)
- Descriptions, class ratings, gradient, length
- Flow band thresholds (low / running / high)
- Gauge associations
Not in scope for v1 (gated to v2+):
- Photos and media attachments
- Trip reports / user-generated content
- Real-time flow readings (partners use their own pipeline)
Required fields per reach
name, geometry, class_max, region. Everything else optional.
Derivation rules
If a partner omits an optional field, H2OFlows fills it via existing services:
| Missing field | Derivation |
|---|---|
comids | NLDI flowline trace from geometry |
river_name | GNIS reverse lookup on put-in coord |
basin | PostGIS containment vs HUC8 basins table |
length_mi | Computed from geometry |
gradient_fpm | Elevation profile via elevation service |
gauges | Nearest USGS gauge within 25 mi of put-in (partner confirms) |
put_in_name | GNIS lookup; fallback to "Put-In" |
take_out_name | GNIS lookup; fallback to "Take-Out" |
Partners can pre-fill these fields if they have better data than what derivation produces. Partner-provided values always win.
Conflict handling
Every reach carries a source field (curated, user, aw, partner:slug). When an ingest payload's reach conflicts with an existing curated reach (matched by external_id or geometry proximity), the payload is written to a partner_reach_proposals table for human review by H2OFlows data stewards. The original curated row is not overwritten.
When an ingest reach matches an existing reach with the same source, the row is updated in place (idempotent upsert keyed on source + external_id).
Ingest paths
Push (preferred)
Partner POSTs payloads to H2OFlows on their own schedule.
POST /api/v1/ingest/reaches
Authorization: Bearer <partner_token>
Content-Type: application/json
{
"source": "aw",
"submitted_at": "2026-06-01T00:00:00Z",
"reaches": [
{ ... reach object ... }
]
}
Response:
{
"results": [
{ "external_id": "1234", "status": "created", "slug": "arkansas-river-browns-canyon" },
{ "external_id": "1235", "status": "updated", "slug": "..." },
{ "external_id": "1236", "status": "rejected", "reason": "geometry crosses state boundary; region mismatch" },
{ "external_id": "1237", "status": "proposal", "proposal_id": "uuid-...", "reason": "matches curated reach; queued for steward review" }
]
}
Partner tokens are issued by H2OFlows; one token per partner. Tokens are revocable. Rate limit: 1000 reaches per request, 10 requests per minute.
Pull (fallback)
If a partner cannot run a push pipeline, H2OFlows pulls from a partner-side endpoint on a schedule.
Partner exposes a GeoJSON FeatureCollection at a public URL where each Feature's properties conforms to the Reach object (with geometry inlined as the Feature's geometry).
H2OFlows side runs a weekly cron via the aw-ingest CLI tool.
Reference implementation
A reference Go adapter ships in the API repo at cmd/aw-ingest/. Compiles to a single binary; reads partner JSON, validates against the schema, enriches missing fields, and POSTs to a configured H2OFlows endpoint. Partners are free to reuse, rewrite in their own stack, or ignore.
Example minimal reach
{
"source": "aw",
"reaches": [{
"external_id": "1234",
"external_url": "https://www.americanwhitewater.org/content/River/detail/id/1234/",
"name": "Browns Canyon (Hecla Junction to Stone Bridge)",
"common_name": "Browns",
"river_name": "Arkansas River",
"region": "CO",
"class_max": 3.5,
"description": "Classic mile-by-mile pool-drop run...",
"geometry": {
"type": "LineString",
"coordinates": [
[-106.0123, 38.7456],
[-106.0150, 38.7400],
[-106.0190, 38.7321]
]
},
"gauges": [
{ "source": "usgs", "external_id": "07091200", "is_primary": true }
],
"flow_ranges": [
{ "label": "low", "max_value": 600 },
{ "label": "running", "min_value": 600, "max_value": 2500 },
{ "label": "high", "min_value": 2500 }
]
}]
}
Versioning policy
- Breaking changes ship as
reach-ingest-v2etc; v1 stays supported for 12 months past v2 release. - Additive changes (new optional fields) can ship inside v1 without bumping major.
- Schema URL is permanent — clients pin to a specific schema version.
Questions / partnership
Contact via the H2OFlows GitHub org or ian@h2oflows.app to coordinate token issuance and pilot integration.