Skip to content
Licensed hail and wind data DATA VERSION 2026.11.03

Conventions

  • Units are in the name. _mm millimetres, _km2 square kilometres, _mi miles, _m metres, _mph miles per hour, _inches inches. Coordinates are decimal degrees (WGS 84); times are UTC, ISO 8601.
  • Dates are storm days (12:00 to 12:00 UTC, named for the day they start), written YYYY-MM-DD.
  • null versus omitted. A field is null when it belongs to the response but has no value now; it is omitted when it does not apply to this response at all. A field that can be null is marked "nullable" below.
  • Model probability (ml_probability, ml_confidence) is 0–1 on the scale of the model generation that scored the row; compare it only with that generation's bar (ml_provenance, methodology.ml_confidence_bars).
  • Radar size (mesh_mm, swath threshold_mm) is radar's uncalibrated estimate of the largest hail aloft. size_category is a calibrated class and is empty below the detection threshold: "no estimate", never pea.
  • Counts of detections count each radar grid cell once per scan.
  • Data version: every response carries X-Data-Version; see the data changelog.

Outcomes and coverage verdicts

outcome (GET /v1/hail/history)

Values: signal, report_signal, possible_signal, no_signal, insufficient_coverage.

The window-level answer of GET /v1/hail/history (both scopes). READ THIS, NOT count. signal: at least one event: a detection that passes min_confidence (the ML confidence bar) and min_size_mm. report_signal: no event, but a vetted ground hail report (SPC storm reports, NWS Local Storm Reports, CoCoRaHS; see ground_reports) meeting min_size_mm lies inside the radius on a storm day of the window. The evidence is the report, not radar: the radar-only answer is in radar_outcome (typically no_signal or possible_signal). It takes precedence over possible_signal and over any coverage state. Radar can miss hail a person saw: storms far from the nearest radar, where the beam passes kilometres above the hail; storms the hail-detection model scores weak; and reports whose filed time or place is off from the storm. possible_signal: no event, but the radius holds detections below the confidence bar that the hail-detection model still flagged at its own detection threshold (daily_peaks[].below_bar_detection_count > 0). It means "radar saw hail-like echoes the model rates as weak here": worth a look, not a confirmed hail signal, and not a statement of how likely hail was or how large. Measured on a sample of populated point-days over 2022-07..2026-10, a possible_signal window held a ground hail report in the radius about 7% of the time at 5 mi and 6% at 15 mi (small samples: 43 and 66 windows), against 17% and 36% for signal and about 1% for a random point-day. Detections the model did not flag at its detection threshold never count, so no_signal still means nothing the model calls hail was seen. no_signal: neither, AND every day of the window is on record (temporal_coverage.covered: full). insufficient_coverage: neither, but part of the window is not on record; never "no hail". Setting min_confidence at or below the detection threshold leaves no room between the two, so possible_signal cannot occur. signal, possible_signal, no_signal and insufficient_coverage describe the radar record only; ground reports change the answer only to report_signal, never to signal.

outcome (verify, exports)

Values: signal, no_signal, insufficient_coverage.

The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422.

possible_signal versus signal

signal means at least one detection passed the confidence bar and the size floor in the radius. possible_signal means none did, but the model flagged detections in the radius below the bar: radar saw hail-like echoes the model rates as weak. It is a reason to look, not a confirmed hail signal and not a statement of size or likelihood.

Coverage verdicts

temporal_coverage.covered is full, partial or none for the requested window; uncovered_ranges names each missing span and its reason. An empty answer on a window that is not fully covered is insufficient_coverage, never "no hail". The full block is under Temporal coverage.

Shared blocks

These blocks recur across endpoints with the same meaning; endpoint tables below link here instead of repeating them.

data_version: Data version

The licensed-data release a response or export was served under. The X-Data-Version header on every /v1 response carries data_version alone. Every release and what changed in it: https://hailsentinel.com/data/changelog.

FieldTypeUnitMeaning
data_version string — Release id, YYYY.MM.DD.
archive_build string — Date of the last historical rewrite of the hail archive this release declares.
model_generation string — Public id of the hail-detection model scoring new radar scans (e.g. anvil-detect-6). Rows can carry an earlier generation; each row's own is in ml_provenance / model_version.
segmentation string — Storm-cell segmentation version for new radar scans (seg-1 before 2026-10-24 01:46 UTC, seg-2 after; the changelog lists each version's span).
changelog_url string — This release's entry in the public data changelog.

snapshot: Export snapshot

A data release's read-only BigQuery snapshot of the tables a bulk export reads. An export read from one can be regenerated byte for byte.

FieldTypeUnitMeaning
dataset string — Snapshot identifier.
as_of string — The instant the archive was snapshotted.
covers_storm_dates_through string — Last storm date the snapshot vouches for; a later date_end reads live (or is refused when data_version was named).

temporal_coverage: Temporal coverage

Which days of the requested window the radar hail record actually holds: the TIME counterpart of the spatial coverage block. Bounds are read from the record itself (cached ~15 minutes), not configured. A span's first and last days are not counted as covered: a convective day (12Z-12Z) is provably complete only once a later scan exists, so the newest day is after_watermark until then. Such an edge day is still searched (uncovered_ranges[].partial_data: true) and events found on it are returned. In a month that borders a hole in the record, a day holding no rows is not counted as covered. A day our completeness audit recorded as incomplete (coverage_exceptions on GET /v1/verify/coverage) is never covered either (reason: coverage_exception). From the first full day of the storm scanner's per-scan heartbeat (introduced 2026-10), a day counts as covered only when the scanner attests it examined the whole day with publishing working: a quiet day with no hail rows IS covered (so data_through advances through a quiet week), a dark day is NOT, and a day whose storms were published but never reached the hail record is an ingest_gap. Before the heartbeat, a dark day in any other month of a covered span is NOT detected unless it is listed in the dark-day ledger (days measured dark, or only partly written, against independent evidence): those are never covered, and one holding no row at all is partial_data: false. This block describes the radar record only: the verify routes also search ground reports for every day of the window (ground_reports_searched), so a day inside uncovered_ranges can still be returned in events from a ground report alone. When ground_reports_searched is true an uncovered range was still searched for ground reports: it has no radar record (or, with partial_data: true, was searched on a partial radar record). A window the radar record holds nothing for (searchable: false, radar_searched: false) is still answered 200 by the verify grains when the ground-report archive holds EVERY day of it (ground_reports_searched: true, covered: none): only ground reports were searched and the radar was not consulted. That answer is charged when a ground report was found (outcome: signal) and free (X-Credits-Cost: 0) when none was (outcome: insufficient_coverage). It is 422 only when neither record can be searched.

FieldTypeUnitMeaning
record string — Which record the block describes (radar_detections). One of: radar_detections.
archive_start string — First convective day the record holds completely.
data_through string — Newest convective day provably complete: the watermark. Equals data_as_of.
latest_scan_time string · nullable — Newest radar scan holding a hail detection. In a quiet stretch it is older than data_through: the days after it were examined and held no hail.
window_start string — First day of the requested window.
window_end string — Last day of the requested window.
window_days integer — Days in the requested window.
covered_days integer — Days of the window the radar record holds provably complete.
covered string — Share of the window the RADAR record holds provably complete. none on a 200 means either the window is only an edge day that holds rows but is not yet complete (searchable: true), or the radar record holds nothing for it and only ground reports were searched (radar_searched: false, ground_reports_searched: true). One of: full, partial, none.
searchable boolean — The RADAR answer. False when the radar record holds NOTHING for any day of the window. True when at least one day is covered or holds partial data. On a route that searches radar only, false means the request is refused 422. On the three verify grains, false is refused 422 only when ground_reports_searched is false too; with ground_reports_searched: true the window is answered 200 from ground reports alone (see radar_searched).
radar_searched boolean — Whether the radar hail record was consulted for this answer. Always equal to searchable; stated by name so a 200 whose radar search was skipped (false: the radar record holds nothing for any day of the window) is never read as "radar looked and found nothing". With radar_searched: false every event came from a ground report alone, and an empty result is insufficient_coverage, never no_signal; and is not charged (X-Credits-Cost: 0).
ground_reports_searched boolean — True when the query behind this answer (on GET /v1/verify/coverage, a verify run over this window) also searches the ground-report archive for EVERY day of the window, the uncovered_ranges days included (all three verify grains). On a window the radar record holds nothing for (searchable: false), true only when the ground-report archive holds every day of the window; the window is then answered 200 from ground reports alone; false means neither record could be searched and the request is refused 422. Always false on routes that search radar only (GET /v1/hail/history, GET /v1/events, GET /v1/hail/swaths, GET /v1/portfolio/impacts, radar-dataset exports). A statement that the search ran, not that the ground-report archive is complete.
uncovered_ranges array — Inclusive day ranges of the window the RADAR record does not fully hold, ascending. Empty when covered is full. Split so a range is either all partial_data: true (searched on a partial radar record) or all false (no radar record).
uncovered_ranges[].start string — First day of the range (YYYY-MM-DD, inclusive).
uncovered_ranges[].end string — Last day of the range (YYYY-MM-DD, inclusive).
uncovered_ranges[].days integer — Number of days in the range.
uncovered_ranges[].reason string — Why these days are not fully on record. Read it with partial_data, which says whether the record holds any row for them. before_archive: earlier than archive_start. after_watermark: later than data_through (not yet ingested or the day is still open). ingest_gap: inside a hole in the record: missing from it (a span gap, a day the writer recorded nothing or only part of, a known dark day, storms published but never written). coverage_exception: a day the record would otherwise count complete, but that our completeness audit recorded as missing part of its radar detections (listed in GET /v1/verify/coverage coverage_exceptions); with partial_data: true it was searched, and an empty result for it is not a finding of no hail. One of: before_archive, after_watermark, ingest_gap, coverage_exception.
uncovered_ranges[].partial_data boolean — Rows exist for these days, but not the whole day. True only when the record holds at least one row for every day of the range: the range WAS searched (events on it are returned) but is not provably complete (an edge day of the record, a day the scanner heartbeat does not attest complete, or a known dark day that holds some rows) so an empty result for it is not a finding. False means the record holds NO row for these days (including a known dark day the writer recorded nothing for): they were not searched, and reason says why they are missing.
message string · nullable — Human-readable statement of what is missing, as ONE string of short sentences, each at most ten words as printed (dates counted). Null when covered is full. The caveats come first: how many days the radar record fully covers (or, when radar_searched is false, that it was not consulted and only ground reports were searched), that an empty result does not mean no hail occurred, and whether ground reports were searched for every day. Then one sentence per uncovered range ("2026-05-11 to 2026-05-13: searched, not fully recorded yet."), days searched on a partial record before days with no record. A shared report page keeps at most 600 characters: it cuts between sentences, never inside a date, says how many ranges it left out ("3 more ranges are not shown."), and keeps the caveat that an empty result does not mean no hail fell, so a long range list may be shortened and the caveat may not. Use uncovered_ranges for the days. Do not parse.

coverage: Service-area coverage

Coverage verdict of a route clipped to the US boundary. in_service_area is false when no part of the footprint is observable (outside the continental-US radar domain or wholly outside the US); fully_in_service_area is false when any part of it is (crossing the domain edge or the US border). An empty result is a statement about hail only for the observable part. Pure geometry against the same boundary the query binds: no query, no cost.

FieldTypeUnitMeaning
in_service_area boolean — True when any part of the request footprint is inside the contiguous US.
fully_in_service_area boolean — True when the whole request footprint is inside the contiguous US.
message string — Present only when either flag is false.
see_also string — Present only when either flag is false.

spatial_clip: Spatial clip

What a hail output was clipped to. Detections outside the contiguous US (Canada, Mexico, Alaska, Hawaii, US territories, open water beyond state waters) are excluded, and polygons are trimmed at the border.

FieldTypeUnitMeaning
clipped_to string — us_ground only on GET /v1/ground-reports when the search area is on Alaska, Hawaii or a US territory (request_area: noncontiguous_us). One of: us_boundary, us_ground.
description string — Plain-language description of the block.
source string — The boundary's source and simplification tolerance.
request_area string — Where THIS request's footprint (its bbox, or its point + radius disc) fell against the boundary. partly_outside_us: results cover only the US part, so an empty result in the rest is a coverage gap, not evidence that nothing occurred. outside_us and outside_contiguous_us appear only on the outside_service_area refusal. outside_contiguous_us: the footprint is wholly outside the contiguous US and lies in, or within its own radius plus about half a kilometre of, a bounding shape held for Alaska, Hawaii (including the Northwestern Hawaiian Islands), Puerto Rico, the U.S. Virgin Islands, Guam, American Samoa, the Northern Mariana Islands or a U.S. Minor Outlying Island (Midway, Wake, ...), none of which this data covers (refused the same way, with a message naming the contiguous-US clip). Those shapes are generous over sea and can reach a little neighbouring foreign ground, so the value means "outside the contiguous US, on or near US territory", never "this place is US ground". outside_us: the footprint reaches none of them (Toronto, Paris, Whitehorse, Haida Gwaii and the British Virgin Islands are outside_us for a point or a small disc; a larger disc that reaches a shape is not). A state= request is within_us (the state is US ground) unless the state lies wholly outside the contiguous US (AK, HI), which is refused as outside_contiguous_us. GET /v1/wind/reports answers Alaska, Hawaii and the territories, so it never sends outside_contiguous_us. noncontiguous_us appears only on a 200 from GET /v1/ground-reports: the search area lies on Alaska, Hawaii or a US territory, whose ground reports are held and searched (clipped to that US ground, not to the contiguous boundary). One of: within_us, partly_outside_us, outside_us, outside_contiguous_us, noncontiguous_us.

ml_provenance: Model provenance

Scoring provenance of the radar hail detections this response used: the same block /v1/storms returns as ml_provenance. null when no radar detection was used (an empty result, or a day answered from ground reports alone). fit_regime is the relationship between each detection's storm date and the train/val/test split of the model that scored it: train means its confidence is an IN-SAMPLE score (true of most 2022-07..2025-06 dates), so in_sample is true and cross-era aggregates should be stratified on these fields; beyond is after the model's training data (normal live data); buffer is a gap day between splits; before precedes the training data; unknown means the scoring model has no published split (older generations). Disclosure only: it never changes which detections are returned or any verdict.

FieldTypeUnitMeaning
model_versions array — Public model identities (e.g. anvil-detect-6) that scored the detections used, never internal run IDs. A model without a public identity is omitted.
fit_regime string — The one regime every detection shares, or mixed. One of: train, val, test, buffer, beyond, before, unknown, mixed.
fit_regimes array — Every distinct regime the detections span, in split order. One of: before, train, buffer, val, test, beyond, unknown.
in_sample boolean — True when ANY detection used is in its model's training split.
feature_schema_versions array · nullable — Always null here: the radar hail detection record does not store the storm-segmentation generation (see /v1/storms for it).

Export file columns

Bulk exports (POST /v1/exports) are CSV, GeoJSON or Parquet. CSV files open with # comment lines (any CoCoRaHS credit, ml_provenance, data_version) before the header row; GeoJSON and Parquet carry the same blocks as a top-level member and as key-value metadata. An empty CSV cell is a null.

detections

ColumnTypeUnitMeaning
scan_time string — UTC time of the radar scan (ISO 8601).
storm_date string — Storm date (12:00 to 12:00 UTC, named for the day it starts).
lat double degrees (WGS 84) Pixel centre latitude, decimal degrees (WGS 84).
lon double degrees (WGS 84) Pixel centre longitude, decimal degrees (WGS 84).
geohash5 string — Geohash of the pixel, 5 characters (about 4.9 km cells).
mesh_mm double mm Radar estimate of the largest hail aloft at the pixel, millimetres (uncalibrated; leans high against ground reports).
ml_probability double — Hail-detection model probability, 0–1, on the scale of the row's own model generation. Never null in an export.
model_version string — Public id of the model generation that scored the row (e.g. anvil-detect-6); empty when that generation has no public id.
detection_strength string — Display tier of ml_probability on the row's own model generation: marginal, moderate or strong. Prefer it over the raw probability, which reads low even for real hail. Empty when the generation cannot be resolved.
size_category string — Calibrated, model-gated size class (pea … softball). Empty when the row is below the detection threshold: no size estimate, never pea, and never to be back-filled from mesh_mm.

reports

ColumnTypeUnitMeaning
event_timestamp string — UTC time of the report (ISO 8601).
storm_date string — Storm date (12:00 to 12:00 UTC).
lat double degrees (WGS 84) Report latitude, decimal degrees (WGS 84).
lon double degrees (WGS 84) Report longitude, decimal degrees (WGS 84).
max_size_inches double in Largest hail reported, inches.
size_mm double mm max_size_inches in millimetres, one decimal.
source string — Report source (NWS/SPC storm reports, IEM Local Storm Reports, CoCoRaHS). CoCoRaHS rows carry a CC BY 3.0 credit in the file.
reporter_source string — Who filed the report, as published (e.g. trained spotter, CO-OP observer).
measurement_method string — measured or estimated, as published.

swaths

ColumnTypeUnitMeaning
storm_date string — Storm date (12:00 to 12:00 UTC).
threshold_mm double mm Tier floor: radar size estimate at least this many millimetres.
category string — Tier label (same vocabulary as size classes, but a radar-size threshold, not a calibrated size).
area_km2 double km² Footprint area of the tier, square kilometres, one decimal.
geometry_geojson string — Tier footprint as a GeoJSON polygon string (CSV/Parquet); the GeoJSON format carries it as the feature geometry.

Endpoint fields

Every field of each licensed endpoint's success response, as the API documentation defines it. Open an endpoint to see its fields.

GET /v1/hail/swaths Hail event-day footprint polygons, banded by size tier 39 fields
FieldTypeUnitMeaning
date string — Storm date the answer is for (YYYY-MM-DD; a storm day runs 12:00 to 12:00 UTC).
query object — The request parameters as the server read them.
query.bbox object — Bounding box as minLon,minLat,maxLon,maxLat, or null.
query.state string — Two-letter US state code, or null.
query.min_size_mm number mm Smallest swath tier requested, millimetres.
query.simplify_m number m Polygon simplification tolerance applied, metres.
active boolean — True while the storm day is still open (it closes at 12:00 UTC the next day); counts can still grow.
outcome string — The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422. One of: signal, no_signal, insufficient_coverage.
temporal_coverage object — See Temporal coverage.
ml_provenance object · nullable — See Model provenance.
n_detections integer — Radar hail detections counted once per scan and pixel location.
max_size_mm number mm Radar-derived MESH (Maximum Estimated Size of Hail), in millimetres; a size inferred from reflectivity ALOFT, in the storm above the ground, not a measurement of a stone that landed. Radar and ground figures can legitimately differ for the same storm. The response also carries size_basis, which states this machine-readably.
data_as_of string — The radar hail record's watermark: the newest provably complete day (temporal_coverage.data_through). Never the requested date echoed back.
tiers array — One polygon per size tier that has any area.
tiers[].threshold_mm number mm Tier floor: the radar size estimate (day maximum within 2.5 km) is at least this many millimetres.
tiers[].category string — Size class name for the tier (nickel 19 mm, quarter 25, golf_ball 38, tennis_ball 51, baseball 64, softball 76). One of: nickel, quarter, golf_ball, tennis_ball, baseball, softball.
tiers[].area_km2 number km² Area, square kilometres.
tiers[].geometry object — GeoJSON MultiPolygon.
geometry_validity_note string — How polygon validity was ensured (simplification and repair applied).
size_basis object — What a GET /v1/hail/swaths tier is: a threshold on the uncalibrated radar size field, with the measured agreement against ground reports.
size_basis.basis string — Which size field the tiers threshold: the uncalibrated radar estimate, not the calibrated size class.
size_basis.quantity string — The quantity the tiers threshold: the uncalibrated radar hail-size estimate.
size_basis.units string — Units of quantity (mm). One of: mm.
size_basis.calibrated boolean — Always false for swath tiers.
size_basis.tier_semantics string — What a tier means, as a sentence you can print beside it.
size_basis.compare_with string — How the tiers relate to size_category on the other hail endpoints.
size_basis.tier_agreement object — Swath tier at each deduplicated SPC/IEM ground report (>= 0.75 in) compared with the reported size class, over window. Fractions of n_reports; mean_bias_mm is radar minus report.
size_basis.tier_agreement.method string — How agreement with ground reports was measured.
size_basis.tier_agreement.window object — Storm dates the agreement was measured over.
size_basis.tier_agreement.window.start string — First day of the range (YYYY-MM-DD, inclusive).
size_basis.tier_agreement.window.end string — Last day of the range (YYYY-MM-DD, inclusive).
size_basis.tier_agreement.n_reports integer — De-duplicated ground hail reports (≥0.75 in) scored.
size_basis.tier_agreement.exact number — Fraction of n_reports whose swath tier equals the reported size class.
size_basis.tier_agreement.over number — Fraction of n_reports whose swath tier is above the reported size class.
size_basis.tier_agreement.under number — Fraction of n_reports whose swath tier is below the reported size class.
size_basis.tier_agreement.within_one_tier number — Fraction of n_reports within one size class of the report.
size_basis.tier_agreement.mean_bias_mm number mm Mean of radar minus reported size, millimetres (positive = radar higher).
coverage object — See Service-area coverage.
spatial_clip object — See Spatial clip.
GET /v1/hail/history Radar-detection hail history (short record) 76 fields
FieldTypeUnitMeaning
coverage object — See Service-area coverage.
events array — Qualifying hail events in the window: detections passing min_confidence and min_size_mm. Empty is not "no hail": read outcome.
events[].hail object — The event's hail detail: size, confidence and model generation.
events[].hail.estimated_size_mm number mm Peak model size estimate (mm) for the cell on that storm day.
events[].hail.size_category string · nullable — **Calibrated and ML-gated, not raw MESH**, and NOT a bucket of estimated_size_mm: see GET /v1/hail/current's max_size_category for the full semantics. null when the cell is below the hail-detection confidence threshold. Endpoint-specific note: this endpoint reads back up to 365 days, spanning several detection-model generations whose probability scales differ. The size gate is applied PER GENERATION (each cell is gated at the detection threshold the model that scored it actually served under) so the question "did this system assert hail here?" is the same one across the whole window. model_version on this object names the generation. One of: pea, nickel, quarter, golf_ball, tennis_ball, baseball, softball.
events[].hail.size_method string · nullable — Which method set size_category for this cell: model (the trained size model's median) or radar_band (the empirical radar-to-report size bands). null exactly when size_category is. The same size_category value can come from either method depending on the storm day, so read this before comparing classes across eras. See the *Size method* note in this API's description for the measured era boundary (storm days from 2026-07-01). One of: model, radar_band.
events[].hail.detection_strength string · nullable — Display tier of confidence, tiered on the model generation that scored it: marginal (cleared the hail-detection threshold), moderate (cleared the default history filter), strong (cleared the bar at which radar alone can confirm hail). null below the detection threshold or when no model probability is available. Prefer this word over the raw probability for display: the probability is a calibrated chance that a 0.75 in+ hail report is filed within 15 km, so it reads low (often 10 to 40%) even for real hail. The bars move with each model promotion; the meaning of each word does not. One of: marginal, moderate, strong.
events[].hail.detection_strength_rank integer · nullable — detection_strength as 1 (marginal), 2 (moderate) or 3 (strong), for sorting and filtering. Null with it.
events[].hail.confidence number · nullable — Peak Anvil hail probability for the cell on that storm day: P(hail >= 0.75" / 19.05 mm within 15 km of the cell), the SAME quantity ml_probability / ml_confidence carry elsewhere in this API. It is NOT a measure of how certain the system is that the event happened; a well-calibrated 0.29 means roughly a 29% chance of hail, not "29% sure". SCALE-BOUND across this endpoint's up-to-365-day window: stored probabilities are never rescored, so a value is only comparable to another value from the SAME generation. model_version on this object is the field that names the scale: compare only within it.
events[].hail.model_version string · nullable — Public identity of the Anvil detect model that produced this cell's confidence, as anvil-detect-{generation}. The generation counter increments once per promotion, so a change here means the probability is on a new model and is NOT comparable to a value carrying a different version. Because stored rows are never rescored, one response covering a long window can legitimately carry several. null when the scoring model has no published public version.
count integer — Number of events. Read outcome, not this.
daily_peaks array — One entry per storm day with any radar detection inside radius_km, newest first: that day's largest radar size estimate over EVERY detection in the window and radius that the hail-detection model flagged at its own detection threshold (ENTER rung); no hail output serves a detection below it, so a day's peak never comes from one. It is computed before min_confidence and min_size_mm are applied and is not limited by limit, so it can be larger than every events[].hail.estimated_size_mm for the same day (a day's largest estimate can sit on a detection whose own confidence is under the filter, or in a cell outside the returned rows). Same source, same size field and same admission as POST /v1/verify events[].max_size_mm: for a storm day wholly inside both windows, with verify's 5-mile search area inside radius_km (radius_km >= 8.05), estimated_size_mm here is never smaller than verify's. A storm day cut by the start or end of the window covers only the scans inside it. An estimate somewhere in the radius, never at the queried point. Always present; empty when the window holds no detection.
daily_peaks[].storm_date string — Storm date (YYYY-MM-DD; a storm day runs 12:00 to 12:00 UTC and is named for the day it starts).
daily_peaks[].estimated_size_mm number mm Largest radar size estimate (mm, 0.1 mm) among all detections within radius_km that storm day at or above the detection threshold (ENTER rung). A size inferred by radar aloft, not a measured stone: see size_basis.
daily_peaks[].distance_km number · nullable km Distance from the queried point to the centre of the ~1 km radar grid cell that carried estimated_size_mm.
daily_peaks[].location object — Centre of that grid cell.
daily_peaks[].location.lat number · nullable degrees (WGS 84) Latitude, decimal degrees (WGS 84).
daily_peaks[].location.lon number · nullable degrees (WGS 84) Longitude, decimal degrees (WGS 84).
daily_peaks[].detection_strength string · nullable — Display tier of confidence, tiered on the model generation that scored it: marginal (cleared the hail-detection threshold), moderate (cleared the default history filter), strong (cleared the bar at which radar alone can confirm hail). null below the detection threshold or when no model probability is available. Prefer this word over the raw probability for display: the probability is a calibrated chance that a 0.75 in+ hail report is filed within 15 km, so it reads low (often 10 to 40%) even for real hail. The bars move with each model promotion; the meaning of each word does not. One of: marginal, moderate, strong.
daily_peaks[].detection_strength_rank integer · nullable — detection_strength as 1 (marginal), 2 (moderate) or 3 (strong), for sorting and filtering. Null with it.
daily_peaks[].confidence number · nullable — Anvil hail probability of the detection that carried the peak: the same scale-bound quantity as events[].hail.confidence, for that one detection (not the day's highest confidence).
daily_peaks[].model_version string · nullable — Public identity of the model confidence is on.
daily_peaks[].meets_min_confidence boolean — Whether the peak detection itself passes the min_confidence filter applied to events. false means the day's largest estimate is NOT among events.
daily_peaks[].filtered_estimated_size_mm number · nullable mm Largest estimate that storm day among detections that pass min_confidence, when it also meets min_size_mm; null when none does. This is the most events can show for the day, and it is independent of limit.
daily_peaks[].detection_count integer — Detection records (radar grid cell x scan) within radius_km that storm day at or above the detection threshold (ENTER rung), before min_confidence and min_size_mm.
daily_peaks[].below_bar_estimated_size_mm number · nullable mm Largest radar size estimate that storm day among detections that MISS min_confidence but that the hail-detection model still flagged at its own detection threshold (the generation's detect ENTER rung, the gate alerts use), and that meet min_size_mm. null when there are none. A size estimate, not a hail chance.
daily_peaks[].below_bar_detection_count integer — Detection records counted the same way (radar grid cell x scan). Non-zero on any day with events empty makes outcome possible_signal.
daily_peaks_basis string — Plain-language statement of what daily_peaks is and how it differs from events, carried in the response.
temporal_coverage object — See Temporal coverage.
outcome string — The window-level answer of GET /v1/hail/history (both scopes). READ THIS, NOT count. signal: at least one event: a detection that passes min_confidence (the ML confidence bar) and min_size_mm. report_signal: no event, but a vetted ground hail report (SPC storm reports, NWS Local Storm Reports, CoCoRaHS; see ground_reports) meeting min_size_mm lies inside the radius on a storm day of the window. The evidence is the report, not radar: the radar-only answer is in radar_outcome (typically no_signal or possible_signal). It takes precedence over possible_signal and over any coverage state. Radar can miss hail a person saw: storms far from the nearest radar, where the beam passes kilometres above the hail; storms the hail-detection model scores weak; and reports whose filed time or place is off from the storm. possible_signal: no event, but the radius holds detections below the confidence bar that the hail-detection model still flagged at its own detection threshold (daily_peaks[].below_bar_detection_count > 0). It means "radar saw hail-like echoes the model rates as weak here": worth a look, not a confirmed hail signal, and not a statement of how likely hail was or how large. Measured on a sample of populated point-days over 2022-07..2026-10, a possible_signal window held a ground hail report in the radius about 7% of the time at 5 mi and 6% at 15 mi (small samples: 43 and 66 windows), against 17% and 36% for signal and about 1% for a random point-day. Detections the model did not flag at its detection threshold never count, so no_signal still means nothing the model calls hail was seen. no_signal: neither, AND every day of the window is on record (temporal_coverage.covered: full). insufficient_coverage: neither, but part of the window is not on record; never "no hail". Setting min_confidence at or below the detection threshold leaves no room between the two, so possible_signal cannot occur. signal, possible_signal, no_signal and insufficient_coverage describe the radar record only; ground reports change the answer only to report_signal, never to signal. One of: signal, report_signal, possible_signal, no_signal, insufficient_coverage.
radar_outcome string — The radar-only answer: outcome computed without ground reports. Equal to outcome unless outcome is report_signal, when it says what the radar record alone shows for the window. One of: signal, possible_signal, no_signal, insufficient_coverage.
ground_reports object — Vetted ground hail reports inside radius_km on storm days (12Z-12Z) of the window, from SPC storm reports, NWS Local Storm Reports (via IEM) and CoCoRaHS observers; never user-submitted reports. Only reports with a recorded size of more than 0 that meets min_size_mm, never a "no hail" observation, inside the same US clip as the radar answer. A report is a person's observation: its size is the observer's, its time and place are as filed, and it is never a radar measurement. One National Weather Service report relayed by both the SPC and LSR feeds is counted once (sources lists both).
ground_reports.outcome string — report_signal when at least one vetted report lies in the radius on a storm day of the window, else no_report. About reports only; the window answer is outcome on the response. One of: report_signal, no_report.
ground_reports.count integer — Distinct reports over the window, after cross-feed duplicates are counted once.
ground_reports.filed integer — Raw filings over the window, before duplicates are merged.
ground_reports.max_size_mm number · nullable mm Largest reported hail size over the window, mm. null with no report.
ground_reports.truncated boolean — true when more than 2,000 filings matched and the rest were not read: counts are then lower bounds. Narrow the window or the radius.
ground_reports.basis string — Plain-language statement of what ground_reports is, carried in the response.
ground_reports.days array — One entry per storm day with a report, newest first.
ground_reports.days[].storm_date string — Storm date (YYYY-MM-DD; a storm day runs 12:00 to 12:00 UTC and is named for the day it starts).
ground_reports.days[].report_count integer — Distinct reports that day.
ground_reports.days[].filed_count integer — Raw filings that day.
ground_reports.days[].max_size_mm number · nullable mm Largest reported hail size that storm day, mm (the observer's, not radar).
ground_reports.days[].nearest_distance_km number · nullable km Distance from the queried point to the nearest report that day.
ground_reports.days[].reports array — The day's distinct reports, nearest first, at most 10.
ground_reports.days[].reports[].size_mm number · nullable mm Reported hail size, mm (the observer's, not radar).
ground_reports.days[].reports[].size_in number · nullable — The same size in inches.
ground_reports.days[].reports[].distance_km number · nullable km Distance from the queried point to the report's filed location, km.
ground_reports.days[].reports[].event_time string · nullable — The time the report was filed for, as filed.
ground_reports.days[].reports[].location object — The point the answer is for.
ground_reports.days[].reports[].location.lat number degrees (WGS 84) Latitude, decimal degrees (WGS 84).
ground_reports.days[].reports[].location.lon number degrees (WGS 84) Longitude, decimal degrees (WGS 84).
ground_reports.days[].reports[].source string — Warehouse source of the nearest filing (spc_storm_reports, iem_lsr, cocorahs).
ground_reports.days[].reports[].source_label string — The source in words.
ground_reports.days[].reports[].sources array — Every feed that carried this report.
spatial_clip object — See Spatial clip.
ml_provenance object · nullable — See Model provenance.
detail string — Detail level of the response (standard or advanced). One of: standard, advanced.
query object — The parameters actually applied. days is the window's day count whichever way it was given; start_date/end_date are the storm days (12Z-12Z) of an explicit window. For a trailing days window, they are the storm days holding now minus days and now.
spans array — Only with scope=address_history. The window cut where the record is, oldest first, covering every day of it exactly once.
spans[].start string — First day of the range (YYYY-MM-DD, inclusive).
spans[].end string — Last day of the range (YYYY-MM-DD, inclusive).
spans[].days integer — Number of days in the range.
spans[].covered boolean — false = the record does not provably hold these days completely. Searched only when partial_data is true; an empty result there is not a finding of no hail.
spans[].reason string — Present when covered is false. Same values as VerificationTemporalCoverage.uncovered_ranges[].reason. One of: before_archive, after_watermark, ingest_gap, coverage_exception.
spans[].partial_data boolean — Present when covered is false. true = the days hold some rows but are not provably complete: the span was searched and carries chunks, events, daily_peaks, max_size_mm and truncated like a covered span. false = the days hold no rows: the span was not queried and carries none of those keys.
spans[].chunks integer — Present when the span was searched (covered or partial_data true). Queries this span ran.
spans[].events array — Present when the span was searched (covered or partial_data true); the same items as the top-level events, for this span's days, newest first.
spans[].daily_peaks array — Present when the span was searched; the same items as the top-level daily_peaks.
spans[].max_size_mm number · nullable mm Present when the span was searched. Largest daily_peaks estimate in the span; null when it holds none.
spans[].truncated boolean — Present when the span was searched. A chunk filled limit, so events may be cut short; daily_peaks is not.
chunks integer — Only with scope=address_history. Queries run across all searched spans (covered and partial_data).
window object — Only with scope=address_history. The resolved window.
window.start_date string — First day of the window.
window.end_date string — Last day of the window.
window.days integer — Number of days in the range.
GET /v1/events Regional event-day summaries over a date range 19 fields
FieldTypeUnitMeaning
date_range object — The date window the answer covers.
area_method string — How affected area was computed. One of: convex_hull.
region object — The region filter as the server read it.
events array — One entry per storm date with radar hail detections in the region.
events[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
events[].cell_count integer — Distinct tracked storm objects (distinct track ids) with at least one counted detection that storm day. Not a count of per-scan objects or geohash cells.
events[].max_size_mm number mm Radar-derived MESH (Maximum Estimated Size of Hail), in millimetres; a size inferred from reflectivity ALOFT, in the storm above the ground, not a measurement of a stone that landed. Radar and ground figures can legitimately differ for the same storm. The response also carries size_basis, which states this machine-readably.
events[].n_detections integer — Radar hail detections counted once per scan and pixel location.
events[].affected_area_km2 number km² Footprint area of the day's detections, square kilometres.
events[].centroid object — Centroid of the day's footprint.
events[].centroid.lat number degrees (WGS 84) Latitude, decimal degrees (WGS 84).
events[].centroid.lon number degrees (WGS 84) Longitude, decimal degrees (WGS 84).
events[].swath_url string — Path to the day's hail swath polygons.
coverage object — See Service-area coverage.
spatial_clip object — See Spatial clip.
event_count integer — Number of entries in events.
outcome string — The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422. One of: signal, no_signal, insufficient_coverage.
temporal_coverage object — See Temporal coverage.
data_as_of string — The radar hail record's watermark: the newest provably complete day (temporal_coverage.data_through). Never the requested date echoed back.
POST /v1/verify Date-of-loss verification: per-event-day verdicts for a location 121 fields
FieldTypeUnitMeaning
location object — The point the answer is for.
date_range object — The date window the answer covers.
radius_mi number mi Search radius, miles.
methodology object — How this verdict was computed; versioned with the API.
methodology.version string — The verdict RULE's version, not the model's. Bumps whenever the rule or its bars change, so a previously-issued verdict stays reproducible under its own stamp. A model promotion that moves a bar therefore bumps this too.
methodology.description string — Plain-language description of the block.
methodology.mesh_min_mm number mm The mesh leg of the strong-uncorroborated-detection rule (mm).
methodology.ml_confidence_bars array — The ML leg, PER MODEL GENERATION: the exact table classifyVerdict cuts against, keyed by public model version. A detection is "strong" when its neighbourhood peak probability reaches the bar for the generation that scored it. Historical bars sit ABOVE the current one: older scales ran hotter, so a single flat bar would over-admit hit on old claims.
methodology.ml_confidence_bars[].model_version string — Public detect-model identity, as anvil-detect-{generation}.
methodology.ml_confidence_bars[].min_ml_confidence number — Detection-model probability a detection must reach to count, for that model generation.
methodology.ml_confidence_bar_default number — Bar applied when a detection's scoring generation cannot be resolved. Deliberately the STRICTEST bar in the table, so an unresolvable row can never be wrong in the direction that admits a hit.
methodology.at_property_radius_mi number mi The radius the verdict's distance leg cuts at. Evidence with nothing inside it is nearby; inside it, a ground report or a strong detection makes the day hit and any other detection makes it detected. Published because a client that renders one of these as another is making a claim this API did not make.
methodology.bands_mi array mi Distance bands, miles, used to classify proximity.
detection_quality object — Same shape as GET /v1/coverage's detection_quality block. Null if the location has no HDQI coverage.
events array — One verdict per storm day with any signal within 5 miles. Days with no signal are absent, never a zero row.
events[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
events[].verdict string — hit: "Confirmed at the property". Hit-grade evidence AND, within methodology.at_property_radius_mi of the queried location, a ground report or a STRONG radar detection (at_property.strong_detection). detected: "Detected at the property, not counted". Added in verify-v5. The SAME hit-grade evidence, and a radar detection within that radius, but neither a strong detection nor a ground report within it: the detection at the location did not meet the standard that confirms a day, and the report or strong detection that documents the storm was farther away. Through verify-v4 these days returned hit. A detected day is not a hit and not confirmed. confirmed_by is null; the day's sentence is in basis_statements. nearby: the SAME hit-grade evidence, but its closest element was farther than that radius. The storm is real and documented; it did not pass over this location. Added in verify-v4: through verify-v3 these days returned hit, because the rule read the 5-mile search disc while consumers worded the answer as "hail at this address". A nearby day is not a hit: that is the exact defect this value exists to prevent. marginal: a weak/uncorroborated detection, OR a ground report the radar did not corroborate. Decided BEFORE distance: proximity never promotes weak evidence. miss: neither signal present. Not reachable in a response; such days are omitted from events entirely. One of: hit, detected, nearby, marginal, miss.
events[].confirmed_by string · nullable — Only on a hit day (null on every other verdict, detected included): what CONFIRMED the day inside methodology.at_property_radius_mi. radar: a strong radar detection; ground_report: a ground report, with no STRONG detection that close (there may be no radar detection inside the radius at all, so at_property.max_size_mm can be null, or a small one that is not what confirmed the day); both. Wording only: never an input to the verdict. A ground_report day does not mean nothing reached the location. Through verify-v4 radar meant a detection of any size. One of: radar, ground_report, both.
events[].band string — Null when no detection matched (report-only marginal case). One of: at_location, within_1mi, within_3mi, within_5mi.
events[].max_size_mm number mm Peak radar-estimated stone size anywhere in the 5-MILE SEARCH DISC: NOT at the queried location. Read it with proximity.peak_detection, which says how far away and in which direction it was measured; for the location itself use at_property.max_size_mm. Publishing this number without its offset is how a stone measured 3.3 miles away was presented as a property's own peak size.
events[].proximity object — WHERE the evidence sat relative to the queried location. Every other scalar on this event describes the 5-mile disc; this block is what stops a disc fact being read as a fact about the roof.
events[].proximity.at_property boolean — True when the closest evidence (radar OR ground report) was within at_property_radius_mi. It says WHERE, not that the day is confirmed: it is true on hit and on detected days (and on a marginal day whose weak evidence is close), false on nearby. Read verdict for the finding.
events[].proximity.at_property_radius_mi number mi Radius, miles, counted as at the property.
events[].proximity.nearest_detection object — Closest radar detection: lat, lon, distance_mi, bearing_deg and a 16-point direction (the way you would look FROM the location TO the evidence). Bearing and direction are null inside 0.05 mi, where a compass point would be noise. Null when the day had no detection.
events[].proximity.peak_detection object — Same shape, for the cell carrying max_size_mm.
events[].proximity.nearest_report object — distance_mi and direction to the closest ground report. Null when none was filed. This and nearest_detection.distance_mi / nearest_detection.direction are the per-day nearest-evidence distances: read them here (the verification report copies them to event_days[].nearest_ground_report_mi etc.). A ground report CONFIRMS a day only when confirmed_by is ground_report or both (it is inside at_property_radius_mi). Otherwise state its distance ("a ground report was also filed 3.06 mi S"): never "corroborated" and never "nearby". Radar distances are to the centre of a ~1 km radar grid cell: a radar distance under about 0.3 mi means the pin lies inside that cell.
events[].at_property object — The ONLY block describing the location itself: radar cells within at_property_radius_mi. Nulls here are a FINDING, not missing data: a null max_size_mm means nothing qualifying reached the location, which is the honest answer for a storm that passed to one side.
events[].at_property.max_size_mm number · nullable mm Peak radar-estimated stone size within proximity.at_property_radius_mi of the queried location. Null means nothing qualifying reached it: a finding, not a gap in the record.
events[].at_property.ml_confidence number · nullable — Peak Anvil hail probability among the detections within the at-property radius: P(hail >= 0.75" / 19.05 mm within 15 km), the SAME quantity ml_probability carries elsewhere in this API, and the same quantity as the event-level ml_confidence above. It is NOT a measure of how certain the system is that the event happened. SCALE-BOUND: comparable only within one detect model generation; the event's model_version names which. Null when no detection reached the property.
events[].at_property.strong_detection boolean — Added in verify-v5. True when a detection within the at-property radius cleared BOTH the strong size (methodology.mesh_min_mm) and its model generation's confidence bar: on the same detection (one grid cell in one scan). The radar half of what separates hit from detected. Not derivable from max_size_mm and ml_confidence above: those are separate maxima and can come from different detections.
events[].at_property.n_detections integer — Radar hail detections counted once per scan and pixel location.
events[].footprint array — The day's swath as drawable cells: one entry per distinct grid cell carrying that cell's largest estimate across every scan (lat, lon, mesh_mm). Capped at limits.footprint_cells_per_event; compare against footprint_cell_count to detect truncation, because a clipped footprint drawn on a map depicts a smaller storm than the one that happened.
events[].footprint_cell_count integer — Distinct cells the day actually had, before the cap.
events[].ml_confidence number — Peak Anvil hail probability over the matched detections: P(hail >= 0.75" / 19.05 mm within 15 km), the SAME quantity ml_probability carries elsewhere in this API. It is NOT a measure of how certain the system is that the event happened; a well-calibrated 0.29 means roughly a 29% chance of hail, not "29% sure". Named ml_confidence for historical reasons on this endpoint only. SCALE-BOUND: comparable only within one detect model generation; model_version below names which.
events[].model_version string · nullable — Public identity of the Anvil detect model that produced this event's ml_confidence, as anvil-detect-{generation}, and therefore the row of methodology.ml_confidence_bars this event's verdict was cut against. Stored rows are never rescored, so a verdict on an old storm day legitimately reports an older version than a recent one. null when the scoring model has no published public version.
events[].n_detections integer — Detection CELLS: one row per radar grid cell per scan with a qualifying signal in the search area. NOT a count of radar scans: a 42-minute storm can carry over a thousand of these across nine scans. The number of scans is scan_counts[].scans_with_hail. Unchanged and part of the hashed events.
events[].first_scan_time string · nullable — ⚠️ READ THIS BEFORE USING THE TIMESTAMP. This and its two siblings are radar scan times when a hail signal was present over the search area: here, the 5-mile disc around the location. They are NOT "when hail hit the property". first_scan_time is the valid time of the earliest MRMS scan in which a qualifying signal appeared ANYWHERE in that disc; hail may have reached this particular address later, earlier in a part of the storm we scored differently, or not at all. Null on a report-only day (no radar detection to time). ISO-8601 UTC.
events[].peak_scan_time string · nullable — Valid time of the scan carrying this day's LARGEST radar-estimated stone within the search area. Same caveat as first_scan_time: a radar scan time over the search area, NOT "when hail hit the property". Ties are broken toward the earlier scan so a day always reports the same instant.
events[].last_scan_time string · nullable — Valid time of the LAST scan in which a qualifying signal was present in the search area. Same caveat as first_scan_time: a radar scan time over the search area, NOT "when hail hit the property".
events[].track_ids array — Stable storm-track ids whose detections fell inside the search area on this day. Two event days sharing an id were produced by the same tracked storm system: see storm_groups, and read its warning before using this for anything contractual. Empty when the day's rows carry no track lineage (the column is nullable and older rows predate it): absence is unknown linkage, never evidence of a separate storm. Capped at limits.track_ids_per_event per day.
events[].wind object — 🔴 WIND CONTEXT: WHAT THE NATIONAL WEATHER SERVICE WARNED, AND NOTHING ELSE. Whether an NWS Severe Thunderstorm or Tornado Warning carrying a wind-threat tag covered this search area on this convective day. The source is the issued warning itself: a public, citable government act you can look up by warning_id, the same warnings GET /v1/warnings serves. It is NEVER a model-derived wind probability; this API does not publish one. Wind is EVIDENCE ATTACHED TO A VERDICT ALREADY REACHED. It is never an input to the hail verdict, and adding it did not move methodology.version. Three things this block does not say. (1) The absence of a warning is NOT the absence of wind: the NWS warns on forecast severity, not on observed damage, and damaging gusts are frequently never warned. (2) It makes no statement about what caused any damage: whether damage is hail damage or wind damage is not a determination this API makes, and nothing here ranks hail against wind as a cause. (3) It makes no statement about what a policy covers.
events[].wind.checked boolean — Whether we looked at all. Read this BEFORE warned. false means "we could not check", NOT "no wind threat was warned" and NOT "there was no wind": see reason.
events[].wind.warned boolean · nullable — True when at least one NWS warning carrying a wind-threat tag covered this search area on this convective day. False means we looked and found none: which is NOT the same as "there was no wind", because the absence of a warning is NOT the absence of wind. Always null when checked is false, so "we did not look" can never be read as "we looked and found nothing".
events[].wind.reason string — Present ONLY when checked is false. before_wind_data_era: this date precedes wind_context.data_era_start, the first convective day the NWS warning archive behind this API covers completely. wind_lookup_failed: the warning query failed; the hail verdict on this day is unaffected, because wind is never an input to it. One of: before_wind_data_era, wind_lookup_failed.
events[].wind.warning_count integer — Distinct covering WARNINGS: each warning's CAP messages (its issuance and every update/cancellation) count once, grouped by the warning's P-VTEC identity (office, phenomenon, event number, year) or, for messages archived before 2026-10-25 without VTEC, by issuing office + expiry + overlapping polygon. Absent when checked is false: a count of zero must never stand in for "we did not look". May exceed the length of warnings, which is capped at limits.wind_warnings_per_event.
events[].wind.threat_tags array — The NWS impact-based wind-threat tags carried by the covering warnings, VERBATIM and de-duplicated: in practice RADAR INDICATED or OBSERVED. There is deliberately NO gust-speed field: this is a tag column, not a measurement, and deriving a wind speed from it would publish precision the source does not carry.
events[].wind.warnings array — The covering warnings, oldest first, capped at limits.wind_warnings_per_event. Every entry is independently checkable against the National Weather Service by its warning_id. One entry per warning, described by its FIRST CAP message.
events[].wind.warnings[].warning_id string — The CAP @id of the warning's first (issuing) message.
events[].wind.warnings[].event string — Warning or watch type. One of: Severe Thunderstorm Warning, Tornado Warning.
events[].wind.warnings[].wind_threat string — The NWS impact-based wind-threat tag, verbatim.
events[].wind.warnings[].onset string — Warning onset / effective time, ISO-8601 UTC.
events[].wind.warnings[].expires string — Warning expiration time, ISO-8601 UTC.
events[].wind.note string — The honesty statement for this day's specific state, written to be rendered beside the value. Render it: it is what stops warned: false being read as "it was not windy", and what keeps checked: false from being read as a finding.
events[].impact object — HOW the stone was falling in the cell carrying the day's largest estimate: angle from vertical, and the bearing it came in from. Derived from RADAR CELL TRACKING (the parent storm's tracked motion and the stone's terminal velocity): never from a wind model, and never from a forecast field. NULL on a day with no radar geometry; a report-only day has no detection to take an angle from, and a zeroed shape would read as "it fell straight down". EVIDENCE ONLY: no verdict is decided by it and methodology.version does not move for it.
events[].impact.angle_deg number degrees Impact angle from vertical, degrees. 0 = straight down, 90 = fully horizontal. Computed at the PARENT STORM OBJECT and denormalised to its pixels, so it describes the cell over the search area rather than a measurement at any one structure.
events[].impact.storm_moving_toward_deg number degrees Azimuth the tracked cell was travelling TOWARD, degrees, 0 = north. This is a heading, NOT the meteorological "direction it comes from" convention used for wind.
events[].impact.storm_moving_toward string — 16-point compass form of storm_moving_toward_deg.
events[].impact.arrives_from_deg number degrees The bearing hail came IN FROM: storm_moving_toward_deg flipped 180 degrees, because the stone is carried with the cell and therefore strikes surfaces facing the opposite way. Published rather than left for the caller to derive: reading this backwards points an inspection at the wrong side of a building.
events[].impact.arrives_from string — 16-point compass form of arrives_from_deg.
events[].impact.exposed_elevations array — The building faces the storm's motion pointed hail at: the windward face and its two neighbours, since an oblique fall loads the corner as well as the flat side. An INSPECTION PRIORITY, never a finding that those elevations were damaged. EMPTY when the fall was near-vertical or the cell's motion was never tracked; no elevation is guessed.
events[].impact.near_vertical boolean — True when the fall was too close to vertical for any elevation to be preferentially exposed. Null when no angle was recorded.
events[].impact.note string — The sentence that must render wherever the geometry does. States that an exposed elevation is an inspection priority and not a damage finding, and that this describes the cell rather than the structure.
events[].ground_truth object — Ground hail reports matched to the event.
events[].ground_truth.corroborated boolean — ⚠️ NAMED FOR HISTORY, NOT FOR WHAT IT PROVES. True when at least one ground report was filed ANYWHERE in the search area on this day: up to radius_mi away. It does NOT mean a report corroborates hail at the location: on a hit day only confirmed_by says whether a report was inside the at-property radius. The distance to the nearest report is proximity.nearest_report. (The name cannot change: events is covered by the verification hash.)
events[].ground_truth.nearest_reports array — Closest ground reports first, capped at limits.reports_per_event. Each carries source, max_size_mm, lat, lon, distance_m, distance_mi, a 16-point direction, and event_time. Positions since verify-v4: a report list with distances but no direction cannot be drawn, and cannot answer the question customers actually ask; which way did it go? ⚠️ event_time is the ONLY timestamp on this response that is not a radar scan time: it is when the OBSERVER says the hail fell, at the report's own position. Null when the warehouse has no time for that report: a report you cannot place in time must not be given one. source LABELS (one map, lib/groundReportSources.js; the verification report PDF prints these, mirror them exactly): spc_storm_reports / spc → "SPC storm report"; iem_lsr → "Local storm report"; cocorahs → "CoCoRaHS volunteer observer"; user / app_user → "User-submitted report"; any other value → "Ground report (<source>)"; no source → "Ground report". Displaying a cocorahs report owes the CC BY 3.0 credit ("Data provided by the Community Collaborative Rain, Hail and Snow Network (CoCoRaHS), used under CC BY 3.0.").
temporal_coverage object — See Temporal coverage.
outcome string — The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422. One of: signal, no_signal, insufficient_coverage.
ml_provenance object · nullable — See Model provenance.
event_count integer — Number of entries in events.
reported_sizes array — The hail size the GROUND REPORTS themselves recorded, one entry per event day in events order. A report-only day has no radar size (events[].max_size_mm and events[].at_property.max_size_mm are null), so this is that day's only size: show it as a reported size ("reported 0.87 in, ground report"), never as a radar estimate and never as a size at the address. A SIBLING of events, deliberately not a key inside it: the verification report's verification_hash is sha256({params, events, methodology_version}) and is recomputable from this response's events.
reported_sizes[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
reported_sizes[].reported_max_size_mm number · nullable mm Largest size, mm (1 decimal), among EVERY ground report filed within radius_mi on that day: not only the ground_truth.nearest_reports listed, which is capped at limits.reports_per_event. Null when no report on the day recorded a size, and on a day with no report. A person's measurement at the report's own position.
ground_report_counts array — How many ground reports each event day really has, one entry per event day in events order. Use this for any count: never the length of ground_truth.nearest_reports, which is capped at limits.reports_per_event. observations.hail_reports carries one National Weather Service storm report twice (SPC's storm-report feed and the Local Storm Report feed both relay it), so filed over-counts observers. distinct counts a sized report carried by BOTH of those feeds, same coordinate to 2 decimals (about 1 km), within 10 minutes, same size, once. Nothing else is merged: CoCoRaHS and user-submitted reports are independent observers, and a report with no recorded size is never merged. Every raw filing is still listed in ground_truth.nearest_reports with its own source. Word it "N ground reports (M with a size); largest reported X" with N = distinct, M = distinct_with_size, X = reported_sizes[].reported_max_size_mm. A SIBLING of events, like reported_sizes: not part of the verification_hash. Point grain only.
ground_report_counts[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
ground_report_counts[].filed integer — Every ground report filed within radius_mi that day (a true count, not the listed length).
ground_report_counts[].filed_with_size integer — How many of filed recorded a hail size.
ground_report_counts[].distinct integer — filed with each report carried by both NWS relay feeds counted once.
ground_report_counts[].distinct_with_size integer — How many of distinct recorded a hail size.
ground_report_counts[].duplicates_merged integer — filed minus distinct.
ground_report_counts[].complete boolean — False only when more reports were filed than were examined for duplicates (250 per day); the unexamined ones are each counted, so distinct is then an upper bound: say "up to N".
scan_counts array — Radar SCANS versus detection CELLS, one entry per event day in events order. events[].n_detections counts cells (grid cell x scan rows); the number of radar scans with a hail signal in the search area is scans_with_hail. For example "Radar scans with a hail signal: 9" beside "detection cells: 1,141"; n_detections is not a scan count. A SIBLING of events (hashed), like reported_sizes.
scan_counts[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
scan_counts[].scans_with_hail integer · nullable — Distinct radar scan times carrying a qualifying hail detection inside the search area that day. Null on a report-only day (no radar rows): not zero.
scan_counts[].detection_cells integer — The day's events[].n_detections, repeated so both counts can be shown under their correct names.
basis_statements array — Added in verify-v5. Each event day's customer-facing label and the sentence its verdict rests on, one entry per event day in events order. Server-authored so a client never words a basis itself: print verdict_label, and where basis_statement is present print it beside the label. Point grain only. A SIBLING of events (not hashed).
basis_statements[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
basis_statements[].verdict_label string — One of "Confirmed at the property", "Confirmed at the property: by ground report", "Detected at the property, not counted", "Nearby, not overhead", "Weak signal", "Reported, not radar-confirmed", "Reported, radar data not available".
basis_statements[].basis_statement string · nullable — On a hit day: "confirmed by ...": which evidence was inside the at-property radius with its distance, anything outside it as a separate sentence, and the at-address radar estimate. On a detected day: "radar detected hail 0.09 mi NW. That detection is inside the 0.5 mi at-property radius. Radar's largest estimate there was 0.33" (8.5 mm). That is below 25 mm, not strong on its own. No ground report was filed inside that radius. The nearest was 3.1 mi S. Distances are measured from the pin placed for this address.": never the word "confirmed". The size sentence names the true shortfall, decided on the size as printed: "That is below 25 mm, not strong on its own." (under the strong size), "It lacked the model confidence to be strong." (the size reached it), or "That is within rounding of 25 mm. It fell short on size or model confidence." (a printed 25.0; the unrounded size may be either side). Null on every other verdict. Starts lower-case so it can follow "Basis: ". Distances are measured from "the pin placed for this address" unless subject_precision is rooftop, which says "the address". The same string a report filed for this response stores in event_days[].basis_statement.
evidence_kinds array — What each event day's CLOSEST evidence was, one entry per event day in events order, for every verdict (not only confirmed days: events[].confirmed_by is null elsewhere). Derived from events[].proximity; the nearer of the closest radar detection and the closest ground report, a tie going to radar. Use this, never ground_truth.corroborated (true for a report filed ANYWHERE in the search area), to say what a day's evidence basis was. Point grain only. A SIBLING of events.
evidence_kinds[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
evidence_kinds[].closest_kind string · nullable — Kind of the closest evidence; null when the day has neither. One of: radar, ground_report.
evidence_kinds[].has_radar boolean — A radar detection of any size was inside the search area that day.
evidence_kinds[].has_ground_report boolean — A ground report was filed inside the search area that day.
charged boolean — Whether this answer was billed. true on every answered (2xx) live request, INCLUDING one whose outcome is insufficient_coverage on a window the radar record was searched for: a partly-covered window that was searched and returned nothing is an answer and costs the same as any other. The refusals (422, 503) carry charged: false. false on a 2xx for a caller that is not metered, for the sandbox fixture, and for an insufficient_coverage answer on a window the radar record holds nothing for (temporal_coverage.radar_searched: false: only ground reports were searched and none was found); that answer is free (X-Credits-Cost: 0); one that found a ground report (signal) is charged. An Idempotent-Replay: true response repeats the original body and is not charged again.
credits integer — Credits charged for this answer: the same number as the X-Credits-Cost response header. 0 when charged is false.
storm_groups array — 🔴 A METEOROLOGICAL FACT, NOT AN INSURANCE DETERMINATION. Two event dates sharing a track id were produced by the same tracked storm system. This is a meteorological fact, NOT an occurrence determination under any insurance policy: occurrence is a contractual test this API does not and cannot apply, because its definition (72-hour clauses and the like) lives in the policy, not in the weather. Grouping is by CONNECTED COMPONENT: two dates land in one group when they share a track directly, or transitively through another date in the same group. Do not read a group as proof that every pair inside it shares a track. Event days whose detections carry no track lineage are absent entirely.
storm_groups[].track_ids array — Every distinct track id appearing on the group's dates.
storm_groups[].event_dates array — The event dates this group links, ascending.
wind_context object — What the per-event-day wind blocks are, what geometry they were scoped to, and (most importantly) what they do not say. Present on every verification response, including one with no event days.
wind_context.source string — Always NWS warned threat.
wind_context.source_detail string — The exact provenance: impact-based wind-threat tags on National Weather Service Severe Thunderstorm and Tornado Warnings.
wind_context.scope string — The search geometry the warnings were matched against, in words. It is the SAME geometry the hail verdict searched at this grain, never widened to make more warnings match.
wind_context.convective_day string — How a warning is dated. A warning is filed under the convective day (12Z-12Z UTC) of its onset: the same convention event_date uses.
wind_context.data_era_start string — The first convective day the NWS warning archive behind this API covers completely. An event date before this returns checked: false with before_wind_data_era: we did not look, which is NOT a finding that no wind threat was warned.
wind_context.honesty string — The statement of what this feature refuses to say: warned: false does NOT mean there was no wind, checked: false is a different state again, and nothing here is a determination of what caused any damage, of whether damage is hail damage or wind damage, or of what a policy covers.
impact_context object — What events[].impact is, and what it is not. Read before using it.
impact_context.source string — Always "radar storm tracking".
impact_context.source_detail string — Names the derivation and states explicitly that no wind model is involved.
impact_context.bearing_convention string — The toward-vs-from distinction, spelled out, because inverting it inverts the answer.
impact_context.honesty string — The refusal: this is the geometry of the fall in the cell over the search area, NOT a measurement at the structure and NOT a statement that any elevation was damaged.
limits object — Published caps on the bounded evidence arrays.
limits.track_ids_per_event integer — Maximum distinct track ids returned per event day.
limits.wind_warnings_per_event integer — Maximum warnings listed in events[].wind.warnings per day. wind.warning_count is the true total and is not capped.
coverage object — Whether this point is inside the CONUS radar domain at all. READ THIS BEFORE READING events. Days with no signal are omitted from events, so an empty array is ambiguous on its own: it means "no hail signal here" when in_service_area: true, and "we cannot observe this point" when in_service_area: false. An empty events is not a clean bill of health until this field is checked: an out-of-domain address has no verdict, not a miss.
coverage.in_service_area boolean — True when any part of the request footprint is inside the contiguous US.
coverage.message string — Present only when in_service_area is false: states what the empty result does and does not mean.
coverage.see_also string — Where to read more about the service area.
coverage.quality string — HOW WELL we can observe here, where in_service_area only says WHETHER. The HDQI band for this cell (radar sample coverage, beam blockage, dual-pol availability, mosaic overlap): the same band GET /v1/coverage and detection_quality.detection_word report. unknown means no HDQI cell resolved for this location, not that coverage is good. One of: excellent, good, fair, limited, unknown.
coverage.quality_hdqi number · nullable — The 0-1 HDQI score behind quality, rounded to 3 decimal places. Null when no cell resolved. DISPLAY PRECISION: 3 decimals, exactly as returned (0.995 prints "0.995"); never rounded or truncated to 2 (the verification report PDF prints the same 3-decimal value as detection_quality.hdqi).
coverage.quality_caveat string · nullable — Non-null when quality is fair, limited or unknown. ONE-SIDED ON PURPOSE: degraded radar geometry can leave real hail unobserved, so it makes a NEGATIVE result (a miss, or a day absent from events) weaker evidence here than the same result in a well-covered cell. It does NOT make a POSITIVE result wrong: hail detected here was detected. Null when coverage is good or excellent.
data_as_of string — The hail record's real watermark: the newest convective day that is provably complete (temporal_coverage.data_through). NOT an echo of the request's date_end: a window ending today reports an earlier date here.
POST /v1/verify/area Territory-grain verification: per-event-day verdicts for a monitored area 70 fields
FieldTypeUnitMeaning
area object — The territory as stored: id, name, kind, purpose, area_sq_mi, centroid, bbox.
grain string — "area" for a polygon (boundary cut, no radius). "point" for a point-kind area, whose answer is the 5-mile-disc answer from POST /v1/verify: read it as a point result, not a boundary one. One of: area, point.
date_range object — The date window the answer covers.
coverage object — Read this before events. in_service_area is false when no part of the territory is inside CONUS radar coverage; fully_covered is false when only part of it is.
coverage.in_service_area boolean — True when any part of the request footprint is inside the contiguous US.
coverage.fully_covered boolean — False when only part of the territory is inside contiguous-US radar coverage.
coverage.message string — Human-readable text. Wording may change; never parse it.
coverage.see_also string — Where to read more about the service area.
coverage.quality string — HOW WELL we can observe, where in_service_area / fully_covered only say WHETHER. Measured at the territory CENTROID (the same point detection_quality describes) so a large or elongated boundary can contain cells graded differently from this one. One of: excellent, good, fair, limited, unknown.
coverage.quality_hdqi number · nullable — The 0-1 HDQI score behind quality, rounded to 3 decimal places. Null when no cell resolved. DISPLAY PRECISION: 3 decimals, exactly as returned (0.995 prints "0.995"); never rounded or truncated to 2 (the verification report PDF prints the same 3-decimal value as detection_quality.hdqi).
coverage.quality_caveat string · nullable — Non-null when quality is fair, limited or unknown. ONE-SIDED: degraded radar geometry can leave real hail unobserved, so a NEGATIVE result is weaker evidence here than in a well-covered cell. It does NOT make a POSITIVE result wrong.
methodology object — Shared with POST /v1/verify: same version, same per-generation ML bars, plus grain=area.
detection_quality object — Radar geometry at the territory centroid. Null if the centroid has no HDQI coverage.
honesty string — States the areal claim this endpoint refuses to make, and why.
events array — One verdict per storm day with a signal inside the territory. Days with no signal are absent.
events[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
events[].verdict string — For grain: area (a polygon). A boundary has no at-property radius, so hit-grade evidence inside it is hit; nearby and detected are never returned and verify-v5 changed nothing at this grain. For grain: point (a point-kind area) the events are the point grain's own: verdict is one of hit, detected, nearby, marginal, and the response also carries basis_statements; see POST /v1/verify. One of: hit, marginal, miss.
events[].max_size_mm number mm Peak radar-estimated stone size inside the boundary that day.
events[].detection_strength string · nullable — Display tier of ml_confidence, tiered on the model generation that scored it: marginal (cleared the hail-detection threshold), moderate (cleared the default history filter), strong (cleared the bar at which radar alone can confirm hail). null below the detection threshold or when no model probability is available. Prefer this word over the raw probability for display: the probability is a calibrated chance that a 0.75 in+ hail report is filed within 15 km, so it reads low (often 10 to 40%) even for real hail. The bars move with each model promotion; the meaning of each word does not. One of: marginal, moderate, strong.
events[].detection_strength_rank integer · nullable — detection_strength as 1 (marginal), 2 (moderate) or 3 (strong), for sorting and filtering. Null with it.
events[].ml_confidence number · nullable — Peak Anvil hail probability over the detections INSIDE the boundary: P(hail >= 0.75" / 19.05 mm within 15 km), the SAME quantity ml_probability carries elsewhere in this API. It is NOT a measure of how certain the system is that the event happened; a well-calibrated 0.29 means roughly a 29% chance of hail, not "29% sure". Named ml_confidence to match the point-grain endpoint. SCALE-BOUND: comparable only within one detect model generation; model_version below names which.
events[].model_version string · nullable — Public identity of the Anvil detect model that produced this event's ml_confidence, as anvil-detect-{generation}, and therefore the row of methodology.ml_confidence_bars that number must be read against. Never an internal run ID; null when the generation has no registered public identity.
events[].n_detections integer — Detection CELLS (grid cell x scan rows) inside the boundary. NOT radar scans (see scan_counts) and NOT an area measurement.
events[].peak_at object — Where the day's largest estimate fell, so a crew can be sent to the right corner.
events[].first_scan_time string · nullable — ⚠️ READ THIS BEFORE USING THE TIMESTAMP. This and its two siblings are radar scan times when a hail signal was present over the search area: here, anywhere inside the territory boundary. They are NOT "when hail hit the property". On a large polygon the instant recorded here can belong to a corner miles from any given parcel. Null on a report-only day. ISO-8601 UTC.
events[].peak_scan_time string · nullable — Valid time of the scan carrying the day's LARGEST estimate inside the boundary: the same scan peak_at locates. Same caveat: a radar scan time over the search area, NOT "when hail hit the property".
events[].last_scan_time string · nullable — Valid time of the LAST scan with a qualifying signal inside the boundary. Same caveat: a radar scan time over the search area, NOT "when hail hit the property".
events[].track_ids array — Stable storm-track ids whose detections fell inside the boundary this day. See storm_groups: and read its warning before using it for anything contractual. Empty when the rows carry no track lineage. Capped at limits.track_ids_per_event.
events[].points array — WHERE inside the boundary the signal fell: individual detection grid samples for this day, largest first, so a client can render which blocks of a large site were hit and which were clean instead of reading one peak pin for the whole polygon. Bounded to limits.points_per_event per day; points_truncated says whether that cap bit. THESE ARE GRID SAMPLES, NOT A COVERAGE POLYGON. No percentage-of-area-affected figure is derivable from them and none is published: see honesty.
events[].points[].lat number degrees (WGS 84) Latitude, decimal degrees (WGS 84).
events[].points[].lon number degrees (WGS 84) Longitude, decimal degrees (WGS 84).
events[].points[].size_mm number mm Radar-estimated stone size at this grid sample (mm).
events[].points_truncated boolean — True when this day had more detections than limits.points_per_event and points is a clipped sample of the largest. A clipped sample must never be read as the complete set.
events[].wind object — 🔴 WIND CONTEXT: WHAT THE NATIONAL WEATHER SERVICE WARNED, AND NOTHING ELSE. Whether an NWS Severe Thunderstorm or Tornado Warning carrying a wind-threat tag covered this search area on this convective day. The source is the issued warning itself: a public, citable government act you can look up by warning_id, the same warnings GET /v1/warnings serves. It is NEVER a model-derived wind probability; this API does not publish one. Wind is EVIDENCE ATTACHED TO A VERDICT ALREADY REACHED. It is never an input to the hail verdict, and adding it did not move methodology.version. Three things this block does not say. (1) The absence of a warning is NOT the absence of wind: the NWS warns on forecast severity, not on observed damage, and damaging gusts are frequently never warned. (2) It makes no statement about what caused any damage: whether damage is hail damage or wind damage is not a determination this API makes, and nothing here ranks hail against wind as a cause. (3) It makes no statement about what a policy covers.
events[].wind.checked boolean — Whether we looked at all. Read this BEFORE warned. false means "we could not check", NOT "no wind threat was warned" and NOT "there was no wind": see reason.
events[].wind.warned boolean · nullable — True when at least one NWS warning carrying a wind-threat tag covered this search area on this convective day. False means we looked and found none: which is NOT the same as "there was no wind", because the absence of a warning is NOT the absence of wind. Always null when checked is false, so "we did not look" can never be read as "we looked and found nothing".
events[].wind.reason string — Present ONLY when checked is false. before_wind_data_era: this date precedes wind_context.data_era_start, the first convective day the NWS warning archive behind this API covers completely. wind_lookup_failed: the warning query failed; the hail verdict on this day is unaffected, because wind is never an input to it. One of: before_wind_data_era, wind_lookup_failed.
events[].wind.warning_count integer — Distinct covering WARNINGS: each warning's CAP messages (its issuance and every update/cancellation) count once, grouped by the warning's P-VTEC identity (office, phenomenon, event number, year) or, for messages archived before 2026-10-25 without VTEC, by issuing office + expiry + overlapping polygon. Absent when checked is false: a count of zero must never stand in for "we did not look". May exceed the length of warnings, which is capped at limits.wind_warnings_per_event.
events[].wind.threat_tags array — The NWS impact-based wind-threat tags carried by the covering warnings, VERBATIM and de-duplicated: in practice RADAR INDICATED or OBSERVED. There is deliberately NO gust-speed field: this is a tag column, not a measurement, and deriving a wind speed from it would publish precision the source does not carry.
events[].wind.warnings array — The covering warnings, oldest first, capped at limits.wind_warnings_per_event. Every entry is independently checkable against the National Weather Service by its warning_id. One entry per warning, described by its FIRST CAP message.
events[].wind.warnings[].warning_id string — The CAP @id of the warning's first (issuing) message.
events[].wind.warnings[].event string — Warning or watch type. One of: Severe Thunderstorm Warning, Tornado Warning.
events[].wind.warnings[].wind_threat string — The NWS impact-based wind-threat tag, verbatim.
events[].wind.warnings[].onset string — Warning onset / effective time, ISO-8601 UTC.
events[].wind.warnings[].expires string — Warning expiration time, ISO-8601 UTC.
events[].wind.note string — The honesty statement for this day's specific state, written to be rendered beside the value. Render it: it is what stops warned: false being read as "it was not windy", and what keeps checked: false from being read as a finding.
events[].ground_truth object — Ground hail reports matched to the event.
temporal_coverage object — See Temporal coverage.
outcome string — The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422. One of: signal, no_signal, insufficient_coverage.
ml_provenance object · nullable — See Model provenance.
event_count integer — Number of entries in events.
scan_counts array — Radar scans versus detection cells per event day, in events order, same shape and rule as on POST /v1/verify: scans_with_hail is the distinct scan times with a signal inside the boundary; detection_cells is events[].n_detections. Beside the hashed events, never inside it. A point-kind area is answered by the point engine and carries the point-grain block (plus evidence_kinds) instead.
scan_counts[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
scan_counts[].scans_with_hail integer · nullable — Distinct radar scan times with a hail signal inside the boundary that day.
scan_counts[].detection_cells integer — Detections that day, counted once per scan and pixel (events[].n_detections).
storm_groups array — 🔴 A METEOROLOGICAL FACT, NOT AN INSURANCE DETERMINATION. Two event dates sharing a track id were produced by the same tracked storm system. This is a meteorological fact, NOT an occurrence determination under any insurance policy: occurrence is a contractual test this API does not and cannot apply. Grouped by CONNECTED COMPONENT: dates land together when they share a track directly, or transitively through another date in the group. Days with no track lineage are absent.
storm_groups[].track_ids array — Storm track ids that link the dates in this group.
storm_groups[].event_dates array — Event dates produced by the same tracked storm system (a meteorological fact, not an occurrence determination).
wind_context object — What the per-event-day wind blocks are, what geometry they were scoped to, and (most importantly) what they do not say. Present on every verification response, including one with no event days.
wind_context.source string — Always NWS warned threat.
wind_context.source_detail string — The exact provenance: impact-based wind-threat tags on National Weather Service Severe Thunderstorm and Tornado Warnings.
wind_context.scope string — The search geometry the warnings were matched against, in words. It is the SAME geometry the hail verdict searched at this grain, never widened to make more warnings match.
wind_context.convective_day string — How a warning is dated. A warning is filed under the convective day (12Z-12Z UTC) of its onset: the same convention event_date uses.
wind_context.data_era_start string — The first convective day the NWS warning archive behind this API covers completely. An event date before this returns checked: false with before_wind_data_era: we did not look, which is NOT a finding that no wind threat was warned.
wind_context.honesty string — The statement of what this feature refuses to say: warned: false does NOT mean there was no wind, checked: false is a different state again, and nothing here is a determination of what caused any damage, of whether damage is hail damage or wind damage, or of what a policy covers.
limits object — Published caps on the bounded evidence arrays.
limits.track_ids_per_event integer — Most track ids listed per event.
limits.points_per_event integer — Maximum detection points returned per event day.
limits.wind_warnings_per_event integer — Maximum warnings listed in events[].wind.warnings per day. wind.warning_count is the true total and is not capped.
data_as_of string — The hail record's real watermark: the newest convective day that is provably complete (temporal_coverage.data_through). NOT an echo of the request's date_end: a window ending today reports an earlier date here.
POST /v1/verify/portfolio Post-storm triage: a verdict for every monitored property 114 fields
FieldTypeUnitMeaning
grain string — Verdict grain (portfolio). One of: portfolio.
date_range object — The date window the answer covers.
radius_mi number mi The sweep radius (1 mile): a day is listed for a property when something was recorded within this distance of it. A listed day's evidence is read over methodology.evidence_radius_mi.
assets_checked integer — Assets checked: those whose impact radius reaches the contiguous US.
truncated boolean — True when the portfolio exceeded limits.max_assets. The list is INCOMPLETE: the properties not returned were not checked.
limits object — Published caps on the request and on the bounded evidence arrays.
limits.max_assets integer — Most assets accepted in one request.
limits.max_range_days integer — Longest date window accepted, days.
limits.track_ids_per_event integer — Maximum distinct track ids returned per property-day.
limits.wind_warnings_per_event integer — Maximum warnings listed in properties[].days[].wind.warnings per property-day. wind.warning_count is the true total and is not capped.
wind_context object — What the per-event-day wind blocks are, what geometry they were scoped to, and (most importantly) what they do not say. Present on every verification response, including one with no event days.
wind_context.source string — Always NWS warned threat.
wind_context.source_detail string — The exact provenance: impact-based wind-threat tags on National Weather Service Severe Thunderstorm and Tornado Warnings.
wind_context.scope string — The search geometry the warnings were matched against, in words. It is the SAME geometry the hail verdict searched at this grain, never widened to make more warnings match.
wind_context.convective_day string — How a warning is dated. A warning is filed under the convective day (12Z-12Z UTC) of its onset: the same convention event_date uses.
wind_context.data_era_start string — The first convective day the NWS warning archive behind this API covers completely. An event date before this returns checked: false with before_wind_data_era: we did not look, which is NOT a finding that no wind threat was warned.
wind_context.honesty string — The statement of what this feature refuses to say: warned: false does NOT mean there was no wind, checked: false is a different state again, and nothing here is a determination of what caused any damage, of whether damage is hail damage or wind damage, or of what a policy covers.
coverage object — Read this before the list. Always present on a 200 (a business with no point assets is refused 422 no_portfolio_assets). ONE PROBE, AT THE PORTFOLIO CENTROID. getDetectionQuality is a warehouse lookup; running it per property would make the cost scale with portfolio size for a field that is context, not a verdict. So it is fetched once, at the arithmetic mean of your asset positions: and for a portfolio spread across several states that centroid is a point no property occupies. quality_basis says so on every response. For true per-property radar geometry call POST /v1/verify (or GET /v1/coverage) for that address.
coverage.in_service_area boolean — Whether the portfolio centroid is inside the CONUS radar domain.
coverage.message string — Human-readable text. Wording may change; never parse it.
coverage.see_also string — Where to read more about the service area.
coverage.centroid object — The point quality was measured at: mean of the asset positions.
coverage.quality_basis string — Always present, even when quality_caveat is null, so a centroid grade is never mistaken for a per-property one. One of: portfolio_centroid.
coverage.quality string — HDQI band at the portfolio centroid. See quality_basis. One of: excellent, good, fair, limited, unknown.
coverage.quality_hdqi number · nullable — The 0-1 HDQI score behind quality, rounded to 3 decimal places. Null when no cell resolved. DISPLAY PRECISION: 3 decimals, exactly as returned (0.995 prints "0.995"); never rounded or truncated to 2 (the verification report PDF prints the same 3-decimal value as detection_quality.hdqi).
coverage.quality_caveat string · nullable — Non-null when quality is fair, limited or unknown. ONE-SIDED: degraded radar geometry can leave real hail unobserved, so a NEGATIVE result (a miss, or a day absent from a property's days) is weaker evidence than the same result in a well-covered cell. It does NOT make a POSITIVE result wrong. On this endpoint the text additionally notes that each property is searched within 1 mile (tighter than the 5-mile single-address check) so degraded coverage removes proportionally more of the signal a wider search would still have caught.
temporal_coverage object — See Temporal coverage.
outcome string — The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422. One of: signal, no_signal, insufficient_coverage.
ml_provenance object · nullable — See Model provenance.
summary object — One count per PROPERTY verdict across the returned properties (a property's verdict is its strongest day). The six counts add up to the list. miss is counted, not derived: a quiet property on a window that is not fully on record is counted under insufficient_coverage, never miss.
summary.hit integer — Properties with at least one day "Confirmed at the property".
summary.detected integer — Properties whose strongest day is "Detected at the property, not counted". Not confirmed: never add this to hit.
summary.nearby integer — Properties whose strongest day is "Nearby, not overhead": the storm is documented, something was recorded within 1 mile of the property and nothing within 0.5 mile of it. Added in verify-v5.
summary.marginal integer — Properties whose strongest day is marginal.
summary.miss integer — Properties with no signal on a window fully on record.
summary.insufficient_coverage integer — Quiet properties on a window not fully on record: never counted as miss.
size_fields object — What the two radar size fields on every property and day measure, as sentences a client can print beside the number. The two measure different things.
size_fields.at_property_size_mm string mm Sentence stating what at_property_size_mm measures (within 0.5 mile of the property).
size_fields.sweep_peak_size_mm string mm Sentence stating what sweep_peak_size_mm measures (the storm's peak across the search radius).
methodology object — version (verify-v5), grain: portfolio, description (the rule above in plain words, generated from the constants the rule uses), the per-generation confidence thresholds (ml_confidence_bars, ml_confidence_bar_default), mesh_min_mm, radius_mi and at_property_radius_mi.
methodology.version string — Verdict-rule version (e.g. verify-v5); changes when the rule changes.
methodology.grain string — Verdict grain (portfolio). One of: portfolio.
methodology.description string — Plain-language description of the block.
methodology.radius_mi number mi The sweep radius around each property (1): which days are listed.
methodology.evidence_radius_mi number mi The area a listed day's evidence is read over (5): the POST /v1/verify search area, so "is this storm documented" has one answer on both endpoints.
methodology.at_property_radius_mi number mi The radius that separates hit / detected from nearby (0.5): the same one POST /v1/verify publishes.
methodology.mesh_min_mm number mm Smallest radar size estimate a detection needs to count, millimetres.
properties array — One entry per asset checked, with its strongest-day verdict and per-day detail.
properties[].asset_id string — Your asset id, as uploaded.
properties[].name string — Your asset name, as uploaded.
properties[].verdict string — The property's STRONGEST day in the window: hit > detected > nearby > marginal > miss. A property with no storm day is miss ONLY when every day of the window is on record (temporal_coverage.covered: full); otherwise it is insufficient_coverage; we did not see every day, so "no hail" is not a finding for this roof. insufficient_coverage is not no signal, and detected or nearby is not a hit. The list is ordered hit, detected, nearby, marginal, miss, insufficient_coverage; then observable properties before ones outside radar coverage; then at_property_size_mm, then sweep_peak_size_mm (largest first); then name. One of: hit, detected, nearby, marginal, miss, insufficient_coverage.
properties[].verdict_label string — The verdict in the words a customer reads: "Confirmed at the property" (or "Confirmed at the property; by ground report" when every confirmed day was confirmed by a ground report alone), "Detected at the property, not counted", "Nearby, not overhead", "Weak signal", "Reported, not radar-confirmed", "No signal", or "Dates not on record" (insufficient_coverage). Print as returned.
properties[].coverage object — See Service-area coverage.
properties[].storm_days integer — How many days of the window have a row in days (any verdict). NOT the number of confirmed days: that is confirmed_days.
properties[].confirmed_days integer — Days whose verdict is hit ("Confirmed at the property").
properties[].detected_days integer — Days whose verdict is detected. Not confirmed days: never add them to confirmed_days.
properties[].nearby_days integer — Days whose verdict is nearby.
properties[].marginal_days integer — Days whose verdict is marginal.
properties[].corroborated_days integer — Days with a ground report within 1 mile of the property: not necessarily within 0.5 mile, so not the number of days a ground report confirmed.
properties[].at_property_size_mm number · nullable mm Largest radar size estimate within 0.5 mile of the property, over the window. Null when radar made no estimate within 0.5 mile on any day.
properties[].at_property_size_date string · nullable — The day at_property_size_mm was estimated.
properties[].sweep_peak_size_mm number · nullable mm Largest radar size estimate within 1 mile of the property, over the window. It may have been up to a mile from the property: it is not the size at the property.
properties[].sweep_peak_date string · nullable — The day sweep_peak_size_mm was estimated.
properties[].peak_size_mm number · nullable mm Same value as sweep_peak_size_mm (within 1 mile of the property), under its pre-verify-v5 name. Prefer sweep_peak_size_mm.
properties[].peak_date string · nullable — Same value as sweep_peak_date.
properties[].closest_distance_mi number · nullable mi Distance from the property to the closest evidence on any day of the window: a radar detection or a ground report, whichever was closer. Miles, 2 decimals. Radar distances are to the centre of a ~1 km grid cell. Null for a property with no storm day.
properties[].closest_distance_date string · nullable — The day closest_distance_mi was measured on.
properties[].days array — Per-day detail, newest first.
properties[].days[].event_date string — Storm date of the event (YYYY-MM-DD, 12:00 to 12:00 UTC).
properties[].days[].verdict string — The day's verdict: the same one POST /v1/verify returns for this property and day. A day with no detection and no ground report within 1 mile has no row, so miss never appears here. One of: hit, detected, nearby, marginal.
properties[].days[].verdict_label string — The day's verdict as a customer reads it (see properties[].verdict_label). Print as returned.
properties[].days[].basis_statement string · nullable — WHAT the day's verdict rests on, in words, on a hit day ("confirmed by ...") and a detected day ("radar detected hail ..."); null on nearby and marginal. Starts lower-case so it can follow "Basis: ". Distances are measured from "the pin placed for this address" (a saved property records no rooftop-level match). "The search area" in this sentence is the 5-mile evidence area. The same sentence basis_statements carries on POST /v1/verify for this pin and day: a detected day never says "confirmed".
properties[].days[].confirmed_by string · nullable — What confirmed a hit day, inside 0.5 mile: a strong radar detection, a ground report, or both. Null on every other verdict. One of: radar, ground_report, both.
properties[].days[].at_property_size_mm number · nullable mm Largest radar size estimate within 0.5 mile of the property that day. Null when none.
properties[].days[].sweep_peak_size_mm number · nullable mm Largest radar size estimate within 1 mile of the property that day.
properties[].days[].closest_distance_mi number · nullable mi Distance to the closest evidence that day (radar or ground report). Miles, 2 decimals.
properties[].days[].proximity object — WHERE the day's evidence was, relative to the property.
properties[].days[].proximity.at_property boolean — The closest evidence is within 0.5 mile. True on hit AND detected (and on a weak day whose evidence is close): it says where, not that the day is confirmed.
properties[].days[].proximity.at_property_radius_mi number mi Radius, miles, counted as at the property.
properties[].days[].proximity.nearest_detection object · nullable — lat, lon, distance_mi, bearing_deg, direction of the closest radar detection within 5 miles (it may be outside the 1-mile sweep). Null when there was none.
properties[].days[].proximity.nearest_report object · nullable — distance_mi, direction of the closest ground report within 5 miles (it may be outside the 1-mile sweep). Null when none was filed there.
properties[].days[].at_property object — What radar recorded within 0.5 mile of the property that day.
properties[].days[].at_property.max_size_mm number · nullable mm Largest size estimate within 0.5 mile of the property that day, millimetres (see size_fields).
properties[].days[].at_property.ml_confidence number · nullable — Peak Anvil hail probability over the detections within 0.5 mile on this day: the same quantity ml_probability carries elsewhere in this API, not a measure of certainty that the event happened.
properties[].days[].at_property.n_detections integer — Radar hail detections counted once per scan and pixel location.
properties[].days[].at_property.strong_detection boolean — One detection within 0.5 mile was at least 25 mm AND met its confidence threshold. The radar half of what separates hit from detected.
properties[].days[].max_size_mm number mm Same value as sweep_peak_size_mm: the largest estimate within 1 mile of the property that day, NOT the size at the property (at_property_size_mm). Radar-derived MESH (Maximum Estimated Size of Hail), in millimetres: a size inferred from reflectivity ALOFT, in the storm above the ground, not a measurement of a stone that landed. Radar and ground figures can legitimately differ for the same storm. The response also carries size_basis, which states this machine-readably.
properties[].days[].detection_strength string · nullable — Display tier of ml_confidence, tiered on the model generation that scored it: marginal (cleared the hail-detection threshold), moderate (cleared the default history filter), strong (cleared the bar at which radar alone can confirm hail). null below the detection threshold or when no model probability is available. Prefer this word over the raw probability for display: the probability is a calibrated chance that a 0.75 in+ hail report is filed within 15 km, so it reads low (often 10 to 40%) even for real hail. The bars move with each model promotion; the meaning of each word does not. One of: marginal, moderate, strong.
properties[].days[].detection_strength_rank integer · nullable — detection_strength as 1 (marginal), 2 (moderate) or 3 (strong), for sorting and filtering. Null with it.
properties[].days[].ml_confidence number · nullable — Peak Anvil hail probability over the detections within this asset's radius on this day: P(hail >= 0.75" / 19.05 mm within 15 km), the SAME quantity ml_probability carries elsewhere in this API. It is NOT a measure of how certain the system is that the event happened; a well-calibrated 0.29 means roughly a 29% chance of hail, not "29% sure". Named ml_confidence to match the point-grain endpoint. SCALE-BOUND: comparable only within one detect model generation; model_version below names which.
properties[].days[].model_version string · nullable — Public id of the detection-model generation the day's ml_confidence is on.
properties[].days[].n_detections integer — Radar hail detections counted once per scan and pixel location.
properties[].days[].first_scan_time string · nullable — ⚠️ READ THIS BEFORE USING THE TIMESTAMP. This and its two siblings are radar scan times when a hail signal was present over the search area: here, the 1-mile disc around this property. They are NOT "when hail hit the property": the signal may have been a mile away from the structure. Null on a report-only day. ISO-8601 UTC.
properties[].days[].peak_scan_time string · nullable — Valid time of the scan carrying this property-day's LARGEST estimate. Same caveat: a radar scan time over the search area, NOT "when hail hit the property".
properties[].days[].last_scan_time string · nullable — Valid time of the LAST scan with a qualifying signal in this property's disc. Same caveat: a radar scan time over the search area, NOT "when hail hit the property".
properties[].days[].track_ids array — Stable storm-track ids whose detections fell in this property's disc that day. See storm_groups: and read its warning before using it for anything contractual. Capped at limits.track_ids_per_event.
properties[].days[].wind object — 🔴 WIND CONTEXT: WHAT THE NATIONAL WEATHER SERVICE WARNED, AND NOTHING ELSE. Whether an NWS Severe Thunderstorm or Tornado Warning carrying a wind-threat tag covered this search area on this convective day. The source is the issued warning itself: a public, citable government act you can look up by warning_id, the same warnings GET /v1/warnings serves. It is NEVER a model-derived wind probability; this API does not publish one. Wind is EVIDENCE ATTACHED TO A VERDICT ALREADY REACHED. It is never an input to the hail verdict, and adding it did not move methodology.version. Three things this block does not say. (1) The absence of a warning is NOT the absence of wind: the NWS warns on forecast severity, not on observed damage, and damaging gusts are frequently never warned. (2) It makes no statement about what caused any damage: whether damage is hail damage or wind damage is not a determination this API makes, and nothing here ranks hail against wind as a cause. (3) It makes no statement about what a policy covers.
properties[].days[].wind.checked boolean — Whether we looked at all. Read this BEFORE warned. false means "we could not check", NOT "no wind threat was warned" and NOT "there was no wind": see reason.
properties[].days[].wind.warned boolean · nullable — True when at least one NWS warning carrying a wind-threat tag covered this search area on this convective day. False means we looked and found none: which is NOT the same as "there was no wind", because the absence of a warning is NOT the absence of wind. Always null when checked is false, so "we did not look" can never be read as "we looked and found nothing".
properties[].days[].wind.reason string — Present ONLY when checked is false. before_wind_data_era: this date precedes wind_context.data_era_start, the first convective day the NWS warning archive behind this API covers completely. wind_lookup_failed: the warning query failed; the hail verdict on this day is unaffected, because wind is never an input to it. One of: before_wind_data_era, wind_lookup_failed.
properties[].days[].wind.warning_count integer — Distinct covering WARNINGS: each warning's CAP messages (its issuance and every update/cancellation) count once, grouped by the warning's P-VTEC identity (office, phenomenon, event number, year) or, for messages archived before 2026-10-25 without VTEC, by issuing office + expiry + overlapping polygon. Absent when checked is false: a count of zero must never stand in for "we did not look". May exceed the length of warnings, which is capped at limits.wind_warnings_per_event.
properties[].days[].wind.threat_tags array — The NWS impact-based wind-threat tags carried by the covering warnings, VERBATIM and de-duplicated: in practice RADAR INDICATED or OBSERVED. There is deliberately NO gust-speed field: this is a tag column, not a measurement, and deriving a wind speed from it would publish precision the source does not carry.
properties[].days[].wind.warnings array — The covering warnings, oldest first, capped at limits.wind_warnings_per_event. Every entry is independently checkable against the National Weather Service by its warning_id. One entry per warning, described by its FIRST CAP message.
properties[].days[].wind.warnings[].warning_id string — The CAP @id of the warning's first (issuing) message.
properties[].days[].wind.warnings[].event string — Warning or watch type. One of: Severe Thunderstorm Warning, Tornado Warning.
properties[].days[].wind.warnings[].wind_threat string — The NWS impact-based wind-threat tag, verbatim.
properties[].days[].wind.warnings[].onset string — Warning onset / effective time, ISO-8601 UTC.
properties[].days[].wind.warnings[].expires string — Warning expiration time, ISO-8601 UTC.
properties[].days[].wind.note string — The honesty statement for this day's specific state, written to be rendered beside the value. Render it: it is what stops warned: false being read as "it was not windy", and what keeps checked: false from being read as a finding.
properties[].days[].ground_truth object — Ground hail reports matched to the event.
storm_groups array — 🔴 A METEOROLOGICAL FACT, NOT AN INSURANCE DETERMINATION. Two event dates sharing a track id were produced by the same tracked storm system. This is a meteorological fact, NOT an occurrence determination under any insurance policy: occurrence is a contractual test this API does not and cannot apply. Computed across the WHOLE portfolio, not per property: two dates land in one group when any property links them, directly or transitively through another date in the group. Days with no track lineage are absent.
storm_groups[].track_ids array — Storm track ids that link the dates in this group.
storm_groups[].event_dates array — Event dates produced by the same tracked storm system (a meteorological fact, not an occurrence determination).
data_as_of string · nullable — The hail record's real watermark: the newest convective day that is provably complete (temporal_coverage.data_through). NOT an echo of the request's date_end: a window ending today reports an earlier date here.
GET /v1/verify/coverage The hail record's coverage bounds (free) 25 fields
FieldTypeUnitMeaning
data_version object — See Data version.
record string — Which record the block describes (radar_detections). One of: radar_detections.
archive_start string — First fully covered storm day of the record.
data_through string — Newest storm day provably complete.
latest_scan_time string · nullable — Newest radar scan in the record (UTC).
spans array — The covered spans, as read from the record. A span breaks at every day listed in the dark-day ledger (see the API-level note *Days withheld from coverage*), at a day holding no rows in a month that borders a hole in the record, at every day listed in coverage_exceptions, and (from the first full day of the scanner heartbeat) at any day the heartbeat does not attest complete.
spans[].start string — First day of the range (YYYY-MM-DD, inclusive).
spans[].end string — Last day of the range (YYYY-MM-DD, inclusive).
uncovered_ranges_recent array — Uncovered ranges inside the last 30 days, same shape as temporal_coverage.uncovered_ranges.
coverage_exceptions array — Every convective day our hail-record completeness audit recorded as incomplete, ascending ([] when none). Such a day is never counted covered: inside the record's range spans break at it and uncovered_ranges name it coverage_exception; a listed day already outside the record keeps its own reason. The list can only withhold coverage, never grant it.
as_of string — When the bounds were read.
window object — The temporal_coverage block for start_date..end_date. Present only when a window was requested.
outcome_if_empty string — Present only when a window was requested. One of: no_signal, insufficient_coverage.
reports object · nullable — The caller's monthly report allowance on the server's billing period (POST /v1/verify/report), read for free. issued is the count the allowance is enforced on. null when the allowance could not be read (never a zero).
reports.period string — Billing period key, YYYY-MM (UTC calendar month).
reports.period_starts_at string — Start of the current billing period (UTC).
reports.period_ends_at string — Exclusive: the first instant of the next period, when the allowance resets.
reports.issued integer — Counted reports issued this period. Duplicates and free re-issues are not counted.
reports.included integer · nullable — The plan's published monthly allowance. null on a plan with no published count (Enterprise, kind: fair_use).
reports.kind string — Allowance type: included (a published monthly count) or fair_use. One of: included, fair_use.
reports.today object — The per-UTC-day report ceiling.
reports.today.issued integer — Reports issued so far in the period (or today).
reports.today.ceiling integer — Daily report ceiling.
reports.today.applies boolean — False during the account's onboarding grace.
request_id string — Correlates this response with server logs; quote it in a support request.
GET /v1/portfolio/impacts Which portfolio assets were impacted on a given date 32 fields
FieldTypeUnitMeaning
date string — Storm date the answer is for (YYYY-MM-DD; a storm day runs 12:00 to 12:00 UTC).
radius_mi number mi Search radius, miles.
assets_checked integer — Point assets checked against the day's detections. Excludes assets whose whole impact radius lies outside the contiguous-US boundary (never checked, never "unaffected").
truncated boolean — True when the portfolio holds more point assets than limits.max_assets; only the first max_assets were checked.
limits object — Published caps on the request.
limits.max_assets integer — Most point assets one call checks.
impacted_count integer — Assets listed in impacts.
outcome string — The window-level answer. READ THIS, NOT events.length. signal: at least one event day. no_signal: nothing found AND every day of the window is on record (temporal_coverage.covered: full): the only state that may be worded as "no hail signal". insufficient_coverage: nothing found, but part of the window is not on record; say which days are missing (temporal_coverage.uncovered_ranges), never "no hail". A window the radar record holds nothing for (temporal_coverage.searchable: false) is answered by the verify grains from ground reports alone when the ground-report archive holds every day of it (temporal_coverage.radar_searched: false, ground_reports_searched: true): signal when a ground report was found (charged), otherwise insufficient_coverage; never no_signal, because radar was not consulted; and that answer is free (X-Credits-Cost: 0). Only a window NEITHER record can be searched for is a 422. One of: signal, no_signal, insufficient_coverage.
temporal_coverage object — See Temporal coverage.
impacts array — Portfolio assets with hail on the date.
impacts[].asset_id string — Your asset id, as uploaded.
impacts[].name string — Your asset name, as uploaded.
impacts[].nearest_detection_m number m Distance from the asset to the nearest qualifying detection, metres.
impacts[].max_size_mm number · nullable mm The largest CALIBRATED, ML-gated hail size (mm) across the geohash5 cells matched within the impact radius; a calibrated estimate of REPORTED surface size (empirical radar-to-report bands below the large-hail crossover, the trained quantile size model at and above it), still inferred from the storm aloft, not a stone that landed. The same per-cell figure GET /v1/hail/current answers as summary.max_size_mm and the portfolio.impact_summary webhook row carries for the same cells. NOT radar MESH (changed BL-271: it used to be the raw radar maximum within the radius). null when no matched cell clears the hail-detection confidence threshold: never 0, and no raw radar figure is substituted. The top-level max_size_basis states its basis machine-readably. Sizes are gated on the current hail-detection model's entry threshold for every date, including dates scored by earlier model generations; on those older dates a cell the scoring model would not itself have sized can carry a size, so treat a historical size here as a looser screen than GET /v1/hail/history and POST /v1/verify, which apply each generation's own threshold.
impacts[].max_size_category string · nullable — The largest per-cell size class across the matched cells, classed from the same calibrated per-cell estimate as max_size_mm (so the two cannot disagree); null when no matched cell clears the hail-detection confidence threshold. Sizes are gated on the current hail-detection model's entry threshold for every date, including dates scored by earlier model generations; on those older dates a cell the scoring model would not itself have sized can carry a size, so treat a historical size here as a looser screen than GET /v1/hail/history and POST /v1/verify, which apply each generation's own threshold. One of: pea, nickel, quarter, golf_ball, tennis_ball, baseball, softball.
impacts[].max_size_method string · nullable — Which method produced max_size_mm / max_size_category, for the cell that sets the maximum: model (the trained size model's median) or radar_band (the empirical radar-to-report size bands). null when max_size_mm is. Storm days before 2026-07-01 mostly carry radar_band at and above about 40 mm; see the *Size method* note in this API's description for the measured boundary. One of: model, radar_band.
impacts[].ml_confidence number · nullable — The maximum hail-detection model probability over the asset's matched cells, 0 to 1, rounded to 3 decimals: the peak Anvil probability of P(hail >= 0.75" / 19.05 mm within 15 km), the SAME quantity ml_probability carries elsewhere in this API. It is NOT a measure of how certain the system is that the event happened; a well-calibrated 0.29 means roughly a 29% chance of hail, not "29% sure". Named ml_confidence for historical reasons on this endpoint only. null when no matched detection carried a scored probability. SCALE-BOUND: stored probabilities are never rescored, so a value is comparable only to one from the SAME detect model generation; this row does not name that generation.
impacts[].n_detections integer — Radar hail detections counted once per scan and pixel location.
max_size_basis object — What every impacts[].max_size_mm is, machine-readably: the calibrated-size basis, identical to GET /v1/hail/current's summary.max_size_basis and the portfolio.impact_summary webhook's data.max_size_basis. Never describes raw radar MESH.
max_size_basis.basis string — Which size field impacts[].max_size_mm is: the calibrated, model-gated size.
max_size_basis.quantity string — What impacts[].max_size_mm measures: the calibrated, model-gated size estimate (never the raw radar field).
max_size_basis.units string — Units of impacts[].max_size_mm (mm). One of: mm.
data_as_of string — The radar hail record's watermark: the newest provably complete day (temporal_coverage.data_through). Never the requested date echoed back.
service_area object — Always present. Assets excluded because their whole 1-mile impact radius lies outside the contiguous-US boundary every detection is clipped to (a Windsor, Ontario, an Anchorage or a San Juan lot). They were NOT checked and are not counted in assets_checked: never read their absence from impacts as unaffected. excluded_count: 0 for an all-US portfolio. The same block the portfolio.impact_summary webhook sends as data.service_area.
service_area.clipped_to string — The boundary results were clipped to. One of: us_boundary.
service_area.excluded_count integer — Always complete.
service_area.excluded_assets array — The excluded assets, capped at 500 (see excluded_assets_truncated).
service_area.excluded_assets[].asset_id string — Your id for an asset that was NOT checked (outside the contiguous US).
service_area.excluded_assets[].name string · nullable — Your name for an asset that was NOT checked.
service_area.excluded_assets_truncated boolean — True when excluded_assets is cut at its cap; excluded_count is the full number.
service_area.partially_covered_count integer — Checked assets whose impact radius crosses the US border: checked on the contiguous-US side only.
service_area.note string — Fixed disclosure text. Says excluded assets lie outside the contiguous United States (Canada, Mexico, Alaska, Hawaii, US territories) and that their absence from impacts is not a finding of no hail.
GET /v1/wind/swaths Storm-day wind swaths: URMA analysis and/or convective (reports + radar) layers 58 fields
FieldTypeUnitMeaning
date string — Storm date the answer is for (YYYY-MM-DD; a storm day runs 12:00 to 12:00 UTC).
query object — The resolved request.
active boolean — True when at least one band has area in the query area.
outcome string — READ THIS, not active. no_signal only for a complete day. One of: signal, no_signal, insufficient_coverage.
max_gust_mph number mph Analysis: highest storm-day max gust (mph) of any returned polygon. Convective/all: highest measured or estimated REPORT gust in the area (null when only damage, radar or analysis bands are present).
bands array — Convective/all: the blended max per band; each item adds bases (the layers present in that band).
bands[].band_mph integer mph Every point inside had a storm-day max analysed gust >= band_mph. One of: 40, 50, 58, 70, 80.
bands[].label string — Display label for the band. One of: gusty, damaging, severe, significant, destructive.
bands[].area_km2 number km² Geodesic area of the band inside the query area.
bands[].max_gust_mph number mph Analysis layer: highest storm-day max analysed gust within the band's polygons. Convective/all: highest measured or estimated report gust in the band; null for damage_report, radar_indicated and analysis areas.
bands[].n_polygons integer — Stored polygons unioned into this band.
bands[].bases array — Convective/all blended bands only: the layers with area in this band. One of: gust_report, damage_report, radar_indicated, analysis.
bands[].geometry object — GeoJSON Polygon or MultiPolygon (WGS84).
layers array — Convective/all only: the attributed polygons, one entry per basis.
layers[].basis string — What set this layer's bands: a measured or estimated gust report, a damage report, radar indication, or the wind analysis. One of: gust_report, damage_report, radar_indicated, analysis.
layers[].speed_claimed boolean — True only for gust_report; never read a speed off another basis.
layers[].description string — Plain-language description of the block.
layers[].bands array — Wind-speed bands present, strongest last.
layers[].bands[].band_mph integer mph Every point inside had a storm-day max analysed gust >= band_mph. One of: 40, 50, 58, 70, 80.
layers[].bands[].label string — Display label for the band. One of: gusty, damaging, severe, significant, destructive.
layers[].bands[].area_km2 number km² Geodesic area of the band inside the query area.
layers[].bands[].max_gust_mph number mph Analysis layer: highest storm-day max analysed gust within the band's polygons. Convective/all: highest measured or estimated report gust in the band; null for damage_report, radar_indicated and analysis areas.
layers[].bands[].n_polygons integer — Stored polygons unioned into this band.
layers[].bands[].bases array — Convective/all blended bands only: the layers with area in this band. One of: gust_report, damage_report, radar_indicated, analysis.
layers[].bands[].geometry object — GeoJSON Polygon or MultiPolygon (WGS84).
at_point object — Radius queries only (null otherwise).
at_point.lat number degrees (WGS 84) Latitude, decimal degrees (WGS 84).
at_point.lon number degrees (WGS 84) Longitude, decimal degrees (WGS 84).
at_point.max_band_mph integer mph Highest band whose polygon covers the point; null if none.
at_point.basis string — Convective/all: the layer that set max_band_mph. One of: gust_report, damage_report, radar_indicated, analysis.
at_point.speed_claimed boolean — Convective/all: true only when a gust report set the point's band.
at_point.band_by_basis object — Convective/all: the point's band in each requested layer (null if none).
at_point.max_band_in_radius_mph integer mph Highest band anywhere in the circle.
at_point.evidence object — Convective/all only: facts to set your own confidence bar. Built from the storm day's convective wind reports, de-duplicated as in GET /v1/wind/reports. Strict reading: basis = gust_report and nearest_gust_report.distance_miles small. Loose reading: any basis. Validation (2025-2026): about 68% of measured severe thunderstorm gusts fall in the ≥58 band at about 40% over-call.
at_point.evidence.nearest_gust_report object · nullable — Nearest measured or estimated gust report within search_radius_km of the point; null when none.
at_point.evidence.nearest_gust_report.distance_km number km Great-circle distance from the query point to the report, in kilometers (1 decimal).
at_point.evidence.nearest_gust_report.distance_miles number mi The same distance in statute miles (1 decimal).
at_point.evidence.nearest_gust_report.gust_mph number mph The report's gust speed in mph (the highest of its de-duplicated SPC/IEM copies).
at_point.evidence.nearest_gust_report.measurement string — measured | estimated | unknown (as filed)
at_point.evidence.nearest_gust_report.event_time string — When the gust was observed (UTC), as filed in the report.
at_point.evidence.reports_in_radius object — De-duplicated convective reports inside the request radius.
at_point.evidence.reports_in_radius.gust_report integer — Reports with a gust speed (any value).
at_point.evidence.reports_in_radius.damage_report integer — Damage-only reports (no speed).
at_point.evidence.reports_in_radius.severe integer — Gust ≥ 58 mph or damage-only.
at_point.evidence.radius_miles number mi The request radius in statute miles that reports_in_radius counts within.
at_point.evidence.search_radius_km integer km How far the nearest-gust search reaches (100).
coverage object — How much of the storm day's input the layer had. Where the radar sub-layer lacks a full day, the answer is insufficient_coverage, never "no wind".
coverage.hours_used integer — Hourly URMA analyses folded into the day max.
coverage.hours_expected integer — Analysis hours expected for the storm day.
coverage.complete boolean — At least 22 of 24 hourly analyses present.
coverage.processed_at string — When the day was last processed (UTC).
coverage.source string — Human-readable description of the source layer.
coverage.source_key string — The source this response was read from. One of: urma.
coverage.archive_start string — First fully covered storm day of the record.
coverage.layers array — Convective/all only: per basis: complete, hours_covered (convective: hours with radar storm objects; analysis: URMA hours), hours_expected, processed_at.
data_as_of string — When this storm day was processed.
spatial_clip object — See Spatial clip.
analysis_note string — The caveat for this source (see the endpoint description). Preserve it when displaying bands.
GET /v1/wind/reports Ground-truth wind reports within a radius, de-duplicated, plus long-record aggregate 44 fields
FieldTypeUnitMeaning
query object — The resolved request (radius in both units, storm-date window, types, filters).
reports array — Wind reports in the radius.
reports[].event_time string — Report event time (UTC).
reports[].storm_date string — SPC storm day (1200-1200 UTC).
reports[].lat number degrees (WGS 84) Latitude, decimal degrees (WGS 84).
reports[].lon number degrees (WGS 84) Longitude, decimal degrees (WGS 84).
reports[].distance_miles number mi Distance from the search center, statute miles.
reports[].distance_km number km Distance from the search center, km.
reports[].report_type string — Report kind: measured gust, estimated gust, or damage. One of: tstm_wind_gust, tstm_wind_damage, non_tstm_wind_gust, non_tstm_wind_damage, marine_tstm_wind.
reports[].convective boolean — Thunderstorm-produced (false for gradient/synoptic wind).
reports[].gust_mph number mph Highest gust in the cluster, mph. Null for damage reports without a speed.
reports[].gust_kt number — gust_mph in knots.
reports[].measurement string — How the speed was obtained (see the endpoint description). One of: measured, estimated, unknown, damage_only.
reports[].severe boolean — gust_mph >= 58 (NWS severe criterion). False when no speed.
reports[].location string — NWS location text, e.g. "4 ESE Tryon".
reports[].county string — County named in the report.
reports[].state string — Two-letter US state code, or null.
reports[].wfo string — Issuing NWS forecast office.
reports[].reporter string — Report source (Mesonet, ASOS, Trained Spotter, Public, ...), when known.
reports[].remarks string — NWS free-text remarks (truncated to 500 characters).
reports[].nws_product_id string — NWS LSR text product id, when known.
reports[].sources array — Every feed that carried this report. One of: iem_lsr, spc_storm_reports.
count integer — Length of the capped reports list.
aggregate object — Cross-source de-duplicated summary of the long wind record within the search radius, rolled up per storm day.
aggregate.report_count integer — De-duplicated reports (gust + damage).
aggregate.gust_report_count integer — Gust reports in the window.
aggregate.damage_report_count integer — De-duplicated reports without a speed.
aggregate.raw_report_count integer — Pre-dedup row count (reveals the SPC/IEM duplication factor).
aggregate.max_gust_mph number mph Strongest gust reported, mph.
aggregate.max_measured_gust_mph number mph Highest gust whose NWS qualifier is "measured".
aggregate.event_days integer — Distinct storm days with at least one report.
aggregate.severe_event_days integer — Days with a thunderstorm gust >= 58 mph or a thunderstorm wind damage report.
aggregate.pct_severe_days number — Share of storm days in the window with a severe gust report (≥58 mph), percent.
aggregate.severe_gust_threshold_mph number mph Gust speed counted as severe, mph (58).
aggregate.window_years integer — Years in the climatology window.
aggregate.window_start string — First storm day the aggregate covers (never before the 2022-07-21 archive floor).
aggregate.min_year integer — First year in the climatology window.
aggregate.max_year integer — Last year in the climatology window.
data_as_of string · nullable — The newest ingested wind report time (UTC): nothing after it has been ingested. Null only when no report was ingested in the last 60 days AND the window ends before those 60 days.
window_partially_ingested boolean — True when the window extends past data_as_of (its storm days end at 12Z the next day), so reports for the later part may not be ingested yet. coverage.ingest_note says so.
coverage object — Archive bounds and the coverage caveat for this product, plus the ingest note on a partly ingested window.
coverage.archive_start string — First storm day in the archive (2022-07-21); continuous from here.
coverage.note string — Plain-language note on the block.
coverage.ingest_note string — Present only when window_partially_ingested is true.
POST /v1/exports Bulk data export (async job): CSV, GeoJSON, or Parquet 9 fields
FieldTypeUnitMeaning
job_id string — Export job id; poll GET /v1/exports/{jobId}.
status string — Job state. One of: pending.
poll_url string — Path to poll for the job's status.
data_version object — See Data version.
snapshot object · nullable — See Export snapshot.
temporal_coverage object · nullable — See Temporal coverage.
coverage object — See Service-area coverage.
spatial_clip object — See Spatial clip.
message string — Human-readable text. Wording may change; never parse it.
GET /v1/exports/{jobId} Poll an export job / fetch the finished file 26 fields
FieldTypeUnitMeaning
job_id string — Export job id; poll GET /v1/exports/{jobId}.
status string — Job state. One of: pending, processing, done, failed.
params object — The request as sent (data_version only when it was named).
data_version object · nullable — See Data version.
snapshot object · nullable — See Export snapshot.
temporal_coverage object · nullable — See Temporal coverage.
coverage object · nullable — See Service-area coverage.
spatial_clip object · nullable — See Spatial clip.
outcome string · nullable — Present when status is done (see VerificationOutcome): READ this, not row_count; no_signal only for a window fully on record. null when there is no radar coverage verdict (dataset: reports). One of: signal, no_signal, insufficient_coverage.
row_count integer — Present when status is done.
truncated boolean — Present when status is done. True if the dataset's row cap was hit.
attribution object · nullable — Present when status is done. CC BY 3.0 attribution for the CoCoRaHS share of a dataset: reports file. Non-null whenever any exported row has source containing "cocorahs"; null for every other file (SPC and IEM Local Storm Reports are U.S. government public domain and owe none) and for a job finished before this was recorded. CC BY 3.0 §4(a) requires the licence URI to accompany every copy you distribute, so preserve it if you redistribute the file. The same credit is inside the file when this is non-null: CSV opens with it as a # comment line (before the header row; skip # lines when parsing), GeoJSON carries a top-level attribution member, Parquet carries attribution and attribution_license_url key-value metadata.
attribution.notice string — Human-readable summary of what is being attributed.
attribution.text string — The required CoCoRaHS credit line. Preserve verbatim on redistribution.
attribution.license_url string — The CC BY 3.0 licence text.
ml_provenance object · nullable — See Model provenance.
content_sha256 string · nullable — Present when status is done. Lowercase hex SHA-256 of the delivered file; every later download is that same file. null for a job finished before it was recorded.
retained_until string · nullable — Present when status is done. The UTC date the stored copy of the file is deleted (400 days after generation); until then a poll re-signs the same file. null for a job finished before it was recorded.
generated_at string — When the file was generated (UTC).
download_url string — Signed download URL, valid for download_url_expires_in_seconds.
download_url_expires_in_seconds integer s Lifetime of download_url, seconds.
error string — Present when status is failed. One of: export_generation_failed.
message string — Present when status is failed. Human text; do not parse.
failure_reason string — Present when status is failed. generation_interrupted: the generating instance died (the job sat in processing past the 5-minute staleness threshold). One of: generation_failed, generation_interrupted.
retryable boolean — Present when status is failed. True while free generation attempts remain.
retry_url string — Present when retryable is true. Poll it to re-run generation free of charge.