Developer documentation

API & webhooks

Connect RainAlert to Procore, a BI tool, or whatever your team already runs. Read the data with an API key; hear about storms as they happen with a webhook.

Try it first — no account

Two things you can do before signing up for anything, at the sandbox:

  • Evaluate your own coordinates. Paste a site's lat/long and get the rainfall both NOAA products are reporting at that grid cell right now, the state we derive, that state's real permit threshold and response window, whether we have verified those numbers against primary permit text, and the exact deadline the current rainfall would create in the site's local time. It runs the production lookups, not a mock.
  • Send yourself a signed webhook. Give an HTTPS endpoint and we POST a real sample event to it, signed the same way live events are, and show you the payload, the headers and how to verify the signature. A receiver written against it works unchanged in production.
curl -sX POST https://swrainalert.com/api/v1/sandbox/evaluate \
  -H 'content-type: application/json' \
  -d '{"lat":33.7554,"lng":-84.4008,"rainfall_inches":0.62}'

The sandbox creates no site and sends no mail. It is rate limited per caller, and the webhook tester refuses any URL that resolves to a private or reserved address — the same guard that protects real endpoints.

The rainfall is read from the same two national grids the monitored sites use, at the same cell, and reconciled the same way: we alert on the higher of the two and never average them. A point with no radar coverage comes back as null with a note — never 0.0. Absent rainfall and zero rainfall are different facts, and nothing in this API conflates them.

API keys

Team → API keys → Create key. Choose a scope:

ScopeWhat it can do
Read only Fetch everything in the next section. Cannot change anything. Use this unless you know you need the other one — it is the right scope for anything you hand to another company's software.
Read and write Can also create sites, file inspections and upload evidence.

The key is shown once. We store only a hash, so nothing can show it to you again. If it is lost, revoke it and issue another.

Send it as an X-API-Key header:

export RA_KEY="ra_live_…"

curl -H "X-API-Key: $RA_KEY" \
  https://swrainalert.com/api/v1/sites/

Read-only is enforced on the HTTP method, centrally, for every endpoint that exists now or later — a read-only key gets 403 on anything that is not a GET. Keys can carry an expiry and can be revoked at any time. Revoked keys stay in the list with the date they stopped working, because "when did we turn that off" is a question an auditor asks.

What you can read

Base URL: https://swrainalert.com/api/v1

WhatEndpoint
Every site, with thresholds and assignmentGET /sites/
One siteGET /sites/{site_id}
Live dual-source reading and the open rain eventGET /sites/{site_id}/rain-event
NWS forecast (24/48/72 h) and active weather alerts — expected, never a measurement; null where nothing covers a horizonGET /sites/{site_id}/forecast
Daily rainfall historyGET /sites/{site_id}/rainfall?days=30
The same as a spreadsheetGET /sites/{site_id}/rainfall.csv?days=30
Inspections for a siteGET /sites/{site_id}/inspections
A filed report, structuredGET /sites/{site_id}/inspections/{id}/report
A filed report as PDFGET /sites/{site_id}/inspections/{id}/report.pdf
Every inspection, all sitesGET /inspections?since=2026-01-01
A date range as one PDFGET /inspections/export.pdf?since=2026-01-01
Open corrective actionsGET /sites/{site_id}/corrective-actions
Exposure summaryGET /analytics/summary?days=30
Weekly trend bucketsGET /analytics/trends?weeks=12

Everything is scoped to one organization. A key issued by Acme sees Acme's sites and nothing else; there is no cross-tenant read anywhere in the API.

Reading rainfall honestly

Three fields decide whether a number means what it looks like.

{
  "stage_iv": 0.699,
  "mrms": 0.551,
  "resolved_inches": 0.699,
  "source_used": "stage_iv",
  "availability": "both",
  "sources_diverged": false,
  "data_gap": false
}
  • null is not zero. A site the radar cannot see reports null with data_gap: true. Rendering that as 0.00 in your system reproduces the single worst bug this product exists to prevent — a site nobody could see, filed as a dry day.
  • We never average the two sources. resolved_inches is the higher of them and source_used says which. If you need one number, use resolved_inches; if you are showing a customer, show both.
  • accumulated_inches on a rain event is the storm, not now. Both sources report a rolling 24-hour total, so a day after a breach the live reading is near zero while the event is still open. The event's figure is what the alert was raised on; the reading is what is in the window right now.

Webhooks

Team → Webhooks → Add endpoint. Give an https:// URL and pick events:

EventWhen
rain_event.breachedA storm crossed the site's permit threshold
inspection.overdueThe deadline passed without an inspection
inspection.filedAn inspection was submitted
corrective_action.openedA finding was raised
corrective_action.closedOne was verified fixed
forecast.advisoryThe NWS forecast for a site reaches its trigger within 24 h. Expected, not observed — no obligation exists until rain falls. Once per forecast episode.
weather.alertA rain-relevant NWS alert (flood, flash flood, severe thunderstorm, tropical) became active for a site. Once per alert.
POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-RainAlert-Event: rain_event.breached
X-RainAlert-Timestamp: 1788950400
X-RainAlert-Signature: sha256=1c8f…
X-RainAlert-Delivery: 0f5c…

{
  "event": "rain_event.breached",
  "occurred_at": "2026-09-07T02:23:44.435637+00:00",
  "data": {
    "site_id": "GA-BWT-8802",
    "location_name": "Riverside Subdivision Ph2",
    "state": "GA",
    "accumulated_inches": 0.699,
    "threshold_inches": 0.5,
    "source_used": "stage_iv",
    "stage_iv": 0.699,
    "mrms": 0.551,
    "sources_diverged": false,
    "inspection_deadline": "2026-09-08T02:23:44.435637+00:00"
  }
}

Verifying signatures

Recompute the signature before trusting a payload, and check the timestamp's age as well.

import hashlib, hmac, time

def verify(secret, timestamp, raw_body, signature, max_age=300):
    expected = "sha256=" + hmac.new(
        secret.encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, signature):
        return False
    return abs(time.time() - int(timestamp)) <= max_age

The timestamp is inside the signed material, not merely a header beside it. That is what stops a captured request being replayed at you a week later — so check its age, or the signature is only doing half its job.

Get the secret from Team → Webhooks → Secret whenever you need it. Unlike an API key it can be shown again: it is derived rather than stored, and the person who creates an endpoint is rarely the one who writes the handler.

Delivery and retries

  • Any 2xx is success. Anything else is retried after 1 min, 10 min, 1 h and 6 h, then given up on.
  • Twenty consecutive failures disables the endpoint. Re-enable it from the same screen once your side is fixed.
  • Team → Webhooks → Delivery log shows the status and body your server returned. Most webhook problems are visible there without asking us.
  • Respond quickly and do the work asynchronously — we time out after 10 seconds.
  • Delivery is at-least-once. Use X-RainAlert-Delivery to make your handler idempotent.
  • Endpoints must be HTTPS and resolve to a public address. We do not follow redirects.

Procore, Autodesk and friends

There is no packaged connector yet. Both patterns work today:

  • Pull into their system — a scheduled job with a read-only key, using the endpoints above. Inspection PDFs are the usual payload.
  • Push from ours — a webhook to a small function you host that creates the object on their side.

For Procore specifically the useful shape is inspection.filed → upload report.pdf into that project's Documents, and rain_event.breached → a Daily Log note. That needs Procore OAuth and a project-to-site mapping, which is a connector we would build rather than something you should have to write. Ask us to prioritise it.

Limits, and what we have not built

  • No rate limiting on the authenticated API yet. Please poll no faster than once a minute per site — webhooks exist so you do not have to poll at all.
  • Read-only still means all of your organization's data. Treat a key like a password and rotate it.
  • The sandbox has its own per-caller budget and will answer 429 if you exceed it.

RainAlert is a monitoring and recordkeeping aid, not a compliance guarantee. Where a permit requires an on-site rain gauge, that gauge is the system of record — including in whatever system you pipe this into.