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 Craton 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). Craton serves all of it: the point lookup here, the event catalog 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.
On this page
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 parameters
| Parameter | What it is |
|---|---|
peril |
One of the perils Craton 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. |
feed |
Optional. The id of the pinned feed to answer from, when more than one could. Required for severe_convective_storm, where each metro is covered by a hail, a thunderstorm wind and a tornado feed. |
Nothing else is read. Any other 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 |
the seven regional USGS catalog extracts (Kanto, California, Cascadia, Alaska, Chile, Turkey, Mexico) | inside one extract's box, less the half-degree cell the lookup counts in; feeds lists each box |
weather_station |
era5-rain-bangkok, the five station rain gauges, or any of the four pinned wind series |
within 0.25° of one of their 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) |
flood |
wsc-flow-bow-calgary, a river gauge |
within 0.25° of the station (listed below) |
severe_convective_storm |
the eighteen NOAA storm-report feeds: hail, thunderstorm wind and tornado for six US metros, named with feed= |
inside one extract's box, less the half-degree cell; the storm-report box lists each box |
grid_outage |
the ten EAGLE-I county outage series (Harris, Dallas and Travis in Texas; Lee, Hillsborough and Pinellas in Florida; Orleans, Cook, King and Wayne) | within 0.25° of the county's 2020 population center (listed below); the answer is the county's count |
wildfire |
the five MTBS mapped-wildfire extracts: Northern and Southern California, the Pacific Northwest, Colorado and the Southwest | inside one extract's box, less the half-degree cell; wildfire, by the acre lists each box |
The points: for a reanalysis series, the publisher's cell center and not the coordinates the snapshot was requested at; for a station, the instrument's own site; for a county's outage series, the county's 2020 Census population center:
| Feed | Point |
|---|---|
eaglei-outage-cook |
41.869515N, -87.757998E (Cook County, Illinois: its 2020 population center) |
eaglei-outage-dallas |
32.809819N, -96.787649E (Dallas County, Texas: its 2020 population center) |
eaglei-outage-harris |
29.816889N, -95.425716E (Harris County, Texas: its 2020 population center) |
eaglei-outage-hillsborough |
27.967264N, -82.39857E (Hillsborough County, Florida: its 2020 population center) |
eaglei-outage-king |
47.550488N, -122.230405E (King County, Washington: its 2020 population center) |
eaglei-outage-lee |
26.576019N, -81.851845E (Lee County, Florida: its 2020 population center) |
eaglei-outage-orleans |
29.975765N, -90.05295E (Orleans Parish, Louisiana: its 2020 population center) |
eaglei-outage-pinellas |
27.900189N, -82.727015E (Pinellas County, Florida: its 2020 population center) |
eaglei-outage-travis |
30.314282N, -97.748216E (Travis County, Texas: its 2020 population center) |
eaglei-outage-wayne |
42.329477N, -83.226321E (Wayne County, Michigan: its 2020 population center) |
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 |
ghcn-rain-houston |
29.98438N, -95.36072E (station USW00012960, Houston Intercontinental Airport) |
ghcn-rain-miami |
25.78805N, -80.31694E (station USW00012839, Miami International Airport) |
ghcn-rain-new-orleans |
29.99755N, -90.27772E (station USW00012916, New Orleans (Louis Armstrong) International Airport) |
ghcn-rain-new-york |
40.77898N, -73.96925E (station USW00094728, New York City, Central Park) |
ghcn-rain-tampa |
27.96331N, -82.54E (station USW00012842, Tampa International Airport) |
wsc-flow-bow-calgary |
51.05027N, -114.05146E (station 05BH004, Bow River at Calgary) |
When more than one snapshot of the peril covers the point, the one whose point is nearest answers, measured as the larger of the latitude and longitude offsets in degrees; ties go to the last feed in id order. So a weather_station lookup at Miami's ERA5 grid point answers from that cell, and one at Miami International Airport answers from the airport's rain gauge. The response names the feed either way.
A tie between different feeds is refused rather than broken by id order when the request names no feed: the storm-report feeds for one metro share one box, and a hail answer, a wind answer and a tornado answer are not interchangeable. The refusal names the tied feeds, and feed= picks one.
Three shapes of snapshot, three shapes of answer:
- a catalog 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 catalog.
- a daily series (a reanalysis cell, a station or a county's outage count) 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.
- a storm-report extract answers with report exceedance: how many reports of the feed's kind at or above each level were made inside the half-degree cell, as an annual rate, and the same reports grouped into storm days (one UTC calendar day), because one storm can leave dozens of reports. The largest report in the cell is named, with its time and place. Levels are hail at 1.00, 1.75, 2.00, 2.75 and 4.00 in; thunderstorm wind at 50, 58, 65, 75 and 87 kt; tornadoes at EF1 to EF4. For example, hail in downtown Dallas, a
GETon/hazard?peril=severe_convective_storm&lat=32.78&lon=-96.80&feed=ncei-hail-dallas. A mapped-wildfire extract answers in the same shape: the fires whose MTBS point lies in the cell, counted at 1,000, 10,000, 50,000, 100,000 and 250,000 acres, and the largest one named. Paradise, California, aGETon/hazard?peril=wildfire&lat=39.76&lon=-121.62.
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 kilometers, 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; the map from every route to the schema governing its responses is 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": "Craton 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-alaska@2026-09-25: its extract covers 50.0..72.0N and -180.0..-129.0E, which does not hold the whole 0.5° cell around this point. usgs-eq-california@2026-09-25: … (one reason per pinned earthquake snapshot)"
}
}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. Two more slices answer elsewhere and write nothing either: the event catalog 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.