---
title: "List traffic events"
method: GET
path: "/events"
tags: ["events"]
---

# List traffic events

`GET /events`

Paginated list of traffic events with filters. Free plan requires
jurisdiction parameter.

**Ordering** (not configurable): severity first — `critical`, `major`,
`moderate`, then everything else — and within a severity band by
`last_updated` descending, tie-broken by `id` descending. Because
`last_updated` is a content-change time, the top of a band is what
changed most recently, not what we polled most recently.

The `id` tiebreak matters for pagination: `last_updated` alone is not a
total order (events land in batches that share a timestamp), so without
it two pages could repeat or skip rows.

**Batch lookup**: pass `?ids=id1,id2,...` to fetch a known set of
events by id. When `ids` is set, all other filters except plan
data delay are ignored and pagination is disabled — the response
always returns the full set in one page. Archived events are
filtered per plan: non-analytics plans
(`allow_analytics: false`) receive only events whose effective
start_time is within the last 7 days. Active events are always
returned regardless of age. `total` reflects the post-filter
count. The batch size is capped by the plan's `max_batch_size`.

## Query parameters

- `ids` string
- `jurisdiction` string
- `limit` integer
- `offset` integer
- `bbox` string
- `lat` number
- `lng` number
- `radius_km` number
- `type` string
- `sub_type` string
- `cause` 'accident' | 'disabled_vehicle' | 'debris' | 'spill' | 'fire' | 'police_activity' | 'animal' | 'congestion' | 'hazard'
- `severity` 'minor' | 'moderate' | 'major' | 'critical'
- `status` 'active' | 'archived' | 'all'
- `road` string
- `start_time_from` string, date-time
- `start_time_to` string, date-time

## Response `200`

Paginated events

- PagedEvents
  - `attribution` SourceCredit[] — Required data-source credits for the sources present in `data`. Present only for sources whose license mandates a displayed credit (e.g. CC-BY, OGL). See `GET /data-sources` for the full catalogue.
    - `attribution` string — The exact credit string this source's license requires you to display.
    - `disclaimer` string — A notice the source requires applications using its data to display. Present only for sources that impose one; it is not a credit and does not replace `attribution`. Where present, display it verbatim.
    - `license` string
    - `license_url` string, uri
    - `source_code` string — Jurisdiction or program code (e.g. `ESP`, `OSM`, `EIA`).
    - `source_name` string
    - `updated_at` string, date-time — The SOURCE's own last-update timestamp, when the upstream feed publishes one. Absent for most sources, which publish none. Several open licenses make this mandatory ALONGSIDE the credit string, not instead of it: Licence Ouverte 2.0 requires "la source ... et la date de la derniere mise a jour", and the Spanish RISP conditions require citing "la fecha de la ultima actualizacion de los documentos objeto de la reutilizacion". Where this field is present, display it with the attribution. Note this is the SOURCE's date, not ours. It is unrelated to a row's `last_updated`, which records when we observed the content change, and it may legitimately be older than the response.
  - `data` 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'
  - `has_more` boolean
  - `limit` integer
  - `offset` integer
  - `total` integer

## Other responses

- `403` — Plan limit exceeded. Common causes: jurisdiction required on Free, trial expired, `start_time_from`/`start_time_to` reaches beyond 7 days without analytics access, `status=archived` or `status=all` requested without a `start_time_from` inside the 7-day window.

---

[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/revisions/92944ef90bfd/schema)
