This page as Markdown, byte for byte: /hazard.md
Point hazard lookup: what the pinned data says about a coordinate#
GET /hazard, with peril, lat and lon in the query, answers one question — what has happened at this place, in the record this build pins — and answers it synchronously. There is no object to create first and no job to poll: a coordinate goes in, a hazard metric comes out, and the response names the exact snapshot it was computed from.
It is the first slice of Analyse (hazard lookups, event footprints, accumulation). This build serves all of it: the point lookup here, the event catalogue and event footprints, the analyse job over an object — which is this same lookup, aimed at an object's own locations — and accumulation across a set of objects.
Read the quickstart first for the two values you export (CEDE_BASE_URL, CEDE_API_KEY) and how to get a key from POST /signup.
What it is for, and what it is not#
A trigger is a threshold, and a threshold is a decision about a place. Before you write attachment.value into a risk object, this route tells you what the public record for that place actually holds: how often the ground there has broken above magnitude 6, or what daily rainfall total the last forty years put a one-in-ten-year label on. That number is the input to the decision, and you can check it against the same bytes the price path reads.
What it is not:
- not a price. No expected loss, no loading, no premium. That is
POST /objects/{id}/price, and it needs a whole structure. - not a view of risk. The response says what the record holds, never
whether the hazard should be carried or on what terms. Every answer carries
that sentence in its
disclosurefield. - not a hazard model. Nothing is fitted, interpolated or extrapolated. The answer is arithmetic over a pinned public record, with the limitations of that record listed in the response itself.
The three parameters#
| Parameter | What it is |
|---|---|
peril |
One of the perils this build pins data for. GET /health will not tell you which; the refusal for a wrong one does, and so does the table below. |
lat |
Latitude in decimal degrees, negative for south. Degrees, minutes and seconds are not read. |
lon |
Longitude in decimal degrees, negative for west. |
Nothing else is read. A fourth parameter is refused rather than ignored — a call answered as though lng were lon is a lookup of somewhere else.
What can be asked, and where#
A peril is answerable when a snapshot of it is committed, because the engine reads snapshots and never the network. So the answerable set is exactly the pinned data, listed in full on the pinned data feeds:
peril |
Answered from | Where it answers |
|---|---|---|
earthquake |
usgs-eq-kanto — the USGS catalogue extract |
34.5–36.5N, 138.5–141.0E: the extract's box, less the half-degree cell the lookup counts in |
weather_station |
era5-rain-bangkok, or any of the four pinned wind series |
within 0.25° of one of their grid points (listed below) |
tropical_cyclone |
the four pinned wind series — a daily wind maximum is a named-storm index | within 0.25° of one of their grid points (listed below) |
The grid points, which are the publisher's cell centres and not the coordinates the snapshots were requested at:
| Feed | Grid point |
|---|---|
era5-rain-bangkok |
13.743409N, 100.495865E |
era5-wind-galveston |
29.279436N, -94.87326E |
era5-wind-miami |
25.764498N, -80.196075E |
era5-wind-new-orleans |
29.982424N, -90.10489E |
era5-wind-tampa |
27.94376N, -82.49155E |
Two shapes of snapshot, two shapes of answer:
- a catalogue extract answers with exceedance — how many solutions the record holds at each magnitude threshold inside a half-degree cell around your point, as an annual rate. It also groups aftershocks into occurrences (72 hours from the first event of a sequence) and reports that rate beside it, because a sequence is one event to a structure and many to a catalogue.
- a gridded daily series answers with return levels — the record reduced to one maximum per calendar year, ranked, and read at the 2-, 5-, 10- and 20-year return periods by the Weibull plotting position. Nothing beyond the record is estimated: a return period the record cannot reach is absent.
Coverage is checked rather than approximated. A cell that reached over the edge of an extract would count fewer events than really occurred and report a rate too low for a reason nothing records, so a point near the edge is refused with the edge it crossed. The area is an axis-aligned cell in degrees rather than a radius in kilometres, deliberately: a great-circle distance needs arithmetic that does not replay byte for byte, and replay is worth more here than a round search area.
Walk it#
The block below looks up earthquake hazard at a point in Tokyo and wind at a point in Miami, prints what each answer rests on, and then fetches the manifest of the snapshot the first answer named — so the feed id, the vintage and the digest in the response are three strings you can follow, not three strings you have to take on trust.
set -euo pipefail
: "${CEDE_BASE_URL:?export CEDE_BASE_URL first — see the quickstart}"
: "${CEDE_API_KEY:?export CEDE_API_KEY first — see the quickstart}"
# 1 — Earthquake hazard at 35.6284N, 139.7387E (Tokyo).
curl -sS "$CEDE_BASE_URL/hazard?peril=earthquake&lat=35.6284&lon=139.7387" \
-H "Authorization: Bearer $CEDE_API_KEY" > quake.json
python3 -c 'import json
answer = json.load(open("quake.json"))
feed = answer["feed"]
print("feed :", feed["id"], feed["vintage"], feed["sha256"][:12] + "…")
record = answer["assumptions"]["period_of_record"]
print("record :", record["start"], "to", record["end"],
"(" + str(record["years"]), "years)")
metrics = answer["metrics"]
print("cell :", metrics["cell"]["min_latitude"], "to",
metrics["cell"]["max_latitude"], "N /",
metrics["cell"]["min_longitude"], "to",
metrics["cell"]["max_longitude"], "E")
print("events :", metrics["events_in_cell"])
for row in metrics["exceedance"]:
print(" M%-5s %4d event(s), %s a year; %d occurrence(s), %s a year"
% (row["magnitude"], row["events"], row["annual_rate"],
row["occurrences"], row["annual_occurrence_rate"]))
largest = metrics.get("largest_event")
if largest:
print("largest : M%s on %s" % (largest["magnitude"], largest["time"]))
print("disclosure:", answer["disclosure"])'
# 2 — Named-storm wind at 25.7617N, -80.1918E (Miami).
curl -sS "$CEDE_BASE_URL/hazard?peril=tropical_cyclone&lat=25.7617&lon=-80.1918" \
-H "Authorization: Bearer $CEDE_API_KEY" > wind.json
python3 -c 'import json
answer = json.load(open("wind.json"))
metrics = answer["metrics"]
measure = metrics["measurement"]
print("measures :", measure["variable"], "in", measure["unit"],
"(" + measure["resolution"] + ")")
grid = metrics["grid_point"]
print("grid cell:", grid["latitude"], grid["longitude"],
"offset", grid["offset_degrees"]["latitude"],
grid["offset_degrees"]["longitude"], "degrees")
print("days :", metrics["days_observed"], "observed,",
metrics["days_missing"], "missing")
print("wettest/windiest day:", metrics["maximum_daily"]["value"],
"on", metrics["maximum_daily"]["date"])
for level in metrics["return_levels"]:
print(" 1-in-%-3d %s %s" % (level["return_period_years"],
level["level"], measure["unit"]))
print("known limits:")
for line in answer["assumptions"]["limitations"]:
print(" -", line)'
# 3 — The snapshot the first answer named, fetched by the id and version in it.
FEED_ID="$(python3 -c 'import json; print(json.load(open("quake.json"))["feed"]["id"])')"
VERSION="$(python3 -c 'import json; print(json.load(open("quake.json"))["feed"]["version"])')"
curl -sS "$CEDE_BASE_URL/feeds/$FEED_ID/versions/$VERSION" \
-H "Authorization: Bearer $CEDE_API_KEY" > manifest.json
python3 -c 'import json, sys
answer = json.load(open("quake.json"))
manifest = json.load(open("manifest.json"))
same = manifest["sha256"] == answer["feed"]["sha256"]
print("the lookup read", manifest["id"], manifest["version"],
"published by", manifest["publisher"])
print("digests agree:", same)
sys.exit(0 if same else 1)'
The last line exits non-zero if the digest on the answer and the digest on the snapshot ever disagreed. They cannot, and that is the point: the lookup names the bytes, and the bytes are fetchable at GET /feeds/{feed_id}/versions/{version}/data, where you can recompute the digest yourself — the pinned data feeds walks that check.
Reading the answer#
Every response carries five things, and none of them is optional:
feed— the id, the version, the vintage, the kind and the SHA-256 of the exact bytes read, with the paths the manifest and the bytes are at. One snapshot per answer, always: a number you cannot reproduce from a single pinned file is a number nobody can check.point— the coordinate as it was read, so a mistyped parameter is visible in the answer rather than only in your request log.metrics— the measurement.basissays which of the two shapes above it is, so a client can switch on it.assumptions— the data source, the period of record, the method in English, andlimitations: what this answer cannot tell you, in the response itself rather than on this page. A hazard number whose limitations live in the documentation is a number that will be read without them.disclosure— the fixed sentence stating that this is a measurement over a public record and not a view of risk.
The full response shape is published as [hazard.v0.schema.json](/hazard.v0.schema.json); the map from every route to the schema governing its responses is [api-contract.v0.json](/api-contract.v0.json).
When it refuses#
Both refusals are 422 in the standard error envelope (error reference), and both name what would have worked.
A peril nothing is pinned for:
{
"schema_version": "0.1.0",
"error": {
"status": 422,
"code": "unsupported_peril",
"message": "this build answers point hazard lookups for earthquake, tropical_cyclone, weather_station; it has no pinned feed that speaks to wildfire. …"
}
}
A point no pinned snapshot of that peril covers:
{
"schema_version": "0.1.0",
"error": {
"status": 422,
"code": "outside_feed_coverage",
"message": "no pinned earthquake snapshot covers 0,0. usgs-eq-kanto@2026-08-10: its extract covers 34.0..37.0N and 138.0..141.5E, which does not hold the whole 0.5° cell around this point. …"
}
}
There is no all-of-the-world feed to fall back on, by design. A snapshot that covered less than the area asked about would answer with less hazard than the record holds, and nothing in the response would say so.
What comes next#
GET /hazard is a lookup, not an artifact: it writes nothing onto an object and leaves no provenance behind. The verb that does — this same reading at every location of an object, recorded on that object's provenance with its assumptions and data vintages — is [POST /objects/{id}/analyse](analyse.md). Two more slices answer elsewhere and write nothing either: the event catalogue and footprints, and accumulation across a set of objects, which totals the exposure you have already stored. GET /health is the list of what the environment in front of you reaches at all.