Developer

Reach Ingest Spec (v1)

Public interoperability spec for partners pushing reach data into H2OFlows

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 fieldDerivation
comidsNLDI flowline trace from geometry
river_nameGNIS reverse lookup on put-in coord
basinPostGIS containment vs HUC8 basins table
length_miComputed from geometry
gradient_fpmElevation profile via elevation service
gaugesNearest USGS gauge within 25 mi of put-in (partner confirms)
put_in_nameGNIS lookup; fallback to "Put-In"
take_out_nameGNIS 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-v2 etc; 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.