v4

latestOpenAPI 3.1.0Proprietary2026-08-034715226.2 KB
events

Get event by ID

Returns full detail for a single event. Active events are always returned. Archived events are gated by plan: on plans without analytics access (allow_analytics: false) the request is rejected with 403 when the event's effective start_time (COALESCE(start_time, created_at)) is older than 7 days. Plans with analytics access have no such restriction.

get/events/{id}

Path parameters

idstring required

Response

Event detail

affected_roadsstring[]
archive_reason'observed' | 'stale_sweep' | 'expired' | 'null' nullable

How the event was archived. NULL while status='active'; set alongside archived_at at archival time.

  • observed — upstream returned a clean response and the event was not in it, so the per-poll archival path retired it. This is the normal case.
  • stale_sweep — the per-poll path could not retire the event because upstream responses were empty or errored (so we cannot prove the event is gone vs. a transient outage). After roughly poll_interval × sweep_factor with no fresh observation (default factor = 15), a safety-net sweep forces the event to archived. A persistently high stale_sweep ratio on a source signals that the upstream feed often fails to emit clearance records or is unreliable in general — treat the archived_at for such events as "presumed gone by", not a precise end timestamp.
  • expired — the event outlived its own end_time by more than the grace period (90 days) while the upstream feed was still publishing it as current. Some feeds are registries that never retire a record, so neither path above can ever fire on them: without this, work orders whose validity window closed years ago would be served as active indefinitely. Because the verdict is derived from the record's own end_time on every poll, such an event returns to active automatically if upstream later moves the end date forward. Treat archived_at as "first poll at which the event was more than 90 days past its stated end", not as an observed end — the real end is end_time.
archived_atstring date-time nullable

Set the moment our sweep first observed the event missing from the upstream feed.

cause'accident' | 'disabled_vehicle' | 'debris' | 'spill' | 'fire' | 'police_activity' | 'animal' | 'congestion' | 'hazard'

Optional underlying operational cause, classified independently of type (unlike sub_type, which is scoped under type). A crash reported by the source as a closure has type=closure, sub_type=lane_closure, and cause=accident — so filtering cause=accident catches accident-caused events regardless of how the source modeled them. Omitted when no cause could be detected. Additive: never replaces type, sub_type, or severity.

created_atstring date-time

When we first stored this event.

descriptionstring
directionstring nullable
effective_end_timestring date-time nullable

Best available end time for the event. Equals end_time when the upstream feed reported one; otherwise falls back to archived_at (the moment our sweep first observed the event had disappeared from the feed). NULL only for active events with no upstream-reported end — i.e., events that are still ongoing. Use this field for "incident duration" analytics.

end_timestring date-time nullable

Upstream-reported end time. NULL for events the upstream feed never assigned an explicit end (the common case — most 511 feeds simply drop resolved incidents rather than emitting a final timestamp).

estimated_end_timestring date-time nullable
estimated_start_timestring date-time nullable
idstring
jurisdictionstring
lanes_affectedstring nullable
last_updatedstring date-time

When this event's content last changed in our data — not when we last polled it. Re-observing an unchanged event does not move it, so a long-running roadworks entry can legitimately carry a last_updated that is months old while still being live and confirmed on every poll. Use it to answer "what changed since I last synced", and do not read it as a freshness or liveness signal.

The comparison covers the fields we publish (type, sub_type, cause, status, severity, title, description, location, affected_roads, direction, lanes_affected, end_time, the estimated_* times and road_class); it is byte-level, so a re-worded description counts as a change.

/events and /events/geojson sort by severity first and last_updated descending second (see the endpoint descriptions).

Feature.last_updated means the same thing as of 2026-07-23; it used to be poll time.

latitudenumber
locationobject

GeoJSON geometry (Point, LineString, or Polygon) describing where the event applies.

longitudenumber
metadataobject

Raw upstream attributes preserved as opaque JSON. Shape varies by source.

road_class'interstate' | 'us_highway' | 'state_highway' | 'local'
severity'minor' | 'moderate' | 'major' | 'critical'
sourcestring

Jurisdiction code

source_created_atstring date-time nullable

Upstream-reported creation timestamp, when available.

source_idstring
source_updated_atstring date-time nullable

Upstream-reported last-modified timestamp, when available.

start_timestring date-time
status'active' | 'archived'
sub_type'accident' | 'disabled_vehicle' | 'debris' | 'hazard' | 'congestion' | 'police_activity' | 'fire' | 'spill' | 'animal' | 'roadwork' | 'maintenance' | 'bridge_work' | 'utility_work' | 'full_closure' | 'ramp_closure' | 'lane_closure' | 'bridge_closure' | 'seasonal_closure' | 'ice' | 'snow' | 'flooding' | 'high_wind' | 'fog' | 'avalanche' | 'weight' | 'height' | 'width' | 'length' | 'hazmat' | 'chain_control' | 'icy' | 'snow_covered' | 'wet' | 'slush' | 'dry' | 'sporting' | 'parade' | 'concert'

Optional additive second-level classification under type (e.g. type=incident, sub_type=accident). Omitted when the event could not be classified. Never replaces type — existing type filters are unaffected.

titlestring
type'incident' | 'construction' | 'closure' | 'special_event' | 'weather' | 'road_condition' | 'planned' | 'hazard'