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

# List features

`GET /features`

Paginated features filtered by type or group. Either `type` or `group` is required.
Free plan requires jurisdiction parameter.

Time-bounded feature types (`future_construction`, `alerts`, `special_events`,
`truck_restrictions`, …) additionally support the scheduled-window filters
`starts_after` / `starts_before` / `ends_after` / `ends_before`.

## Query parameters

- `type` string
- `group` 'imagery' | 'weather' | 'road_conditions' | 'traffic_performance' | 'planned_events' | 'alerts_advisories' | 'wildfires' | 'trucking' | 'traveler_services' | 'fuel_charging' | 'borders' | 'ferries' | 'transit' | 'tolls' | 'static_infrastructure' | 'operations'
- `jurisdiction` string
- `limit` integer
- `offset` integer
- `bbox` string
- `lat` number
- `lng` number
- `radius_km` number
- `active` 'true' | 'false'
- `starts_after` string
- `starts_before` string
- `ends_after` string
- `ends_before` string

## Response `200`

Paginated features

- PagedFeatures
  - `attribution` SourceCredit[] — Required data-source credits for the sources present in `data`. Present only for sources whose license mandates a displayed credit. See `GET /data-sources` for the full catalogue.
    - `attribution` string — The exact credit string this source's license requires you to display.
    - `license` string
    - `license_url` string, uri
    - `source_code` string — Jurisdiction or program code (e.g. `ESP`, `OSM`, `EIA`).
    - `source_name` string
  - `data` Feature[]
    - `description` string
    - `direction` string, nullable
    - `end_time` string, date-time, nullable
    - `estimated_end_time` string, date-time, nullable
    - `estimated_start_time` string, date-time, nullable
    - `feature_type` string
    - `geometry` object, nullable — Full GeoJSON geometry when the feature is a line or polygon; NULL for point-only features (read latitude/longitude instead).
    - `has_details` boolean — `true` when this feature's `(source, feature_type)` pair has a registered lazy-load detail function — calling `GET /features/{id}/details` or batching via `POST /features/details/batch` will return strictly richer data than what's in the list row (multi-view camera URLs, EV connector availability, sign message text, etc.). `false` means the list row already carries everything the source publishes, and the detail call would be a no-op. Not set on the `/features/geojson` path (that JSON is built in Postgres for performance — call `/features` for the hint).
    - `id` string
    - `is_active` boolean
    - `jurisdiction` string
    - `last_updated` string, date-time — When this feature's **content** last changed in our data — not when we last polled it. Re-observing an unchanged feature does not move it, so a camera or a truck restriction confirmed on every poll can legitimately carry a `last_updated` that is weeks old. 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 (name, description, is_active, the start/end/estimated_* times, `properties`, the geometry and the coordinates); it is value-level, so a re-worded description counts as a change. It is NOT the upstream's own timestamp. Where a source publishes one and we retain it, it appears inside `properties`. Changed 2026-07-23: this field used to be poll time, stamped on every refresh. It now matches `TrafficEvent.last_updated`, which has always meant content-change time — the two schemas previously disagreed on what the name meant, and now they do not.
    - `latitude` number
    - `longitude` number
    - `name` string
    - `properties` object — Type-specific fields (JSONB)
    - `road_name` string, nullable
    - `source` string
    - `source_id` string — Upstream-provided identifier, before our synthetic `id` prefix.
    - `start_time` string, date-time, nullable — Populated for time-bounded feature types (future_construction, alerts, special_events, truck_restrictions, etc.). NULL for permanent features.
  - `has_more` boolean — Whether more features match beyond this page. Always exact, including past the `total` cap.
  - `limit` integer
  - `offset` integer
  - `total` integer — Number of matching features, **counted up to 10,000**. A `total` of exactly `10000` means "10,000 or more", not exactly ten thousand — some layers run to millions of rows (`truck_restrictions` alone is ~1.5M in North America) and counting them exactly cannot be done within the request budget. Under the cap, `total` is exact. `has_more` stays exact at any depth and is the reliable signal for "is there another page" — prefer it over comparing against `total` when paginating deep.

## Other responses

- `400` — Missing type parameter
- `403` — Plan limit exceeded

---

[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)
