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