---
title: "Get event by ID"
method: GET
path: "/events/{id}"
tags: ["events"]
---

# Get event by ID

`GET /events/{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.

## Path parameters

- `id` string, required

## Response `200`

Event detail

- TrafficEvent
  - `affected_roads` string[]
  - `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_at` string, 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_at` string, date-time — When we first stored this event.
  - `description` string
  - `direction` string, nullable
  - `effective_end_time` string, 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_time` string, 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_time` string, date-time, nullable
  - `estimated_start_time` string, date-time, nullable
  - `id` string
  - `jurisdiction` string
  - `lanes_affected` string, nullable
  - `last_updated` string, 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.
  - `latitude` number
  - `location` object — GeoJSON geometry (Point, LineString, or Polygon) describing where the event applies.
  - `longitude` number
  - `metadata` object — Raw upstream attributes preserved as opaque JSON. Shape varies by source.
  - `road_class` 'interstate' | 'us_highway' | 'state_highway' | 'local'
  - `severity` 'minor' | 'moderate' | 'major' | 'critical'
  - `source` string — Jurisdiction code
  - `source_created_at` string, date-time, nullable — Upstream-reported creation timestamp, when available.
  - `source_id` string
  - `source_updated_at` string, date-time, nullable — Upstream-reported last-modified timestamp, when available.
  - `start_time` string, 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.
  - `title` string
  - `type` 'incident' | 'construction' | 'closure' | 'special_event' | 'weather' | 'road_condition' | 'planned' | 'hazard'

## Other responses

- `403` — Archived event beyond the caller's plan window.
- `404` — Event not found

---

[API](https://skmtc.net/road511/apis/road511-traffic-api.md) · [All operations](https://skmtc.net/road511/apis/road511-traffic-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/road511/road511-traffic-api/versions/39a088b9c155/schema)
