Skip to content
CRATON Docs Get a free API key

Data

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 disclosure field.
  • 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 GET on /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, a GET on /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.

Shell Runs in CI
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. basis says 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, and limitations: 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:

JSON
{
  "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:

JSON
{
  "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.

This page as Markdown, byte for byte: /hazard.md