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:
| Scope | What 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
| What | Endpoint |
|---|---|
| Every site, with thresholds and assignment | GET /sites/ |
| One site | GET /sites/{site_id} |
| Live dual-source reading and the open rain event | GET /sites/{site_id}/rain-event |
| NWS forecast (24/48/72 h) and active weather alerts — expected, never a measurement; null where nothing covers a horizon | GET /sites/{site_id}/forecast |
| Daily rainfall history | GET /sites/{site_id}/rainfall?days=30 |
| The same as a spreadsheet | GET /sites/{site_id}/rainfall.csv?days=30 |
| Inspections for a site | GET /sites/{site_id}/inspections |
| A filed report, structured | GET /sites/{site_id}/inspections/{id}/report |
| A filed report as PDF | GET /sites/{site_id}/inspections/{id}/report.pdf |
| Every inspection, all sites | GET /inspections?since=2026-01-01 |
| A date range as one PDF | GET /inspections/export.pdf?since=2026-01-01 |
| Open corrective actions | GET /sites/{site_id}/corrective-actions |
| Exposure summary | GET /analytics/summary?days=30 |
| Weekly trend buckets | GET /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
}
nullis not zero. A site the radar cannot see reportsnullwithdata_gap: true. Rendering that as0.00in 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_inchesis the higher of them andsource_usedsays which. If you need one number, useresolved_inches; if you are showing a customer, show both. accumulated_incheson 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:
| Event | When |
|---|---|
rain_event.breached | A storm crossed the site's permit threshold |
inspection.overdue | The deadline passed without an inspection |
inspection.filed | An inspection was submitted |
corrective_action.opened | A finding was raised |
corrective_action.closed | One was verified fixed |
forecast.advisory | The 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.alert | A 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
2xxis 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-Deliveryto 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
429if 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.