{
  "components": {
    "parameters": {
      "bbox": {
        "description": "Bounding box: minLng,minLat,maxLng,maxLat",
        "in": "query",
        "name": "bbox",
        "schema": {
          "type": "string"
        }
      },
      "jurisdiction": {
        "description": "Filter by jurisdiction code (e.g., `GA`, `ON`, `CA`). Required on Free plan.\n\n**Auto-expansion:** querying a state's primary code that has WZDx/CWZ\nor sub-state regional siblings (see `GET /jurisdictions/groups`) is\nexpanded transparently to include all members. For example,\n`?jurisdiction=CA` returns rows from `CA`, `WZDX_CA`, and `511SF`.\n\nTo opt out, query a non-primary code directly (`?jurisdiction=WZDX_CA`\nreturns WZDx-only rows) or pass a comma-separated list explicitly\n(`?jurisdiction=CA,WZDX_CA`), which bypasses expansion and matches\neach code literally.\n",
        "in": "query",
        "name": "jurisdiction",
        "schema": {
          "type": "string"
        }
      },
      "lat": {
        "description": "Latitude for radius search",
        "in": "query",
        "name": "lat",
        "schema": {
          "type": "number"
        }
      },
      "limit": {
        "description": "Results per page (capped by plan)",
        "in": "query",
        "name": "limit",
        "schema": {
          "default": 100,
          "type": "integer"
        }
      },
      "lng": {
        "description": "Longitude for radius search",
        "in": "query",
        "name": "lng",
        "schema": {
          "type": "number"
        }
      },
      "offset": {
        "description": "Pagination offset",
        "in": "query",
        "name": "offset",
        "schema": {
          "default": 0,
          "type": "integer"
        }
      },
      "radius_km": {
        "description": "Radius in km (used with lat/lng)",
        "in": "query",
        "name": "radius_km",
        "schema": {
          "default": 50,
          "type": "number"
        }
      }
    },
    "schemas": {
      "APIKey": {
        "properties": {
          "allowed_origins": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "expires_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "key_prefix": {
            "type": "string"
          },
          "last_used_at": {
            "format": "date-time",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "owner_email": {
            "type": "string"
          },
          "rate_limit_rpm": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "Customer": {
        "properties": {
          "company": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "email_verified": {
            "type": "boolean"
          },
          "id": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "plan_id": {
            "type": "integer"
          },
          "subscription_ends_at": {
            "format": "date-time",
            "type": "string"
          },
          "subscription_status": {
            "enum": [
              "none",
              "active",
              "canceled",
              "past_due",
              "paused"
            ],
            "type": "string"
          },
          "trial_ends_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Error": {
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "EventWeatherSnapshot": {
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "distance_km": {
            "description": "Distance from event to weather station in km",
            "type": "number"
          },
          "event_id": {
            "type": "string"
          },
          "humidity": {
            "description": "Relative humidity percentage",
            "type": "number"
          },
          "id": {
            "type": "integer"
          },
          "precipitation": {
            "description": "Precipitation type (none, rain, snow, ice, etc.)",
            "type": "string"
          },
          "reading_recorded_at": {
            "description": "When the weather reading was taken",
            "format": "date-time",
            "type": "string"
          },
          "road_surface": {
            "description": "Road surface condition (dry, wet, icy, etc.)",
            "type": "string"
          },
          "temperature": {
            "description": "Temperature in °C",
            "type": "number"
          },
          "visibility": {
            "description": "Visibility in km",
            "type": "number"
          },
          "wind_direction": {
            "type": "string"
          },
          "wind_speed": {
            "description": "Wind speed in km/h",
            "type": "number"
          }
        },
        "type": "object"
      },
      "Feature": {
        "properties": {
          "description": {
            "type": "string"
          },
          "direction": {
            "type": [
              "string",
              "null"
            ]
          },
          "end_time": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "estimated_end_time": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "estimated_start_time": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "feature_type": {
            "type": "string"
          },
          "geometry": {
            "description": "Full GeoJSON geometry when the feature is a line or polygon; NULL for point-only features (read latitude/longitude instead).",
            "type": [
              "object",
              "null"
            ]
          },
          "has_details": {
            "description": "`true` when this feature's `(source, feature_type)` pair has a\nregistered lazy-load detail function — calling\n`GET /features/{id}/details` or batching via\n`POST /features/details/batch` will return strictly richer data\nthan what's in the list row (multi-view camera URLs, EV connector\navailability, sign message text, etc.). `false` means the list\nrow already carries everything the source publishes, and the\ndetail call would be a no-op.\nNot set on the `/features/geojson` path (that JSON is built in\nPostgres for performance — call `/features` for the hint).\n",
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "jurisdiction": {
            "type": "string"
          },
          "last_updated": {
            "format": "date-time",
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "name": {
            "type": "string"
          },
          "properties": {
            "description": "Type-specific fields (JSONB)",
            "type": "object"
          },
          "road_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string"
          },
          "source_id": {
            "description": "Upstream-provided identifier, before our synthetic `id` prefix.",
            "type": "string"
          },
          "start_time": {
            "description": "Populated for time-bounded feature types (future_construction, alerts, special_events, truck_restrictions, etc.). NULL for permanent features.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "HealthResponse": {
        "properties": {
          "db": {
            "description": "Combined Postgres and Redis liveness, formatted as\n`\u003cpostgres\u003e/\u003credis\u003e` where each segment is `ok` or `down`\n(e.g. `ok/ok`, `ok/down`). Probe timeout is 2 seconds; the\nunderlying error is logged server-side, not returned.\n",
            "example": "ok/ok",
            "type": "string"
          },
          "status": {
            "enum": [
              "ok",
              "degraded"
            ],
            "type": "string"
          },
          "uptime": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "status",
          "version",
          "uptime",
          "db"
        ],
        "type": "object"
      },
      "Jurisdiction": {
        "properties": {
          "code": {
            "description": "Identifier used everywhere else in the API (`source`, `jurisdiction`,\nwebhook filters, etc.). Two-letter postal codes for US states and\nCanadian provinces; `WZDX_\u003cSTATE\u003e` for the parallel WZDx feed; ad-hoc\ncodes for sub-state and federal sources (`511SF`, `EIA`, `FHWA`,\n`WZDX_NPS`, ...).\n",
            "type": "string"
          },
          "country": {
            "description": "ISO 3166-1 alpha-2 (`US`, `CA`, `DE`, ...).",
            "type": "string"
          },
          "group_id": {
            "description": "When non-null, points at a record in `GET /jurisdictions/groups` —\nthe canonical bundle that includes this row's primary code plus its\nWZDx / CWZ / regional siblings. Querying `?jurisdiction=\u003cprimary\u003e`\non `/events` or `/features` auto-expands to all members.\n",
            "type": [
              "string",
              "null"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "last_sync_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "official_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "scope": {
            "description": "`state` is the default — real geographic jurisdictions (US states,\nCanadian provinces). `federal` covers cross-cutting national sources\n(`EIA`, `FHWA`, `WZDX_NPS`). `regional` covers sub-state feeds\n(`511SF`, `WZDX_AUSTIN`, `WZDX_MCDOT`, `WZDX_STC_MO`). The default\nresponse from `GET /jurisdictions` is `scope=state` only.\n",
            "enum": [
              "state",
              "federal",
              "regional"
            ],
            "type": "string"
          }
        },
        "required": [
          "code",
          "name",
          "country",
          "scope",
          "is_active"
        ],
        "type": "object"
      },
      "JurisdictionGroup": {
        "properties": {
          "description": {
            "type": "string"
          },
          "id": {
            "description": "Stable slug, e.g. `us-ca`, `us-co`, `us-tx`.",
            "type": "string"
          },
          "members": {
            "description": "Jurisdiction codes in this group, primary first. Includes the state\nfeed plus its `WZDX_\u003cSTATE\u003e`, optional `WZDX_CWZ_\u003cSTATE\u003e`, and any\nsub-state regional siblings.\n",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "type": "string"
          },
          "primary_jurisdiction_code": {
            "description": "The canonical state/province code (`CA`, `CO`, `TX`). Querying this\ncode on `/events` or `/features` auto-expands to all `members`.\n",
            "type": "string"
          },
          "sort_order": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "primary_jurisdiction_code",
          "members"
        ],
        "type": "object"
      },
      "PagedEvents": {
        "properties": {
          "attribution": {
            "description": "Required data-source credits for the sources present in `data`.\nPresent only for sources whose license mandates a displayed credit\n(e.g. CC-BY, OGL). See `GET /data-sources` for the full catalogue.\n",
            "items": {
              "$ref": "#/components/schemas/SourceCredit"
            },
            "type": "array"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/TrafficEvent"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PagedFeatures": {
        "properties": {
          "attribution": {
            "description": "Required data-source credits for the sources present in `data`.\nPresent only for sources whose license mandates a displayed credit.\nSee `GET /data-sources` for the full catalogue.\n",
            "items": {
              "$ref": "#/components/schemas/SourceCredit"
            },
            "type": "array"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/Feature"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "Plan": {
        "properties": {
          "allow_analytics": {
            "type": "boolean"
          },
          "allow_geojson": {
            "type": "boolean"
          },
          "allow_truck_data": {
            "type": "boolean"
          },
          "allow_webhooks": {
            "type": "boolean"
          },
          "code": {
            "type": "string"
          },
          "data_delay_sec": {
            "description": "0 = real-time",
            "type": "integer"
          },
          "data_unit_price": {
            "description": "Published per-data-request rate. 0 for Enterprise (unlimited).",
            "type": "number"
          },
          "description": {
            "type": "string"
          },
          "features": {
            "description": "Marketing bullets shown on pricing pages. Admin-managed via\n`/admin/plans/{id}/features`. Resolution order on the frontend:\n`i18n_key` (if the active locale has it) → raw `label` → the key\nstring itself.\n",
            "items": {
              "$ref": "#/components/schemas/PlanFeature"
            },
            "type": "array"
          },
          "id": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "is_contact_sales": {
            "description": "When true, the pricing page renders a \"Contact sales\" CTA instead of Paddle checkout.",
            "type": "boolean"
          },
          "is_popular": {
            "description": "Marketing flag — highlights this plan on the pricing page.",
            "type": "boolean"
          },
          "max_batch_size": {
            "description": "Maximum number of ids accepted by batch endpoints\n(`GET /events?ids=…`, `POST /features/details/batch`). 0 = unlimited.\n",
            "type": "integer"
          },
          "max_jurisdictions": {
            "description": "0 = unlimited",
            "type": "integer"
          },
          "max_keys": {
            "type": "integer"
          },
          "max_requests_day": {
            "description": "0 = unlimited",
            "type": "integer"
          },
          "max_results_per_page": {
            "description": "0 = unlimited",
            "type": "integer"
          },
          "max_saved_routes": {
            "description": "Maximum number of persisted (non-expiring) saved routes. 0 = unlimited.",
            "type": "integer"
          },
          "max_webhooks": {
            "description": "0 = unlimited (only meaningful when `allow_webhooks` is true).",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "paddle_price_id": {
            "description": "Paddle price ID for the monthly billing cadence. NULL for free/trial plans.",
            "type": [
              "string",
              "null"
            ]
          },
          "paddle_price_id_annual": {
            "description": "Paddle price ID for the annual billing cadence. NULL when annual billing isn't offered.",
            "type": [
              "string",
              "null"
            ]
          },
          "price_annual": {
            "description": "Annual price (≈ monthly × 12 × 0.85). NULL for the Free plan.",
            "type": [
              "number",
              "null"
            ]
          },
          "price_monthly": {
            "description": "Monthly price in the currency the Paddle account is configured for.",
            "type": "number"
          },
          "rate_limit_rpm": {
            "type": "integer"
          },
          "routing_max_feature_types": {
            "description": "Cap on how many distinct POI feature types a routing request may select\nvia `enrichment.include_features`. 0 = the `features[]` channel is not\navailable on this plan; N = up to N types; -1 = unlimited.\n",
            "type": "integer"
          },
          "routing_rpm": {
            "description": "Routing-only per-minute rate gate. 0 = no override (falls back to `rate_limit_rpm`).",
            "type": "integer"
          },
          "routing_truck_quota": {
            "description": "Monthly truck-routing call allotment (`POST /routing/route`). 0 = routing not enabled on the plan.",
            "type": "integer"
          },
          "routing_unit_price": {
            "description": "Published per-call routing rate (used for the EU 14-day refund proration).",
            "type": "number"
          },
          "tier_family": {
            "description": "Coarse tier grouping used for display and quota logic.",
            "enum": [
              "free",
              "starter",
              "pro",
              "enterprise"
            ],
            "type": "string"
          },
          "trial_days": {
            "description": "0 = no trial limit",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PlanFeature": {
        "description": "One marketing bullet on a plan's pricing card. Either `i18n_key` or\n`label` is always populated (DB CHECK constraint). Stable bullets use\n`i18n_key` so translations live in frontend locale files; short-lived\nmarketing copy uses `label` only and ships unchanged in every locale.\n",
        "properties": {
          "i18n_key": {
            "description": "Translation key, e.g. `plan_feature.events_cameras`.",
            "type": [
              "string",
              "null"
            ]
          },
          "icon": {
            "description": "Short icon hint (`check`, `zap`, `shield`, ...) the frontend maps to an SVG.",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "integer"
          },
          "label": {
            "description": "Raw fallback shown when no `i18n_key` is set or the locale lacks it.",
            "type": [
              "string",
              "null"
            ]
          },
          "plan_id": {
            "type": "integer"
          },
          "sort_order": {
            "type": "integer"
          },
          "tooltip": {
            "type": [
              "string",
              "null"
            ]
          },
          "tooltip_i18n_key": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "plan_id",
          "sort_order"
        ],
        "type": "object"
      },
      "SourceCredit": {
        "description": "A single data-source attribution / license entry.",
        "properties": {
          "attribution": {
            "description": "The exact credit string this source's license requires you to display.",
            "example": "© Dirección General de Tráfico (DGT) — licensed under CC BY 4.0",
            "type": "string"
          },
          "license": {
            "example": "CC-BY 4.0",
            "type": "string"
          },
          "license_url": {
            "example": "https://creativecommons.org/licenses/by/4.0/",
            "format": "uri",
            "type": "string"
          },
          "source_code": {
            "description": "Jurisdiction or program code (e.g. `ESP`, `OSM`, `EIA`).",
            "example": "ESP",
            "type": "string"
          },
          "source_name": {
            "example": "Spain — DGT National Access Point",
            "type": "string"
          }
        },
        "type": "object"
      },
      "TrafficEvent": {
        "properties": {
          "affected_roads": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "archive_reason": {
            "description": "How the event was archived. NULL while `status='active'`; set\nalongside `archived_at` at archival time.\n\n- `observed` — upstream returned a clean response and the event\n  was not in it, so the per-poll archival path retired it. This\n  is the normal case.\n- `stale_sweep` — the per-poll path could not retire the event\n  because upstream responses were empty or errored (so we cannot\n  prove the event is gone vs. a transient outage). After roughly\n  `poll_interval × sweep_factor` with no fresh observation\n  (default factor = 15), a safety-net sweep forces the event to\n  archived. A persistently high `stale_sweep` ratio on a source\n  signals that the upstream feed often fails to emit clearance\n  records or is unreliable in general — treat the\n  `archived_at` for such events as \"presumed gone by\", not a\n  precise end timestamp.\n",
            "enum": [
              "observed",
              "stale_sweep",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "archived_at": {
            "description": "Set the moment our sweep first observed the event missing from the upstream feed.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "direction": {
            "type": [
              "string",
              "null"
            ]
          },
          "effective_end_time": {
            "description": "Best available end time for the event. Equals `end_time` when\nthe upstream feed reported one; otherwise falls back to\n`archived_at` (the moment our sweep first observed the event\nhad disappeared from the feed). NULL only for active events\nwith no upstream-reported end — i.e., events that are still\nongoing. Use this field for \"incident duration\" analytics.\n",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "end_time": {
            "description": "Upstream-reported end time. NULL for events the upstream feed\nnever assigned an explicit end (the common case — most 511 feeds\nsimply drop resolved incidents rather than emitting a final\ntimestamp).\n",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "estimated_end_time": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "estimated_start_time": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "jurisdiction": {
            "type": "string"
          },
          "lanes_affected": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_updated": {
            "format": "date-time",
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "location": {
            "description": "GeoJSON geometry (Point, LineString, or Polygon) describing where the event applies.",
            "type": "object"
          },
          "longitude": {
            "type": "number"
          },
          "metadata": {
            "description": "Raw upstream attributes preserved as opaque JSON. Shape varies by source.",
            "type": "object"
          },
          "road_class": {
            "enum": [
              "interstate",
              "us_highway",
              "state_highway",
              "local"
            ],
            "type": "string"
          },
          "severity": {
            "enum": [
              "minor",
              "moderate",
              "major",
              "critical"
            ],
            "type": "string"
          },
          "source": {
            "description": "Jurisdiction code",
            "type": "string"
          },
          "source_created_at": {
            "description": "Upstream-reported creation timestamp, when available.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "source_id": {
            "type": "string"
          },
          "source_updated_at": {
            "description": "Upstream-reported last-modified timestamp, when available.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "start_time": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "enum": [
              "active",
              "archived"
            ],
            "type": "string"
          },
          "sub_type": {
            "description": "Optional additive second-level classification under `type`\n(e.g. `type=incident`, `sub_type=accident`). Omitted when the event\ncould not be classified. Never replaces `type` — existing `type`\nfilters are unaffected.\n",
            "enum": [
              "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"
            ],
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "enum": [
              "incident",
              "construction",
              "closure",
              "special_event",
              "weather",
              "road_condition",
              "planned",
              "hazard"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "WeatherCorrelationStats": {
        "properties": {
          "avg_severity": {
            "type": "number"
          },
          "avg_temp_c": {
            "type": "number"
          },
          "avg_visibility": {
            "type": "number"
          },
          "avg_wind_speed": {
            "type": "number"
          },
          "event_count": {
            "type": "integer"
          },
          "precipitation": {
            "type": "string"
          },
          "road_surface": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyHeader": {
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      },
      "ApiKeyQuery": {
        "in": "query",
        "name": "api_key",
        "type": "apiKey"
      },
      "BearerAuth": {
        "bearerFormat": "JWT",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {
      "email": "support@napspan.com"
    },
    "description": "Aggregated European traffic data API. Normalizes DATEX II and national mobility-data\nsources from European countries into a single REST API.\n\n## Authentication\n- **API Key**: Pass via `X-API-Key` header or `api_key` query parameter\n- **Admin**: Bearer JWT token via `Authorization: Bearer \u003ctoken\u003e`\n- **Customer Portal**: Bearer JWT token via `Authorization: Bearer \u003ctoken\u003e`\n\n## Plan Limits\n| Plan | RPM | Daily Requests | Jurisdictions | Results/Page | GeoJSON | Analytics | Truck | Data Delay | Trial |\n|------|-----|----------------|---------------|--------------|---------|-----------|-------|------------|-------|\n| Free | 60 | 1,000 | 2 | 100 | No | No | No | 15 min | 14 days |\n| Starter | 300 | 50,000 | 10 | 500 | Yes | No | No | Real-time | - |\n| Pro | 1,000 | 500,000 | Unlimited | 1,000 | Yes | Yes | Yes | Real-time | - |\n| Enterprise | 5,000 | Unlimited | Unlimited | Unlimited | Yes | Yes | Yes | Real-time | - |\n",
    "license": {
      "name": "Proprietary"
    },
    "title": "NAPSPAN Traffic API",
    "version": "1.0.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/analytics/changes": {
      "get": {
        "description": "Latest event lifecycle changes across all jurisdictions. Requires\nPro+ plan. Archival entries carry `new_value=archived` or\n`new_value=stale_sweep` — see `archive_reason` on the\n`TrafficEvent` schema for what each means.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "change_type": "severity_change",
                      "event_id": "on-evt-401-001",
                      "event_type": "incident",
                      "id": 45,
                      "jurisdiction": "ON",
                      "new_value": "major",
                      "old_value": "moderate",
                      "recorded_at": "2026-03-29T14:35:00Z",
                      "road_name": "Highway 401",
                      "severity": "major",
                      "source": "ON"
                    },
                    {
                      "change_type": "end_time_change",
                      "event_id": "ga-evt-i75-002",
                      "event_type": "construction",
                      "id": 44,
                      "jurisdiction": "GA",
                      "new_value": "2026-04-15T18:00:00Z",
                      "old_value": "2026-04-10T18:00:00Z",
                      "recorded_at": "2026-03-29T10:00:00Z",
                      "road_name": "I-75",
                      "severity": "moderate",
                      "source": "ga"
                    }
                  ],
                  "has_more": false,
                  "limit": 100,
                  "offset": 0,
                  "total": 2
                }
              }
            },
            "description": "Paginated recent changes"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "analytics access requires Pro plan or higher"
                }
              }
            },
            "description": "Analytics not available on current plan"
          }
        },
        "summary": "Recent changes feed",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/clearance": {
      "get": {
        "description": "Event clearance time statistics. Requires Pro+ plan.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "avg_minutes": 62.8,
                    "count": 320,
                    "event_type": "incident",
                    "jurisdiction": "ON",
                    "max_minutes": 540,
                    "min_minutes": 8,
                    "p50_minutes": 45.2,
                    "p95_minutes": 180.5,
                    "severity": "major"
                  },
                  {
                    "avg_minutes": 7200,
                    "count": 150,
                    "event_type": "construction",
                    "jurisdiction": "GA",
                    "max_minutes": 43200,
                    "min_minutes": 360,
                    "p50_minutes": 4320,
                    "p95_minutes": 21600,
                    "severity": "moderate"
                  }
                ]
              }
            },
            "description": "Clearance statistics"
          }
        },
        "summary": "Clearance times (P50/P95)",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/corridors": {
      "get": {
        "description": "Event frequency and clearance by road corridor. Requires Pro+ plan.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "avg_severity": 2.6,
                    "event_count": 245,
                    "jurisdiction": "ON",
                    "last_event": "2026-03-29T14:35:00Z",
                    "road_name": "Highway 401"
                  },
                  {
                    "avg_severity": 2.1,
                    "event_count": 112,
                    "jurisdiction": "GA",
                    "last_event": "2026-03-29T11:08:00Z",
                    "road_name": "I-75"
                  }
                ]
              }
            },
            "description": "Corridor stats"
          }
        },
        "summary": "Corridor reliability",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/event-weather/{id}": {
      "get": {
        "description": "Returns the weather conditions near an event at the time it was created.\nCaptures nearest weather station data (temperature, wind, visibility,\nprecipitation, road surface, humidity) via PostGIS spatial query.\nRequires Pro+ plan.\n",
        "parameters": [
          {
            "description": "Event ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "created_at": "2026-03-29T14:30:00Z",
                  "distance_km": 3.2,
                  "event_id": "on-evt-401-001",
                  "humidity": 88,
                  "id": 42,
                  "precipitation": "snow",
                  "reading_recorded_at": "2026-03-29T14:28:00Z",
                  "road_surface": "icy",
                  "temperature": -2.5,
                  "visibility": 1.5,
                  "wind_direction": "NW",
                  "wind_speed": 35
                },
                "schema": {
                  "$ref": "#/components/schemas/EventWeatherSnapshot"
                }
              }
            },
            "description": "Weather snapshot for the event"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "no weather snapshot for this event"
                }
              }
            },
            "description": "No weather snapshot for this event"
          }
        },
        "summary": "Event weather snapshot",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/feature-history": {
      "get": {
        "description": "Lifecycle changes for features. Requires Pro+ plan.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "change_type": "deactivated",
                    "changed_field": "is_active",
                    "feature_id": "on-cam-401-yonge",
                    "feature_type": "cameras",
                    "id": 4011,
                    "new_value": "false",
                    "old_value": "true",
                    "recorded_at": "2026-03-15T08:00:00Z",
                    "source": "ON"
                  },
                  {
                    "change_type": "reactivated",
                    "changed_field": "is_active",
                    "feature_id": "on-cam-401-yonge",
                    "feature_type": "cameras",
                    "id": 4012,
                    "new_value": "true",
                    "old_value": "false",
                    "recorded_at": "2026-03-16T06:00:00Z",
                    "source": "ON"
                  }
                ]
              }
            },
            "description": "Feature history entries"
          }
        },
        "summary": "Feature history",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/forecast": {
      "get": {
        "description": "Empirical incident probability and expected delay for a road segment by day-of-week and hour, derived from historical aggregates. Omit dow/hour to get the full 7x24 grid. Thin segments degrade to corridor then jurisdiction basis; sample_size, confidence and basis report how much data backs each bucket. Requires Pro+ plan.",
        "parameters": [
          {
            "description": "Jurisdiction code (e.g. GA).",
            "in": "query",
            "name": "jurisdiction",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Road name (e.g. I-85).",
            "in": "query",
            "name": "road",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional road class filter.",
            "in": "query",
            "name": "road_class",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Day of week (0=Sunday..6=Saturday). Omit for the full grid.",
            "in": "query",
            "name": "dow",
            "schema": {
              "maximum": 6,
              "minimum": 0,
              "type": "integer"
            }
          },
          {
            "description": "Hour of day (0-23). Omit for the full grid.",
            "in": "query",
            "name": "hour",
            "schema": {
              "maximum": 23,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "bucket": {
                    "basis": "segment",
                    "confidence": "high",
                    "dow": 1,
                    "expected_count": 0.6,
                    "expected_delay_min": 47.2,
                    "hour": 17,
                    "p95_delay_min": 120,
                    "probability": 0.35,
                    "probability_ci": [
                      0.18,
                      0.57
                    ],
                    "sample_size": 12
                  },
                  "computed_at": "2026-06-11T03:00:00Z",
                  "jurisdiction": "GA",
                  "road": "I-85",
                  "road_class": "interstate",
                  "window_days": 90
                }
              }
            },
            "description": "Segment forecast — a single bucket when dow+hour are pinned, otherwise the full grid in buckets[]"
          }
        },
        "summary": "Incident-probability forecast",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/history/{id}": {
      "get": {
        "description": "Lifecycle changes for a specific event. Requires Pro+ plan.\n\n`change_type` values include `created`, `severity_change`,\n`lanes_change`, `end_time_change`, `estimated_end_time_change`,\n`archived`, and `stale_sweep`. The last two both mean the event\nis no longer active — see `archive_reason` on the `TrafficEvent`\nschema for the distinction.\n",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "change_type": "created",
                    "event_id": "on-evt-401-001",
                    "event_type": "incident",
                    "id": 0,
                    "jurisdiction": "ON",
                    "recorded_at": "2026-03-29T14:30:00Z",
                    "road_name": "Highway 401",
                    "severity": "moderate",
                    "source": "ON"
                  },
                  {
                    "change_type": "severity_change",
                    "event_id": "on-evt-401-001",
                    "event_type": "incident",
                    "id": 2,
                    "jurisdiction": "ON",
                    "new_value": "major",
                    "old_value": "moderate",
                    "recorded_at": "2026-03-29T14:35:00Z",
                    "road_name": "Highway 401",
                    "severity": "major",
                    "source": "ON"
                  },
                  {
                    "change_type": "lanes_change",
                    "event_id": "on-evt-401-001",
                    "event_type": "incident",
                    "id": 3,
                    "jurisdiction": "ON",
                    "new_value": "2 of 4 lanes blocked",
                    "old_value": "1 of 4 lanes blocked",
                    "recorded_at": "2026-03-29T14:40:00Z",
                    "road_name": "Highway 401",
                    "severity": "major",
                    "source": "ON"
                  }
                ]
              }
            },
            "description": "Array of history entries"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "analytics access requires Pro plan or higher"
                }
              }
            },
            "description": "Analytics not available on current plan"
          }
        },
        "summary": "Event history timeline",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/hotspots": {
      "get": {
        "description": "Geographic clustering of events. Requires Pro+ plan.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "avg_severity": 2.8,
                    "event_count": 28,
                    "latitude": 43.651,
                    "longitude": -79.347,
                    "top_road": "Highway 401"
                  },
                  {
                    "avg_severity": 2.2,
                    "event_count": 15,
                    "latitude": 33.758,
                    "longitude": -84.392,
                    "top_road": "I-75"
                  }
                ]
              }
            },
            "description": "Hotspot clusters"
          }
        },
        "summary": "Hotspot clustering",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/scorecard": {
      "get": {
        "description": "Per-jurisdiction rollup (incident volume, clearance speed, severity mix, construction schedule drift, road-condition coverage, source uptime) with a 0-100 composite score, optionally ranked. Returns all active jurisdictions by default. Requires Pro+ plan.",
        "parameters": [
          {
            "description": "Restrict to one jurisdiction code (e.g. GA).",
            "in": "query",
            "name": "jurisdiction",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter by country.",
            "in": "query",
            "name": "country",
            "schema": {
              "enum": [
                "US",
                "CA"
              ],
              "type": "string"
            }
          },
          {
            "description": "Window start (YYYY-MM-DD, inclusive).",
            "in": "query",
            "name": "from",
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Window end (YYYY-MM-DD, exclusive upper bound).",
            "in": "query",
            "name": "to",
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Ranking metric.",
            "in": "query",
            "name": "rank_by",
            "schema": {
              "default": "score",
              "enum": [
                "score",
                "incident_volume",
                "avg_clearance_minutes",
                "source_uptime",
                "construction_overrun_avg_days"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "default": "desc",
              "enum": [
                "asc",
                "desc"
              ],
              "type": "string"
            }
          },
          {
            "description": "Max rows; defaults to all ~90 jurisdictions.",
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 200,
              "maximum": 200,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "avg_clearance_minutes": 48.2,
                    "avg_severity": 2.4,
                    "construction_jobs": 54,
                    "construction_overrun_avg_days": 3.1,
                    "construction_reschedule_avg": 0.7,
                    "country": "CA",
                    "event_volume": 1240,
                    "incident_volume": 612,
                    "jurisdiction": "ON",
                    "name": "Ontario",
                    "p95_clearance_minutes": 190,
                    "rank": 1,
                    "road_condition_features": 320,
                    "road_condition_updates": 1450,
                    "score": 87.4,
                    "severity_mix": {
                      "critical": 18,
                      "major": 140,
                      "minor": 680,
                      "moderate": 402
                    },
                    "source_uptime": 0.991,
                    "window": {
                      "from": "2026-05-01",
                      "to": "2026-06-11"
                    }
                  }
                ]
              }
            },
            "description": "Ranked jurisdiction scorecards"
          }
        },
        "summary": "Jurisdiction scorecard",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/trends": {
      "get": {
        "description": "Time-series event counts. Requires Pro+ plan.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "count": 58,
                    "timestamp": "2026-03-29T00:00:00Z",
                    "type": "incident"
                  },
                  {
                    "count": 62,
                    "timestamp": "2026-03-29T00:00:00Z",
                    "type": "construction"
                  },
                  {
                    "count": 45,
                    "timestamp": "2026-03-28T00:00:00Z",
                    "type": "incident"
                  },
                  {
                    "count": 60,
                    "timestamp": "2026-03-28T00:00:00Z",
                    "type": "construction"
                  }
                ]
              }
            },
            "description": "Trend data points"
          }
        },
        "summary": "Event trends",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/weather": {
      "get": {
        "description": "Historical weather station data. Requires Pro+ plan.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "feature_id": "ga-wx-i75-atl",
                    "humidity": 62,
                    "id": 88412,
                    "precipitation": "none",
                    "recorded_at": "2026-03-29T14:45:00Z",
                    "road_surface": "dry",
                    "source": "ga",
                    "temperature": 18.5,
                    "visibility": 16,
                    "wind_direction": "NW",
                    "wind_speed": 12.3
                  }
                ]
              }
            },
            "description": "Weather readings"
          }
        },
        "summary": "Weather readings history",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/weather-correlation": {
      "get": {
        "description": "Returns aggregated weather conditions across events, grouped by\nprecipitation type and road surface. Shows how weather conditions\ncorrelate with incident frequency and severity. Requires Pro+ plan.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "description": "Filter by event type",
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "severity",
            "schema": {
              "enum": [
                "minor",
                "moderate",
                "major",
                "critical"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from",
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "to",
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Max station distance in km (default 50)",
            "in": "query",
            "name": "max_dist_km",
            "schema": {
              "default": 50,
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 50,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "avg_severity": 3.2,
                    "avg_temp_c": -5.1,
                    "avg_visibility": 2.3,
                    "avg_wind_speed": 28.4,
                    "event_count": 84,
                    "precipitation": "snow",
                    "road_surface": "icy"
                  },
                  {
                    "avg_severity": 2.1,
                    "avg_temp_c": 8.3,
                    "avg_visibility": 8.5,
                    "avg_wind_speed": 18.7,
                    "event_count": 156,
                    "precipitation": "rain",
                    "road_surface": "wet"
                  },
                  {
                    "avg_severity": 1.8,
                    "avg_temp_c": 15.2,
                    "avg_visibility": 16,
                    "avg_wind_speed": 10.1,
                    "event_count": 320,
                    "precipitation": "none",
                    "road_surface": "dry"
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WeatherCorrelationStats"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Correlation statistics"
          }
        },
        "summary": "Weather-incident correlation",
        "tags": [
          "analytics"
        ]
      }
    },
    "/data-sources": {
      "get": {
        "description": "Returns the licensing and attribution catalogue for every data source.\nPublic — no API key required.\n\nUse this to render the credits your application must display. Sources\nunder open licenses that require attribution (CC-BY, OGL v3.0, ODbL,\nNLOD, Licence Ouverte, DL-DE→BY) carry the exact credit string in\n`attribution`. List responses (`/events`, `/features`) also embed an\ninline `attribution[]` for the sources present in that response.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/SourceCredit"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The data-source catalogue"
          }
        },
        "security": [],
        "summary": "Data-source licensing \u0026 attribution catalogue",
        "tags": [
          "data-sources"
        ]
      }
    },
    "/events": {
      "get": {
        "description": "Paginated list of traffic events with filters. Free plan requires\njurisdiction parameter.\n\n**Batch lookup**: pass `?ids=id1,id2,...` to fetch a known set of\nevents by id. When `ids` is set, all other filters except plan\ndata delay are ignored and pagination is disabled — the response\nalways returns the full set in one page. Archived events are\nfiltered per plan: non-analytics plans\n(`allow_analytics: false`) receive only events whose effective\nstart_time is within the last 7 days. Active events are always\nreturned regardless of age. `total` reflects the post-filter\ncount. The batch size is capped by the plan's `max_batch_size`.\n",
        "parameters": [
          {
            "description": "Comma-separated event IDs for batch lookup. Mutually exclusive\nwith the filter parameters below (when present, other filters\nare ignored). Bounded by the plan's `max_batch_size`.\n",
            "in": "query",
            "name": "ids",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "$ref": "#/components/parameters/bbox"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lng"
          },
          {
            "$ref": "#/components/parameters/radius_km"
          },
          {
            "description": "Filter by event type",
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter by second-level classification (e.g. `accident`). Additive —\nomit to match all sub_types. See the `sub_type` enum on the\nTrafficEvent schema for the full vocabulary.\n",
            "in": "query",
            "name": "sub_type",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "severity",
            "schema": {
              "enum": [
                "minor",
                "moderate",
                "major",
                "critical"
              ],
              "type": "string"
            }
          },
          {
            "description": "`active` (default) returns active events, `archived` returns\narchived events only, `all` disables the filter and returns both.\n\nPlans without analytics access (`allow_analytics: false`) may\nrequest `archived` or `all` only when paired with a\n`start_time_from` no older than 7 days — otherwise the\nrequest is rejected with 403. Plans with analytics access\nhave no such restriction.\n",
            "in": "query",
            "name": "status",
            "schema": {
              "default": "active",
              "enum": [
                "active",
                "archived",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filter by road name (partial match)",
            "in": "query",
            "name": "road",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Lower bound (inclusive, RFC3339) on the event's start time.\nMatches `COALESCE(start_time, created_at)`, so events whose\nupstream feed never populated `start_time` still surface via\ntheir ingest time. When either time-range parameter is set\nand `status` is omitted, the default flips from `active` to\n`all` so archived (historical) events are returned. Lookback\nbeyond the last 7 days requires a plan with analytics\naccess (`allow_analytics: true`).\n",
            "in": "query",
            "name": "start_time_from",
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Upper bound (inclusive, RFC3339) on the event's start time.\nSee `start_time_from` for column semantics. Without a\n`start_time_from` companion the range is treated as\nopen-ended on the lower end and therefore counts as deep\nhistory (analytics plan required).\n",
            "in": "query",
            "name": "start_time_to",
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "affected_roads": [
                        "Highway 401"
                      ],
                      "created_at": "2026-03-29T14:30:00Z",
                      "description": "Two right lanes blocked. Emergency services on scene. Expect delays of 30+ minutes.",
                      "direction": "westbound",
                      "end_time": null,
                      "estimated_end_time": "2026-03-29T17:00:00Z",
                      "id": "on-evt-401-001",
                      "jurisdiction": "ON",
                      "lanes_affected": "2 of 4 lanes blocked",
                      "last_updated": "2026-03-29T14:45:00Z",
                      "latitude": 43.65,
                      "location": {
                        "coordinates": [
                          -79.38,
                          43.65
                        ],
                        "type": "Point"
                      },
                      "longitude": -79.38,
                      "road_class": "interstate",
                      "severity": "major",
                      "source": "on",
                      "source_id": "EVT-2026-04521",
                      "start_time": "2026-03-29T14:30:00Z",
                      "status": "active",
                      "title": "Multi-vehicle collision on Highway 401 WB near Yonge St",
                      "type": "incident"
                    },
                    {
                      "affected_roads": [
                        "I-75"
                      ],
                      "created_at": "2026-03-25T06:00:00Z",
                      "description": "Road resurfacing. Right lane closed from mile marker 248 to 251.",
                      "direction": "northbound",
                      "end_time": null,
                      "estimated_end_time": "2026-04-15T18:00:00Z",
                      "id": "ga-evt-i75-002",
                      "jurisdiction": "GA",
                      "lanes_affected": "1 of 3 lanes closed",
                      "last_updated": "2026-03-29T08:00:00Z",
                      "latitude": 33.75,
                      "location": {
                        "coordinates": [
                          -84.39,
                          33.75
                        ],
                        "type": "Point"
                      },
                      "longitude": -84.39,
                      "road_class": "interstate",
                      "severity": "moderate",
                      "source": "ga",
                      "source_id": "GA-CONST-8832",
                      "start_time": "2026-03-25T06:00:00Z",
                      "status": "active",
                      "title": "Lane closure on I-75 NB near Exit 249",
                      "type": "construction"
                    }
                  ],
                  "has_more": false,
                  "limit": 100,
                  "offset": 0,
                  "total": 2
                },
                "schema": {
                  "$ref": "#/components/schemas/PagedEvents"
                }
              }
            },
            "description": "Paginated events"
          },
          "403": {
            "content": {
              "application/json": {
                "examples": {
                  "archivedStatusGate": {
                    "summary": "Archived/all status without a recent start_time_from",
                    "value": {
                      "error": "status=archived requires start_time_from within the last 168h0m0s on the Free plan, or a plan with analytics access"
                    }
                  },
                  "jurisdictionRequired": {
                    "summary": "Free plan requires jurisdiction",
                    "value": {
                      "error": "jurisdiction parameter required on Free plan"
                    }
                  },
                  "timeRangeGate": {
                    "summary": "start_time_from older than 7 days",
                    "value": {
                      "error": "historical event search beyond 168h0m0s requires a plan with analytics access — the Free plan only allows the last 168h0m0s"
                    }
                  }
                }
              }
            },
            "description": "Plan limit exceeded. Common causes: jurisdiction required on\nFree, trial expired, `start_time_from`/`start_time_to`\nreaches beyond 7 days without analytics access,\n`status=archived` or `status=all` requested without a\n`start_time_from` inside the 7-day window.\n"
          }
        },
        "summary": "List traffic events",
        "tags": [
          "events"
        ]
      }
    },
    "/events/geojson": {
      "get": {
        "description": "Returns events as a GeoJSON FeatureCollection. Requires Starter+ plan.",
        "parameters": [
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "$ref": "#/components/parameters/bbox"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lng"
          },
          {
            "$ref": "#/components/parameters/radius_km"
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "severity",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`active` (default) returns active events, `archived` returns\narchived events only, `all` disables the filter and returns both.\n\n`/events/geojson` exposes no `start_time_from` parameter, so\nfor callers on plans without analytics access\n(`allow_analytics: false`) the effective allowed values\ncollapse to `active` only — requesting `archived` or `all`\nreturns 403. Plans with analytics access are unrestricted.\n",
            "in": "query",
            "name": "status",
            "schema": {
              "default": "active",
              "enum": [
                "active",
                "archived",
                "all"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/geo+json": {
                "example": {
                  "features": [
                    {
                      "geometry": {
                        "coordinates": [
                          -79.38,
                          43.65
                        ],
                        "type": "Point"
                      },
                      "properties": {
                        "affected_roads": [
                          "Highway 401"
                        ],
                        "direction": "westbound",
                        "id": "on-evt-401-001",
                        "jurisdiction": "ON",
                        "severity": "major",
                        "source": "on",
                        "start_time": "2026-03-29T14:30:00Z",
                        "status": "active",
                        "title": "Multi-vehicle collision on Highway 401 WB near Yonge St",
                        "type": "incident"
                      },
                      "type": "Feature"
                    }
                  ],
                  "type": "FeatureCollection"
                },
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "GeoJSON FeatureCollection"
          },
          "403": {
            "content": {
              "application/json": {
                "examples": {
                  "archivedStatusGate": {
                    "summary": "Archived status requires analytics access",
                    "value": {
                      "error": "status=archived requires start_time_from within the last 168h0m0s on the Starter plan, or a plan with analytics access"
                    }
                  },
                  "geojsonGate": {
                    "summary": "GeoJSON not available on Free",
                    "value": {
                      "error": "GeoJSON access requires Starter plan or higher"
                    }
                  }
                }
              }
            },
            "description": "GeoJSON requires Starter+, or `status=archived`/`all` was\nrequested without analytics access.\n"
          }
        },
        "summary": "Events as GeoJSON",
        "tags": [
          "events"
        ]
      }
    },
    "/events/{id}": {
      "get": {
        "description": "Returns full detail for a single event. Active events are always\nreturned. Archived events are gated by plan: on plans without\nanalytics access (`allow_analytics: false`) the request is\nrejected with 403 when the event's effective start_time\n(`COALESCE(start_time, created_at)`) is older than 7 days.\nPlans with analytics access have no such restriction.\n",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "affected_roads": [
                    "Highway 401"
                  ],
                  "created_at": "2026-03-29T14:30:00Z",
                  "description": "Two right lanes blocked. Emergency services on scene. Expect delays of 30+ minutes.",
                  "direction": "westbound",
                  "end_time": null,
                  "estimated_end_time": "2026-03-29T17:00:00Z",
                  "id": "on-evt-401-001",
                  "jurisdiction": "ON",
                  "lanes_affected": "2 of 4 lanes blocked",
                  "last_updated": "2026-03-29T14:45:00Z",
                  "latitude": 43.65,
                  "location": {
                    "coordinates": [
                      -79.38,
                      43.65
                    ],
                    "type": "Point"
                  },
                  "longitude": -79.38,
                  "road_class": "interstate",
                  "severity": "major",
                  "source": "on",
                  "source_id": "EVT-2026-04521",
                  "start_time": "2026-03-29T14:30:00Z",
                  "status": "active",
                  "title": "Multi-vehicle collision on Highway 401 WB near Yonge St",
                  "type": "incident"
                },
                "schema": {
                  "$ref": "#/components/schemas/TrafficEvent"
                }
              }
            },
            "description": "Event detail"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "event on-evt-401-001 is archived beyond 168h0m0s — historical detail requires a plan with analytics access (current plan: Free)"
                }
              }
            },
            "description": "Archived event beyond the caller's plan window."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "event not found"
                }
              }
            },
            "description": "Event not found"
          }
        },
        "summary": "Get event by ID",
        "tags": [
          "events"
        ]
      }
    },
    "/features": {
      "get": {
        "description": "Paginated features filtered by type or group. Either `type` or `group` is required. Free plan requires jurisdiction parameter.",
        "parameters": [
          {
            "description": "Feature type: cameras, rest_areas, weigh_stations, weather_stations, ev_charging, signs, road_conditions, etc. Required unless `group` is provided.",
            "in": "query",
            "name": "type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Feature group id. Convenience expansion to all feature_types in the group. See GET /features/groups.",
            "in": "query",
            "name": "group",
            "required": false,
            "schema": {
              "enum": [
                "imagery",
                "weather",
                "road_conditions",
                "traffic_performance",
                "planned_events",
                "alerts_advisories",
                "wildfires",
                "trucking",
                "traveler_services",
                "fuel_charging",
                "borders",
                "ferries",
                "transit",
                "tolls",
                "static_infrastructure",
                "operations"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "$ref": "#/components/parameters/bbox"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lng"
          },
          {
            "$ref": "#/components/parameters/radius_km"
          },
          {
            "description": "Tri-state filter on `is_active`. `true` returns active only,\n`false` returns inactive (archived) only, omitted returns both.\n",
            "in": "query",
            "name": "active",
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "description": "Traffic camera - Highway 401 westbound at Yonge Street",
                      "direction": "westbound",
                      "feature_type": "cameras",
                      "has_details": true,
                      "id": "on-cam-401-yonge",
                      "is_active": true,
                      "jurisdiction": "ON",
                      "last_updated": "2026-03-29T14:50:00Z",
                      "latitude": 43.6532,
                      "longitude": -79.3832,
                      "name": "Hwy 401 at Yonge St",
                      "properties": {
                        "url": "https://511on.ca/cameras/cam_401_yonge.jpg",
                        "video_url": null
                      },
                      "road_name": "Highway 401",
                      "source": "on"
                    },
                    {
                      "description": "RWIS weather station on I-75 near downtown Atlanta",
                      "direction": null,
                      "feature_type": "weather_stations",
                      "has_details": false,
                      "id": "ga-wx-i75-atl",
                      "is_active": true,
                      "jurisdiction": "GA",
                      "last_updated": "2026-03-29T14:45:00Z",
                      "latitude": 33.749,
                      "longitude": -84.388,
                      "name": "I-75 Atlanta Weather Station",
                      "properties": {
                        "humidity": 62,
                        "precipitation": "none",
                        "road_surface": "dry",
                        "temperature": 18.5,
                        "visibility": 16,
                        "wind_direction": "NW",
                        "wind_speed": 12.3
                      },
                      "road_name": "I-75",
                      "source": "ga"
                    }
                  ],
                  "has_more": false,
                  "limit": 100,
                  "offset": 0,
                  "total": 2
                },
                "schema": {
                  "$ref": "#/components/schemas/PagedFeatures"
                }
              }
            },
            "description": "Paginated features"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "type parameter is required"
                }
              }
            },
            "description": "Missing type parameter"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "jurisdiction parameter required on Free plan"
                }
              }
            },
            "description": "Plan limit exceeded"
          }
        },
        "summary": "List features",
        "tags": [
          "features"
        ]
      }
    },
    "/features/details/batch": {
      "post": {
        "description": "Fan-out variant of `/features/{id}/details` for callers that need to\nresolve many features at once. POST is used so the id list isn't\nconstrained by URL length.\n\nPer-id caching is identical to the single endpoint (Redis-backed\nkeyed on the id), so a batch that's a superset of previously\nrequested ids costs nothing extra for the overlap. Partial success\nis preserved: a single failed upstream fetch sets `cache: \"error\"`\nwith an `error` string on that entry without failing the whole\nrequest. Missing ids land in the response with `not_found: true`.\n\nBounded by the plan's `max_batch_size`.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "ids": [
                  "on-cam-401-yonge",
                  "ga-cam-i75-249",
                  "missing-id"
                ]
              },
              "schema": {
                "properties": {
                  "ids": {
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "type": "array"
                  }
                },
                "required": [
                  "ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "count": 3,
                  "data": [
                    {
                      "cache": "hit",
                      "data": {
                        "feature_type": "cameras",
                        "id": "on-cam-401-yonge",
                        "jurisdiction": "ON",
                        "name": "Hwy 401 at Yonge St",
                        "properties": {
                          "url": "https://511on.ca/cameras/cam_401_yonge.jpg"
                        },
                        "source": "on"
                      },
                      "detail_available": true,
                      "id": "on-cam-401-yonge"
                    },
                    {
                      "cache": "miss",
                      "data": {
                        "feature_type": "cameras",
                        "id": "ga-cam-i75-249",
                        "jurisdiction": "GA",
                        "name": "I-75 at Exit 249",
                        "source": "ga"
                      },
                      "detail_available": true,
                      "id": "ga-cam-i75-249"
                    },
                    {
                      "cache": "",
                      "detail_available": false,
                      "id": "missing-id",
                      "not_found": true
                    }
                  ]
                }
              }
            },
            "description": "Per-id detail results (one entry per requested id, in order)"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "ids is required and must contain at least one non-empty id"
                }
              }
            },
            "description": "Invalid JSON body or empty ids array"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "batch too large: 150 ids requested, max 100 on the Pro plan — upgrade for more"
                }
              }
            },
            "description": "Batch exceeds plan's max_batch_size"
          }
        },
        "summary": "Feature detail batch (lazy-loaded)",
        "tags": [
          "features"
        ]
      }
    },
    "/features/geojson": {
      "get": {
        "description": "Returns features as a GeoJSON FeatureCollection. Requires Starter+ plan.",
        "parameters": [
          {
            "in": "query",
            "name": "type",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "$ref": "#/components/parameters/bbox"
          },
          {
            "description": "Defaults to active-only for map rendering. `active=false` drops the\nfilter and returns both active and inactive features (asymmetric vs\n`/features` for back-compat with map clients — use `/features?active=false`\nto fetch inactive-only).\n",
            "in": "query",
            "name": "active",
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/geo+json": {
                "example": {
                  "features": [
                    {
                      "geometry": {
                        "coordinates": [
                          -79.3832,
                          43.6532
                        ],
                        "type": "Point"
                      },
                      "properties": {
                        "feature_type": "cameras",
                        "id": "on-cam-401-yonge",
                        "is_active": true,
                        "jurisdiction": "ON",
                        "name": "Hwy 401 at Yonge St",
                        "source": "on"
                      },
                      "type": "Feature"
                    }
                  ],
                  "type": "FeatureCollection"
                },
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "GeoJSON FeatureCollection"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "GeoJSON access requires Starter plan or higher"
                }
              }
            },
            "description": "GeoJSON not available on Free plan"
          }
        },
        "summary": "Features as GeoJSON",
        "tags": [
          "features"
        ]
      }
    },
    "/features/groups": {
      "get": {
        "description": "Returns the 16 feature-type groups with their member feature_types. Use the group `id` with `GET /features?group=\u003cid\u003e` to fetch all features in that group.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "description": "Live traffic camera feeds",
                    "feature_types": [
                      "cameras"
                    ],
                    "id": "imagery",
                    "name": "Imagery",
                    "sort_order": 1
                  },
                  {
                    "description": "Weather stations, forecasts, alerts and warnings",
                    "feature_types": [
                      "weather_stations",
                      "regional_weather",
                      "weather_forecasts",
                      "weather_alerts",
                      "wind_warnings"
                    ],
                    "id": "weather",
                    "name": "Weather \u0026 Environment",
                    "sort_order": 2
                  },
                  {
                    "description": "Truck restrictions, routes, parking, inspections",
                    "feature_types": [
                      "truck_restrictions",
                      "weight_restrictions",
                      "bridge_clearances",
                      "truck_routes",
                      "freight_corridors",
                      "truck_parking",
                      "truck_rest_areas",
                      "weigh_stations",
                      "inspection_stations"
                    ],
                    "id": "trucking",
                    "name": "Trucking \u0026 Commercial Vehicles",
                    "sort_order": 8
                  }
                ]
              }
            },
            "description": "Feature group taxonomy"
          }
        },
        "summary": "List feature groups (taxonomy)",
        "tags": [
          "features"
        ]
      }
    },
    "/features/types": {
      "get": {
        "description": "Returns all feature types with total and active counts",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "active_count": 11893,
                    "count": 12450,
                    "type": "cameras"
                  },
                  {
                    "active_count": 3142,
                    "count": 3200,
                    "type": "weather_stations"
                  },
                  {
                    "active_count": 4651,
                    "count": 4870,
                    "type": "signs"
                  },
                  {
                    "active_count": 1580,
                    "count": 1580,
                    "type": "rest_areas"
                  },
                  {
                    "active_count": 98500,
                    "count": 98500,
                    "type": "ev_charging"
                  },
                  {
                    "active_count": 45200,
                    "count": 45200,
                    "type": "bridge_clearances"
                  },
                  {
                    "active_count": 7200,
                    "count": 8900,
                    "type": "truck_restrictions"
                  },
                  {
                    "active_count": 1850,
                    "count": 2100,
                    "type": "road_conditions"
                  }
                ]
              }
            },
            "description": "Feature type counts"
          }
        },
        "summary": "List feature types",
        "tags": [
          "features"
        ]
      }
    },
    "/features/{id}/details": {
      "get": {
        "description": "Fetches detailed properties for a feature on demand",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "cache": "miss",
                  "data": {
                    "description": "Traffic camera - Highway 401 westbound at Yonge Street",
                    "direction": "westbound",
                    "feature_type": "cameras",
                    "id": "on-cam-401-yonge",
                    "is_active": true,
                    "jurisdiction": "ON",
                    "last_updated": "2026-03-29T14:50:00Z",
                    "latitude": 43.6532,
                    "longitude": -79.3832,
                    "name": "Hwy 401 at Yonge St",
                    "properties": {
                      "url": "https://511on.ca/cameras/cam_401_yonge.jpg",
                      "video_url": "https://511on.ca/cameras/cam_401_yonge.m3u8",
                      "views": [
                        {
                          "name": "Westbound",
                          "url": "https://511on.ca/cameras/cam_401_yonge_wb.jpg"
                        },
                        {
                          "name": "Eastbound",
                          "url": "https://511on.ca/cameras/cam_401_yonge_eb.jpg"
                        }
                      ]
                    },
                    "road_name": "Highway 401",
                    "source": "on"
                  },
                  "detail_available": true
                }
              }
            },
            "description": "Feature with full properties"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "feature not found"
                }
              }
            },
            "description": "Feature not found"
          }
        },
        "summary": "Feature detail (lazy-loaded)",
        "tags": [
          "features"
        ]
      }
    },
    "/fuel-prices": {
      "get": {
        "description": "Returns regional fuel prices. Weekly US state-level (EIA) and monthly Canadian\nprovince-level (StatCan). Use `latest=true` to get only the most recent observation\nper jurisdiction + fuel_type. 24-hour cache (data refreshes daily at the source).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "description": "Filter by country",
            "in": "query",
            "name": "country",
            "schema": {
              "enum": [
                "US",
                "CA"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filter by fuel grade",
            "in": "query",
            "name": "fuel_type",
            "schema": {
              "enum": [
                "regular",
                "midgrade",
                "premium",
                "diesel"
              ],
              "type": "string"
            }
          },
          {
            "description": "Start date (YYYY-MM-DD)",
            "in": "query",
            "name": "from",
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "End date (YYYY-MM-DD)",
            "in": "query",
            "name": "to",
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Return only the most recent observation per jurisdiction + fuel_type",
            "in": "query",
            "name": "latest",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "country": "US",
                      "currency": "USD",
                      "frequency": "weekly",
                      "fuel_type": "regular",
                      "id": 12048,
                      "jurisdiction": "CA",
                      "period_date": "2026-05-05",
                      "price": 4.85,
                      "source": "EIA",
                      "unit": "gallon"
                    },
                    {
                      "country": "CA",
                      "currency": "CAD",
                      "frequency": "monthly",
                      "fuel_type": "regular",
                      "id": 12122,
                      "jurisdiction": "ON",
                      "period_date": "2026-04-01",
                      "price": 1.62,
                      "source": "StatCan",
                      "unit": "litre"
                    }
                  ],
                  "has_more": true,
                  "limit": 100,
                  "offset": 0,
                  "total": 312
                }
              }
            },
            "description": "Paginated fuel prices"
          }
        },
        "summary": "Regional fuel prices",
        "tags": [
          "fuel-prices"
        ]
      }
    },
    "/health": {
      "get": {
        "description": "Returns server status, version, uptime, and the combined live status\nof the Postgres and Redis dependencies in `db` (formatted as\n`\u003cpostgres\u003e/\u003credis\u003e`, each segment `ok` or `down`). Returns 200 when\nboth respond within 2 seconds; otherwise 503 with the same body shape.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "db": "ok/ok",
                  "status": "ok",
                  "uptime": "2h30m",
                  "version": "0.1.0"
                },
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "All dependencies healthy"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "db": "ok/down",
                  "status": "degraded",
                  "uptime": "2h30m",
                  "version": "0.1.0"
                },
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "One or more dependencies unhealthy"
          }
        },
        "security": [],
        "summary": "Health check",
        "tags": [
          "metadata"
        ]
      }
    },
    "/jurisdictions": {
      "get": {
        "description": "Returns all active jurisdictions. By default, only real geographic\njurisdictions (US states + Canadian provinces, `scope=state`) are\nreturned — cross-cutting federal sources and sub-state regional\nfeeds are filtered out so customer pickers stay clean.\n\nUse `?scope=federal` to list cross-cutting federal sources (EIA,\nFHWA, WZDX_NPS), `?scope=regional` for sub-state feeds (511SF,\nWZDX_AUSTIN, ...), or `?scope=all` to return everything.\n\nEach row's `group_id` (when present) points at a record in\n`GET /jurisdictions/groups`: a state's primary feed plus its WZDx\nand sub-state siblings collapsed under one umbrella id.\n",
        "parameters": [
          {
            "description": "Filter by jurisdiction scope; default `state`",
            "in": "query",
            "name": "scope",
            "schema": {
              "default": "state",
              "enum": [
                "state",
                "federal",
                "regional",
                "all"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "code": "ON",
                    "country": "CA",
                    "group_id": null,
                    "is_active": true,
                    "name": "Ontario",
                    "scope": "state"
                  },
                  {
                    "code": "GA",
                    "country": "US",
                    "group_id": null,
                    "is_active": true,
                    "name": "Georgia",
                    "scope": "state"
                  },
                  {
                    "code": "CA",
                    "country": "US",
                    "group_id": "us-ca",
                    "is_active": true,
                    "name": "California",
                    "scope": "state"
                  },
                  {
                    "code": "NY",
                    "country": "US",
                    "group_id": "us-ny",
                    "is_active": true,
                    "name": "New York",
                    "scope": "state"
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Jurisdiction"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of jurisdictions"
          }
        },
        "summary": "List jurisdictions",
        "tags": [
          "metadata"
        ]
      }
    },
    "/jurisdictions/groups": {
      "get": {
        "description": "Returns the canonical bundles for states whose feed is split across\nmultiple codes (state + WZDx + optional CWZ + optional sub-state\nregional). Querying `?jurisdiction=\u003cprimary_jurisdiction_code\u003e` on\n`/events` or `/features` is transparently expanded to all members.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "description": "California feeds (state + WZDx + 511SF Bay Area)",
                    "id": "us-ca",
                    "members": [
                      "CA",
                      "WZDX_CA",
                      "511SF"
                    ],
                    "name": "California",
                    "primary_jurisdiction_code": "CA",
                    "sort_order": 2
                  },
                  {
                    "description": "Colorado feeds (state + WZDx + CWZ)",
                    "id": "us-co",
                    "members": [
                      "CO",
                      "WZDX_CO",
                      "WZDX_CWZ_CO"
                    ],
                    "name": "Colorado",
                    "primary_jurisdiction_code": "CO",
                    "sort_order": 3
                  },
                  {
                    "description": "Texas feeds (state + WZDx + sub-state regional)",
                    "id": "us-tx",
                    "members": [
                      "TX",
                      "WZDX_TX",
                      "WZDX_AUSTIN"
                    ],
                    "name": "Texas",
                    "primary_jurisdiction_code": "TX",
                    "sort_order": 24
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/JurisdictionGroup"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of jurisdiction groups"
          }
        },
        "summary": "List jurisdiction groups",
        "tags": [
          "metadata"
        ]
      }
    },
    "/map/config": {
      "get": {
        "description": "Returns map detail level and other config",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "detail_level": "compact",
                  "max_results": 100,
                  "rpm": 60
                }
              }
            },
            "description": "Map config"
          }
        },
        "summary": "Map configuration",
        "tags": [
          "map"
        ]
      }
    },
    "/map/events": {
      "get": {
        "description": "Public, capped to MAP_MAX_RESULTS, no pagination (offset always 0).\nNo API key required.\n\nMap endpoints always return **active events only** — `status=archived`\nand `status=all` are silently clamped to `active`, and\n`start_time_from`/`start_time_to` are ignored for the analytics\ngate. Historical access requires an authenticated `/events`\ncall with an appropriate plan.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/bbox"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "on-evt-401-001",
                      "jurisdiction": "ON",
                      "last_updated": "2026-03-29T14:45:00Z",
                      "latitude": 43.65,
                      "longitude": -79.38,
                      "road_class": "interstate",
                      "severity": "major",
                      "source": "on",
                      "start_time": "2026-03-29T14:30:00Z",
                      "status": "active",
                      "title": "Multi-vehicle collision on Highway 401 WB",
                      "type": "incident"
                    }
                  ],
                  "has_more": false,
                  "limit": 100,
                  "offset": 0,
                  "total": 1
                }
              }
            },
            "description": "Paginated events"
          }
        },
        "security": [],
        "summary": "Map events",
        "tags": [
          "map"
        ]
      }
    },
    "/map/events/geojson": {
      "get": {
        "description": "Public, same restrictions as /map/events (capped, no pagination)",
        "responses": {
          "200": {
            "content": {
              "application/geo+json": {
                "example": {
                  "features": [
                    {
                      "geometry": {
                        "coordinates": [
                          -79.38,
                          43.65
                        ],
                        "type": "Point"
                      },
                      "properties": {
                        "id": "on-evt-401-001",
                        "jurisdiction": "ON",
                        "severity": "major",
                        "source": "on",
                        "title": "Multi-vehicle collision on Highway 401 WB",
                        "type": "incident"
                      },
                      "type": "Feature"
                    }
                  ],
                  "type": "FeatureCollection"
                }
              }
            },
            "description": "GeoJSON FeatureCollection"
          }
        },
        "summary": "Map events GeoJSON",
        "tags": [
          "map"
        ]
      }
    },
    "/map/features": {
      "get": {
        "description": "Public, capped to MAP_MAX_RESULTS, no pagination (offset always 0)",
        "parameters": [
          {
            "in": "query",
            "name": "type",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/jurisdiction"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/bbox"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "feature_type": "cameras",
                      "id": "on-cam-401-yonge",
                      "is_active": true,
                      "jurisdiction": "ON",
                      "last_updated": "2026-03-29T14:50:00Z",
                      "latitude": 43.6532,
                      "longitude": -79.3832,
                      "name": "Hwy 401 at Yonge St",
                      "source": "on"
                    }
                  ],
                  "has_more": false,
                  "limit": 100,
                  "offset": 0,
                  "total": 1
                }
              }
            },
            "description": "Paginated features (capped)"
          }
        },
        "summary": "Map features",
        "tags": [
          "map"
        ]
      }
    },
    "/map/features/geojson": {
      "get": {
        "description": "Public, same restrictions as /map/features (capped, no pagination)",
        "responses": {
          "200": {
            "content": {
              "application/geo+json": {
                "example": {
                  "features": [
                    {
                      "geometry": {
                        "coordinates": [
                          -79.3832,
                          43.6532
                        ],
                        "type": "Point"
                      },
                      "properties": {
                        "feature_type": "cameras",
                        "id": "on-cam-401-yonge",
                        "is_active": true,
                        "jurisdiction": "ON",
                        "name": "Hwy 401 at Yonge St",
                        "source": "on"
                      },
                      "type": "Feature"
                    }
                  ],
                  "type": "FeatureCollection"
                }
              }
            },
            "description": "GeoJSON FeatureCollection"
          }
        },
        "summary": "Map features GeoJSON",
        "tags": [
          "map"
        ]
      }
    },
    "/map/features/types": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "active_count": 11893,
                    "count": 12450,
                    "type": "cameras"
                  },
                  {
                    "active_count": 3142,
                    "count": 3200,
                    "type": "weather_stations"
                  },
                  {
                    "active_count": 4651,
                    "count": 4870,
                    "type": "signs"
                  }
                ]
              }
            },
            "description": "Feature type counts"
          }
        },
        "summary": "Map feature types",
        "tags": [
          "map"
        ]
      }
    },
    "/plans": {
      "get": {
        "description": "Returns active subscription plans with their gating columns (RPM,\nrequest quotas, allow_* flags) **and** their admin-curated marketing\nbullets (`features`). The response is wrapped in a `plans` array;\nwhen Paddle is configured the response also carries\n`paddle_client_token` + `paddle_env` for the checkout overlay.\n\nResolution order for each feature on the frontend:\n`i18n_key` (when the current locale has it) → raw `label` → key.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "paddle_client_token": "live_abc123",
                  "paddle_env": "live",
                  "plans": [
                    {
                      "allow_analytics": false,
                      "allow_geojson": false,
                      "allow_truck_data": false,
                      "code": "free",
                      "data_delay_sec": 900,
                      "description": "14-day trial with basic access",
                      "features": [
                        {
                          "icon": "check",
                          "id": 11,
                          "label": "60 RPM · 1k req/day",
                          "plan_id": 1,
                          "sort_order": 10
                        },
                        {
                          "icon": "check",
                          "id": 12,
                          "label": "2 jurisdictions",
                          "plan_id": 1,
                          "sort_order": 20
                        },
                        {
                          "i18n_key": "plan_feature.events_cameras",
                          "icon": "check",
                          "id": 13,
                          "plan_id": 1,
                          "sort_order": 30
                        },
                        {
                          "i18n_key": "plan_feature.email_support",
                          "icon": "check",
                          "id": 14,
                          "plan_id": 1,
                          "sort_order": 40
                        }
                      ],
                      "id": 1,
                      "is_active": true,
                      "max_jurisdictions": 2,
                      "max_keys": 1,
                      "max_requests_day": 1000,
                      "max_results_per_page": 100,
                      "name": "Free",
                      "rate_limit_rpm": 60,
                      "trial_days": 14
                    },
                    {
                      "allow_analytics": false,
                      "allow_geojson": true,
                      "allow_truck_data": false,
                      "code": "starter",
                      "data_delay_sec": 0,
                      "description": "For small apps and prototypes",
                      "features": [
                        {
                          "icon": "check",
                          "id": 21,
                          "label": "300 RPM · 50k req/day",
                          "plan_id": 2,
                          "sort_order": 10
                        },
                        {
                          "icon": "check",
                          "id": 22,
                          "label": "All 65 jurisdictions",
                          "plan_id": 2,
                          "sort_order": 20
                        },
                        {
                          "i18n_key": "plan_feature.geojson_export",
                          "icon": "check",
                          "id": 23,
                          "plan_id": 2,
                          "sort_order": 30
                        },
                        {
                          "icon": "check",
                          "id": 24,
                          "label": "500 results per page",
                          "plan_id": 2,
                          "sort_order": 40
                        }
                      ],
                      "id": 2,
                      "is_active": true,
                      "max_jurisdictions": 10,
                      "max_keys": 3,
                      "max_requests_day": 50000,
                      "max_results_per_page": 500,
                      "name": "Starter",
                      "rate_limit_rpm": 300,
                      "trial_days": 0
                    }
                  ]
                }
              }
            },
            "description": "Active plans + Paddle config"
          }
        },
        "security": [],
        "summary": "List subscription plans",
        "tags": [
          "metadata"
        ]
      }
    },
    "/routing/quota": {
      "get": {
        "description": "Returns the same `quota` block emitted alongside `/routing/route`\nresponses, without performing a routing call (zero quota charge).\nUsed by the portal's subscription card to render the usage bar +\nactive top-ups list.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "reset_at": "2026-06-01T00:00:00Z",
                  "subscription": {
                    "limit": 7500,
                    "plan": "pro_7500",
                    "tier": "pro",
                    "used": 234
                  },
                  "topups": [
                    {
                      "calls_added": 1000,
                      "calls_remaining": 750,
                      "expires_at": "2026-05-31T23:59:59Z",
                      "id": "01HK...",
                      "pack_sku": "topup_routing_truck_1000",
                      "purchased_at": "2026-05-04T09:12:00Z",
                      "title": "Standard Pack"
                    }
                  ],
                  "total": {
                    "remaining": 8016
                  }
                }
              }
            },
            "description": "Quota envelope"
          }
        },
        "summary": "Probe the customer's current routing quota envelope",
        "tags": [
          "routing"
        ]
      }
    },
    "/routing/route": {
      "post": {
        "description": "Returns a routed polyline annotated with `warnings[]` — every nearby\nhazard, restriction, and operational note our database knows about\n(active incidents, planned construction, bridge clearance/weight\nproblems for the requested truck, public at-grade rail crossings\nwith 49 CFR 392.10 hazmat-stop awareness, truck/weight restrictions,\nweigh stations, weather, alerts, special events). When the load is\nhazardous, segments restricted to hazardous materials surface as\n`hazmat_restriction` warnings, and the routing engine is asked to avoid\nthem via the `hazardous`/`tunnel_category` profile. Invalid `hazardous`\nor `tunnel_category` values are rejected with HTTP 400.\n\nEvery successful call:\n  * Counts against the customer's monthly routing quota (subscription\n    bucket first, then top-ups FIFO by expiry). Cache hits still\n    charge — the route is the API call regardless of whether we\n    re-used a cached payload.\n  * Auto-saves the result to `saved_routes` with a 30-minute TTL.\n    The returned `route_id` can be refetched (with re-evaluated\n    warnings) via `GET /routing/route/saved/{id}` or persisted via\n    `POST /routing/route/saved/{id}/persist`.\n\nThe `truck` block is required on every request. See the request\nschema for the full set of vehicle parameters.\n\nSupplying `truck.hos` (the driver's Hours-of-Service clock) adds an\n`hos[]` channel to each route: the points where the driver must take a\nbreak or stop driving under the chosen regime (US, Canada, EU), each with\nreachable truck parking / rest areas before the limit. It is free on every\nplan. See `truck.hos` in the request schema.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "alternatives": {
                    "maximum": 3,
                    "minimum": 0,
                    "type": "integer"
                  },
                  "arrival_time": {
                    "description": "Mutually exclusive with departure_time",
                    "type": "string"
                  },
                  "avoid": {
                    "items": {
                      "enum": [
                        "highway",
                        "toll_road",
                        "ferry",
                        "tunnel",
                        "dirt_road",
                        "car_shuttle_train",
                        "seasonal_closure"
                      ],
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "avoid_areas": {
                    "description": "Hard-block routing through one or more geographic areas. Each entry\nsets `type` and the matching field group.\n",
                    "items": {
                      "properties": {
                        "east": {
                          "description": "bbox",
                          "type": "number"
                        },
                        "lat": {
                          "description": "circle centre",
                          "type": "number"
                        },
                        "lng": {
                          "description": "circle centre",
                          "type": "number"
                        },
                        "north": {
                          "description": "bbox",
                          "type": "number"
                        },
                        "points": {
                          "description": "polygon vertices as [lat, lng] pairs",
                          "items": {
                            "items": {
                              "type": "number"
                            },
                            "type": "array"
                          },
                          "type": "array"
                        },
                        "radius_m": {
                          "description": "circle radius in metres",
                          "type": "number"
                        },
                        "south": {
                          "description": "bbox",
                          "type": "number"
                        },
                        "type": {
                          "enum": [
                            "bbox",
                            "circle",
                            "polygon"
                          ],
                          "type": "string"
                        },
                        "west": {
                          "description": "bbox",
                          "type": "number"
                        }
                      },
                      "required": [
                        "type"
                      ],
                      "type": "object"
                    },
                    "type": "array"
                  },
                  "avoid_countries": {
                    "description": "Route around these countries entirely where possible (ISO 3166-1 alpha-3 codes, e.g. \"USA\", \"CAN\").",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "avoid_truck_roads": {
                    "description": "Avoid these truck-road usage classes.",
                    "items": {
                      "enum": [
                        "through_traffic",
                        "through_routes"
                      ],
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "avoid_zones": {
                    "description": "Avoid whole categories of zone along the route.",
                    "items": {
                      "enum": [
                        "environmental",
                        "congestion_pricing"
                      ],
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "cargo": {
                    "description": "Cargo metadata. Not proxied to the routing engine; stored on the saved route\nand available to future enrichment rules.\n",
                    "properties": {
                      "hazmat_class": {
                        "description": "DOT placard class (e.g., \"1.3D\", \"3\", \"8\"). Finer-grained than\nthe truck.hazardous bucket; future enrichment rules can use the\nclass digit for stricter 49 CFR 392.10 rail-crossing logic.\n",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "currency": {
                    "description": "ISO 4217; only honored when tolls is requested",
                    "type": "string"
                  },
                  "customer_route_id": {
                    "description": "Opaque client-side correlation key. Echoed verbatim in the response.\nNot used by enrichment.\n",
                    "maxLength": 128,
                    "type": "string"
                  },
                  "departure_time": {
                    "description": "ISO 8601 or 'now'; defaults to 'now'",
                    "type": "string"
                  },
                  "destination": {
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "lat",
                      "lng"
                    ],
                    "type": "object"
                  },
                  "details": {
                    "description": "Per-span road-attribute detail channels to compute along the route\n(returned on the route's spans). Independent of `include`.\n",
                    "items": {
                      "enum": [
                        "speed_limit",
                        "dynamic_speed",
                        "functional_class",
                        "names",
                        "route_numbers",
                        "country",
                        "lanes",
                        "notices",
                        "restrictions",
                        "truck_attributes",
                        "toll_systems"
                      ],
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "enrichment": {
                    "description": "Per-request tuning for the post-routing truck-aware filter pipeline.\nNone of these fields are sent to the routing engine — they shape what\n`warnings[]` contains and, via `include_features`, what\n`features[]` contains. Omit the block to keep defaults.\n",
                    "properties": {
                      "buffer_m": {
                        "default": 100,
                        "description": "Spatial-join buffer radius around the polyline, in metres.",
                        "maximum": 1000,
                        "minimum": 10,
                        "type": "number"
                      },
                      "clearance_pad_m": {
                        "default": 0.15,
                        "description": "Safety pad added to `truck.height_m` when evaluating\nbridge_clearances. A warning fires when\n`clearance_m \u003c truck.height_m + clearance_pad_m`.\n",
                        "maximum": 1,
                        "minimum": 0,
                        "type": "number"
                      },
                      "exclude_types": {
                        "description": "Drop these feature_types from the spatial-join entirely. Useful\nfor trimming the response when a UI doesn't display, e.g., weigh\nstations.\n",
                        "items": {
                          "enum": [
                            "future_construction",
                            "bridge_clearances",
                            "rail_crossings",
                            "truck_restrictions",
                            "weight_restrictions",
                            "weigh_stations",
                            "inspection_stations",
                            "alerts",
                            "special_events",
                            "weather_stations"
                          ],
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "include_features": {
                        "description": "Opt the response into the `features[]` channel — amenity/POI\nmatches along the route (truck parking, rest areas, service\nplazas, EV charging), distinct from hazard `warnings[]`. Each\nentry is a POI feature_type; `rest_areas` are surfaced only\nwhen truck-friendly. Omitted/empty = no `features[]` (the\ndefault — no added cost).\n\n**Plan-gated by count.** Each plan caps how many distinct\nfeature types you may select at once (Free: none; the cap\nrises with tier; Enterprise: unlimited). Selecting more types\nthan your plan allows returns `403 feature_types_limit`, and a\nplan with no allowance returns `403 feature_channel_unavailable`.\nYour current cap is published as `routing_max_feature_types` on\n`GET /customer/plans`.\n",
                        "items": {
                          "enum": [
                            "truck_parking",
                            "truck_rest_areas",
                            "rest_areas",
                            "service_plazas",
                            "ev_charging"
                          ],
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "max_distance_m": {
                        "description": "Only emit warnings within this distance from the route origin.\n0 (default) = unlimited. Also bounds features[] when set.\n",
                        "minimum": 0,
                        "type": "number"
                      },
                      "min_severity": {
                        "description": "Filter out warnings below this threshold. Ranking is\ninfo \u003c warning \u003c critical. Critical warnings (e.g., bridge\nclearance violations) always pass.\n",
                        "enum": [
                          "info",
                          "warning",
                          "critical"
                        ],
                        "type": "string"
                      },
                      "skip_temporal_filter": {
                        "default": false,
                        "description": "When true, keeps traffic events whose end_time is already past\nprojected arrival. Useful for planning-ahead runs.\n",
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  },
                  "exclude_countries": {
                    "description": "Hard-exclude these countries from the route (ISO 3166-1 alpha-3). Stricter than avoid_countries — the route will fail rather than pass through.",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "include": {
                    "description": "Optional route detail channels to compute. `polyline` and `summary`\nare always included. Request `tolls` to have the routing engine\nprice the route's toll systems for your truck profile — the cost\nthen appears as `routes[].sections[].tolls[]` (per toll system)\nand is rolled up per currency into `routes[].summary.toll_costs[]`.\nSet `currency` (ISO 4217) to control the fare currency. Toll\npricing adds upstream cost, so it is off unless requested.\n",
                    "items": {
                      "enum": [
                        "polyline",
                        "summary",
                        "instructions",
                        "actions",
                        "tolls",
                        "incidents",
                        "routing_zones",
                        "elevation",
                        "route_handle",
                        "section_fares"
                      ],
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "language": {
                    "description": "BCP-47 (e.g. en-US)",
                    "type": "string"
                  },
                  "optimize": {
                    "default": "time",
                    "enum": [
                      "time",
                      "distance"
                    ],
                    "type": "string"
                  },
                  "origin": {
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "lat",
                      "lng"
                    ],
                    "type": "object"
                  },
                  "tags": {
                    "description": "Free-form labels for fleet/lane analytics rollup. Echoed and stored\non the saved route. Max 16 tags, each up to 64 chars.\n",
                    "items": {
                      "maxLength": 64,
                      "type": "string"
                    },
                    "maxItems": 16,
                    "type": "array"
                  },
                  "traffic": {
                    "default": "live",
                    "enum": [
                      "live",
                      "historical",
                      "off"
                    ],
                    "type": "string"
                  },
                  "truck": {
                    "description": "Required block (the endpoint is truck-only).",
                    "properties": {
                      "axle_count": {
                        "type": "integer"
                      },
                      "axle_group_weight_t": {
                        "description": "Per-axle-group weight caps in tonnes (mapped to the router's weight-per-axle-group profile).",
                        "properties": {
                          "single": {
                            "type": "number"
                          },
                          "tandem": {
                            "type": "number"
                          },
                          "triple": {
                            "type": "number"
                          }
                        },
                        "type": "object"
                      },
                      "axle_weight_t": {
                        "type": "number"
                      },
                      "commercial": {
                        "type": "boolean"
                      },
                      "fuel": {
                        "enum": [
                          "diesel",
                          "gasoline",
                          "lpg",
                          "cng"
                        ],
                        "type": "string"
                      },
                      "hazardous": {
                        "items": {
                          "enum": [
                            "explosive",
                            "gas",
                            "flammable",
                            "combustible",
                            "organic",
                            "poison",
                            "radioactive",
                            "corrosive",
                            "poison_inhalation",
                            "harmful_to_water",
                            "other"
                          ],
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "height_m": {
                        "maximum": 5,
                        "type": "number"
                      },
                      "hos": {
                        "description": "Driver's Hours-of-Service clock at departure. When present (with\na `ruleset`), the response gains an `hos[]` projection of where\nthe driver must take a break or stop driving under that regime,\neach annotated with reachable truck parking / rest areas before\nthe limit. **Free on every plan** — no quota beyond the routing\ncall itself. Not proxied to the routing engine.\n\nA reusable truck profile should store only `ruleset` (your\nfleet's governing regime); supply the per-trip `*_remaining_s`\ncounters inline — they win over the stored profile per field.\nEach counter is *seconds remaining* against the ruleset's limit;\nomit a counter to assume a fresh driver (the full limit).\n",
                        "properties": {
                          "cycle_remaining_s": {
                            "description": "Seconds left in the weekly/cycle budget.",
                            "minimum": 0,
                            "type": "integer"
                          },
                          "drive_remaining_s": {
                            "description": "Seconds left on the driving limit.",
                            "minimum": 0,
                            "type": "integer"
                          },
                          "duty_remaining_s": {
                            "description": "Seconds left in the on-duty window (US/Canada).",
                            "minimum": 0,
                            "type": "integer"
                          },
                          "elapsed_remaining_s": {
                            "description": "Seconds left in the elapsed (wall-clock) window (Canada).",
                            "minimum": 0,
                            "type": "integer"
                          },
                          "ruleset": {
                            "description": "Hours-of-Service regime. Omit to default from the deployment\nregion (NA → `us`, EU → `eu`). Canadian operators must set\n`canada_south` explicitly. (`canada_north` and `aetr` are\nplanned.)\n",
                            "enum": [
                              "us",
                              "canada_south",
                              "eu"
                            ],
                            "type": "string"
                          },
                          "since_break_s": {
                            "description": "Seconds of driving since the last qualifying break.",
                            "minimum": 0,
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "length_m": {
                        "maximum": 30,
                        "type": "number"
                      },
                      "max_speed_kph": {
                        "type": "integer"
                      },
                      "occupancy": {
                        "type": "integer"
                      },
                      "permit_number": {
                        "description": "Oversize/overweight permit identifier carried by this truck.\nNot proxied to the routing engine — stored on the saved route and echoed in\nthe response for client-side correlation.\n",
                        "type": "string"
                      },
                      "profile": {
                        "default": "tractor",
                        "enum": [
                          "tractor",
                          "straight",
                          "box"
                        ],
                        "type": "string"
                      },
                      "profile_id": {
                        "description": "ID of a reusable truck profile from your fleet library\n(managed under the developer portal). When set, the server\nloads that profile's stored truck block and deep-merges any\ninline `truck.*` fields supplied alongside it on top — inline\nvalues win per field. The fully-resolved block is echoed back\nas `resolved_truck` so you can audit exactly what was applied.\nA 400 (`profile_not_found`) is returned if the ID isn't one of\nyour profiles.\n",
                        "type": "string"
                      },
                      "trailer_axle_count": {
                        "type": "integer"
                      },
                      "trailer_count": {
                        "type": "integer"
                      },
                      "tunnel_category": {
                        "enum": [
                          "B",
                          "C",
                          "D",
                          "E"
                        ],
                        "type": "string"
                      },
                      "weight_t": {
                        "description": "Gross combined weight in tonnes",
                        "type": "number"
                      },
                      "width_m": {
                        "maximum": 4.5,
                        "type": "number"
                      }
                    },
                    "required": [],
                    "type": "object"
                  },
                  "units": {
                    "default": "metric",
                    "enum": [
                      "metric",
                      "imperial"
                    ],
                    "type": "string"
                  },
                  "waypoints": {
                    "items": {
                      "properties": {
                        "lat": {
                          "type": "number"
                        },
                        "lng": {
                          "type": "number"
                        },
                        "stop_over": {
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "origin",
                  "destination",
                  "truck"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "cached": false,
                  "customer_route_id": "my-route-42",
                  "expires_at": "2026-05-18T13:30:00Z",
                  "is_persisted": false,
                  "quota": {
                    "reset_at": "2026-06-01T00:00:00Z",
                    "subscription": {
                      "limit": 7500,
                      "plan": "pro_7500",
                      "tier": "pro",
                      "used": 1
                    },
                    "topups": [],
                    "total": {
                      "remaining": 7499
                    }
                  },
                  "resolved_truck": {
                    "axle_count": 5,
                    "fuel": "diesel",
                    "height_m": 4.1,
                    "profile_id": "9f1c2e7a-6b3d-4a21-9f0e-2c5d8b7a1e34",
                    "weight_t": 36
                  },
                  "route_id": "01HKZ9P5XJVQ6NB8E2RTH4FMW7",
                  "routes": [
                    {
                      "features": [
                        {
                          "distance_along_route_m": 142000,
                          "geometry": {
                            "coordinates": [
                              -77.6,
                              44.1
                            ],
                            "type": "Point"
                          },
                          "name": "ONroute Trenton North",
                          "properties": {
                            "amenities": [
                              "restrooms",
                              "fuel"
                            ],
                            "spaces": 40
                          },
                          "source": "ON 511",
                          "source_id": "feat_truck_parking_on_4821",
                          "type": "truck_parking"
                        }
                      ],
                      "geometry": {
                        "coordinates": [
                          [
                            -74.006,
                            40.7128
                          ]
                        ],
                        "type": "LineString"
                      },
                      "hos": [
                        {
                          "distance_along_route_m": 720000,
                          "feasible": true,
                          "legal_deadline": "2026-05-18T14:00:00Z",
                          "projected_time": "2026-05-18T14:00:00Z",
                          "reason": "break_required",
                          "suggested_stops": [
                            {
                              "distance_along_route_m": 705000,
                              "geometry": {
                                "coordinates": [
                                  -93.1,
                                  41.6
                                ],
                                "type": "Point"
                              },
                              "name": "I-80 Rest Area MP 284",
                              "properties": {
                                "truck_parking": 24
                              },
                              "source": "IA 511",
                              "source_id": "feat_rest_areas_ia_284",
                              "type": "rest_area"
                            }
                          ]
                        },
                        {
                          "distance_along_route_m": 990000,
                          "feasible": false,
                          "legal_deadline": "2026-05-18T17:30:00Z",
                          "projected_time": "2026-05-18T17:30:00Z",
                          "reason": "drive_limit",
                          "suggested_stops": []
                        }
                      ],
                      "notices": [
                        {
                          "code": "violatedVehicleRestriction",
                          "title": "Route violates a posted truck restriction near the destination"
                        }
                      ],
                      "sections": [
                        {
                          "distance_m": 120000,
                          "duration_s": 4200,
                          "summary": "I-95 N",
                          "tolls": [
                            {
                              "fares": [
                                {
                                  "name": "Class 5",
                                  "price": {
                                    "currency": "USD",
                                    "value": 12.5
                                  }
                                }
                              ],
                              "system": "New Jersey Turnpike"
                            }
                          ]
                        }
                      ],
                      "summary": {
                        "distance_m": 350000,
                        "duration_s": 12600,
                        "toll_costs": [
                          {
                            "currency": "USD",
                            "value": 25
                          }
                        ]
                      },
                      "warnings": [
                        {
                          "description": "I-95 NB overpass at MP 23: 4.0m clearance; your truck is 4.2m.",
                          "distance_along_route_m": 12450,
                          "geometry": {
                            "coordinates": [
                              -74.006,
                              40.7128
                            ],
                            "type": "Point"
                          },
                          "projected_arrival_time": "2026-05-18T11:12:00Z",
                          "properties": {
                            "clearance_m": 4,
                            "truck_height_m": 4.2
                          },
                          "severity": "critical",
                          "source": "FHWA NBI",
                          "source_id": "feat_bridge_clearances_nbi_12345",
                          "title": "Bridge clearance below truck height",
                          "type": "bridge_clearance"
                        }
                      ]
                    }
                  ],
                  "tags": [
                    "fleet-east",
                    "lane-LA-NYC"
                  ]
                }
              }
            },
            "description": "Route computed with warnings enriched"
          },
          "400": {
            "content": {
              "application/json": {
                "examples": {
                  "conflicting_times": {
                    "value": {
                      "code": "conflicting_times",
                      "message": "departure_time and arrival_time are mutually exclusive"
                    }
                  },
                  "invalid_body": {
                    "value": {
                      "code": "invalid_body",
                      "message": "request body is not valid JSON"
                    }
                  },
                  "invalid_enrichment_severity": {
                    "value": {
                      "code": "invalid_enrichment_severity",
                      "message": "enrichment.min_severity must be info, warning, or critical (got \"loud\")"
                    }
                  },
                  "invalid_enrichment_type": {
                    "value": {
                      "code": "invalid_enrichment_type",
                      "message": "enrichment.exclude_types: unknown feature_type \"made_up\""
                    }
                  },
                  "invalid_hos_ruleset": {
                    "value": {
                      "code": "invalid_hos_ruleset",
                      "message": "truck.hos.ruleset \"canada_north\" is not a known regime"
                    }
                  },
                  "out_of_range_enrichment": {
                    "value": {
                      "code": "out_of_range",
                      "message": "enrichment.buffer_m must be in [10, 1000]"
                    }
                  },
                  "profile_not_found": {
                    "value": {
                      "code": "profile_not_found",
                      "message": "truck.profile_id \"9f1c…\" not found"
                    }
                  },
                  "truck_block_missing": {
                    "value": {
                      "code": "truck_block_required",
                      "message": "every routing request must include a truck block"
                    }
                  },
                  "unrouteable": {
                    "value": {
                      "code": "unrouteable",
                      "message": "no route found between the given coordinates"
                    }
                  }
                }
              }
            },
            "description": "Validation failure — see `code` for the specific reason"
          },
          "403": {
            "content": {
              "application/json": {
                "examples": {
                  "feature_channel_unavailable": {
                    "value": {
                      "code": "feature_channel_unavailable",
                      "message": "features[] (enrichment.include_features) is not available on the Free plan"
                    }
                  },
                  "feature_types_limit": {
                    "value": {
                      "code": "feature_types_limit",
                      "message": "the Starter plan allows at most 1 feature type(s) in enrichment.include_features (requested 2)"
                    }
                  },
                  "no_subscription": {
                    "value": {
                      "code": "no_subscription",
                      "message": "routing is not enabled on this plan"
                    }
                  }
                }
              }
            },
            "description": "Plan does not include the requested capability"
          },
          "429": {
            "content": {
              "application/json": {
                "examples": {
                  "daily_cap": {
                    "value": {
                      "code": "trial_daily_cap",
                      "message": "Free plan daily limit reached (5/day). Upgrade to remove the cap."
                    }
                  },
                  "perk_temporarily_unavailable": {
                    "value": {
                      "code": "perk_temporarily_unavailable",
                      "message": "Free-plan routing temporarily unavailable — please retry tomorrow or upgrade"
                    }
                  },
                  "quota_exhausted": {
                    "value": {
                      "code": "quota_exhausted",
                      "message": "Routing quota exhausted. Upgrade or buy a top-up pack to continue."
                    }
                  },
                  "routing_rate_limit": {
                    "value": {
                      "code": "routing_rate_limit",
                      "message": "routing rate limit reached (60/min). Retry in 12s."
                    }
                  }
                }
              }
            },
            "description": "Quota or rate limit hit"
          },
          "502": {
            "content": {
              "application/json": {
                "example": {
                  "code": "routing_unavailable",
                  "message": "routing service unavailable"
                }
              }
            },
            "description": "Upstream routing provider unavailable"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "code": "routing_unavailable",
                  "message": "routing is not configured on this deployment"
                }
              }
            },
            "description": "Routing is not configured on this deployment"
          },
          "504": {
            "content": {
              "application/json": {
                "example": {
                  "code": "routing_timeout",
                  "message": "routing service timed out"
                }
              }
            },
            "description": "Upstream routing provider timed out"
          }
        },
        "summary": "Compute a truck route with hazard enrichment",
        "tags": [
          "routing"
        ]
      }
    },
    "/routing/route/saved/recent": {
      "get": {
        "description": "Returns the slim row shape (`id`, `label`, `is_persisted`, `created_at`,\n`expires_at`) — fetch the full payload via the by-id refetch\nendpoint. Used by the portal's \"Recent routes (last 30 min)\" section.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "routes": [
                    {
                      "created_at": "2026-05-18T10:00:00Z",
                      "expires_at": "2026-05-18T10:30:00Z",
                      "id": "01HKZ9P5XJVQ6NB8E2RTH4FMW7",
                      "is_persisted": false,
                      "label": ""
                    }
                  ]
                }
              }
            },
            "description": "List returned"
          }
        },
        "summary": "List the customer's recent auto-saved + persisted routes",
        "tags": [
          "routing"
        ]
      }
    },
    "/routing/route/saved/{id}": {
      "delete": {
        "description": "Removes the row early. Works for both auto-saved and persisted rows.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "description": "Route not found (or owned by another customer)"
          }
        },
        "summary": "Delete a saved route",
        "tags": [
          "routing"
        ]
      },
      "get": {
        "description": "Returns the previously-computed route with `warnings[]` re-run against\ncurrent data. Does NOT count against routing quota, but is subject to\na separate refetch rate limit (1 / 30 s per route, 60 / minute per\ncustomer; both configurable). A 15-second refetch cache absorbs\nthe dispatcher's tweak-and-re-evaluate flow.\n\nAuto-saved routes (`is_persisted=false`) expire 30 minutes after the\noriginal routing call. After that, this endpoint returns `route_expired`.\n\nPass `route_index=N` to refetch warnings for an alternative route. The\nresponse always carries exactly one route in `routes[]`; the original\nrequest's other alternatives are still available via repeat calls\nwith different `route_index` values, but each response shows only the\none you asked for. Each `route_index` caches independently.\n",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Zero-based index of the route to refetch. `0` is the primary\n(default). Valid range is `0 ≤ N \u003c len(routes)` against the\nsaved row's stored response. Out-of-range returns\n`invalid_route_index` (400).\n",
            "in": "query",
            "name": "route_index",
            "required": false,
            "schema": {
              "default": 0,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Route returned (warnings re-evaluated). `routes[]` is a single-element array carrying the chosen `route_index`."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "code": "invalid_route_index",
                  "message": "route_index 3 out of range (have 2 route(s))"
                }
              }
            },
            "description": "route_index out of range or invalid"
          },
          "404": {
            "content": {
              "application/json": {
                "examples": {
                  "expired": {
                    "value": {
                      "code": "route_expired",
                      "message": "route auto-save window has expired"
                    }
                  },
                  "not_found": {
                    "value": {
                      "code": "route_not_found",
                      "message": "route not found"
                    }
                  }
                }
              }
            },
            "description": "Route not found or expired"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "code": "refetch_rate_limit_exceeded",
                  "message": "refetch rate limit exceeded"
                }
              }
            },
            "description": "Refetch rate limit exceeded"
          }
        },
        "summary": "Refetch a saved route with re-evaluated warnings",
        "tags": [
          "routing"
        ]
      }
    },
    "/routing/route/saved/{id}/persist": {
      "post": {
        "description": "Counts against the customer's per-plan persisted-route cap\n(`max_saved_routes`, or the per-customer override when set).\n\nSupplying a `notify` block opts the route into monitoring: the\nroute-monitor re-evaluates the route on a fixed interval and POSTs a\n`route.alert` webhook to `notify.webhook_id` whenever warnings appear\non — or clear from — the route. Alerts are filtered to `min_severity`\nand `types`; an optional `valid_from`/`valid_to` window bounds when\nmonitoring is active. The webhook body is\n`{ event: \"route.alert\", timestamp, data: { route_id, label,\nnew_warnings[], cleared_warnings[] } }`, HMAC-signed like every other\nRoad511 webhook.\n",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "buffer_m": {
                    "default": 100,
                    "type": "integer"
                  },
                  "label": {
                    "type": "string"
                  },
                  "notify": {
                    "properties": {
                      "min_severity": {
                        "default": "warning",
                        "enum": [
                          "info",
                          "warning",
                          "critical"
                        ],
                        "type": "string"
                      },
                      "types": {
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "webhook_id": {
                        "type": "integer"
                      }
                    },
                    "type": "object"
                  },
                  "route_index": {
                    "default": 0,
                    "description": "Which alternative route to persist. `0` (default) keeps\nthe primary as it was computed. `N \u003e 0` re-enriches that\nalternative against current data and **replaces** the\nstored polyline + `routing_response.routes[]` with the\nchosen route only; other alternatives are dropped from\nthe row.\n",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "valid_from": {
                    "format": "date-time",
                    "type": "string"
                  },
                  "valid_to": {
                    "format": "date-time",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "description": "Persisted"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "code": "invalid_route_index",
                  "message": "route_index 3 out of range (have 2 route(s))"
                }
              }
            },
            "description": "Invalid request (e.g. route_index out of range)"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "code": "saved_routes_quota_exhausted",
                  "message": "persisted-routes cap reached (5/5)"
                }
              }
            },
            "description": "Persisted-routes cap reached"
          },
          "404": {
            "description": "Route not found or already persisted"
          }
        },
        "summary": "Mark a saved route as persisted (keeps it past the 30-min TTL)",
        "tags": [
          "routing"
        ]
      }
    },
    "/stats": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "active_events": 4523,
                  "features_by_type": {
                    "bridge_clearances": 45200,
                    "cameras": 12450,
                    "ev_charging": 98500,
                    "rest_areas": 1580,
                    "road_conditions": 2100,
                    "signs": 4870,
                    "truck_restrictions": 8900,
                    "weather_stations": 3200
                  },
                  "jurisdictions": 47,
                  "total_features": 176800,
                  "uptime": "72h15m"
                }
              }
            },
            "description": "Event and feature counts"
          }
        },
        "summary": "Basic stats",
        "tags": [
          "metadata"
        ]
      }
    },
    "/stats/summary": {
      "get": {
        "description": "Jurisdictions breakdown, events by type/severity, feature counts, last sync times",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "events": {
                    "active": 4523,
                    "by_severity": {
                      "critical": 123,
                      "major": 780,
                      "minor": 1520,
                      "moderate": 2100
                    },
                    "by_type": {
                      "closure": 380,
                      "construction": 2150,
                      "incident": 1230,
                      "road_condition": 553,
                      "weather": 210
                    },
                    "total": 158420
                  },
                  "features": {
                    "by_type": {
                      "cameras": 12450,
                      "ev_charging": 98500,
                      "rest_areas": 1580,
                      "signs": 4870,
                      "weather_stations": 3200
                    },
                    "total": 176800
                  },
                  "jurisdictions": {
                    "countries": {
                      "CA": 12,
                      "US": 35
                    },
                    "list": [
                      {
                        "code": "CA",
                        "country": "US",
                        "name": "California"
                      },
                      {
                        "code": "ON",
                        "country": "CA",
                        "name": "Ontario"
                      }
                    ],
                    "total": 47
                  },
                  "last_sync": "2026-03-29T14:55:00Z",
                  "sources": {
                    "resources": 162,
                    "servers": 38
                  },
                  "uptime": "72h15m"
                }
              }
            },
            "description": "Summary stats object"
          }
        },
        "summary": "Rich summary stats",
        "tags": [
          "metadata"
        ]
      }
    },
    "/status": {
      "get": {
        "description": "Aggregate health snapshot suitable for an unauthenticated status page or external uptime monitor.\nReturns the worker heartbeat freshness, total/healthy/warning/critical resource counts, and a\nper-jurisdiction breakdown with 7-day uptime. 60-second cache. No API key required.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "critical": 5,
                  "generated_at": "2026-05-08T14:00:00Z",
                  "healthy": 295,
                  "jurisdictions": [
                    {
                      "code": "ON",
                      "country": "CA",
                      "name": "Ontario",
                      "open_circuits": 0,
                      "status": "operational",
                      "total_resources": 8,
                      "uptime_7day_percent": 99.8
                    },
                    {
                      "code": "WA",
                      "country": "US",
                      "name": "Washington",
                      "open_circuits": 1,
                      "status": "degraded",
                      "total_resources": 6,
                      "uptime_7day_percent": 94.5
                    }
                  ],
                  "overall": "operational",
                  "total_resources": 312,
                  "warning": 12,
                  "worker": {
                    "healthy": true,
                    "last_seen": "2026-05-08T13:59:50Z",
                    "staleness_seconds": 10
                  }
                }
              }
            },
            "description": "Aggregate status snapshot"
          }
        },
        "security": [],
        "summary": "Public status snapshot",
        "tags": [
          "metadata"
        ]
      }
    },
    "/truck/corridor": {
      "get": {
        "description": "Returns all truck-related restrictions within a buffer around the straight-line path\nbetween two points. Uses PostGIS spatial intersection (ST_Buffer + ST_Intersects).\n\nSearches across feature types: `bridge_clearances`, `bridges`, `weight_restrictions`,\n`truck_restrictions`, `truck_routes`, `freight_corridors`, `truck_parking`.\n\nResults are ordered by distance from the corridor centerline. Each feature includes\n`_distance_km` in its properties.\n\nRequires Pro+ plan with truck data access.\n",
        "parameters": [
          {
            "description": "Origin latitude",
            "in": "query",
            "name": "from_lat",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Origin longitude",
            "in": "query",
            "name": "from_lng",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Destination latitude",
            "in": "query",
            "name": "to_lat",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Destination longitude",
            "in": "query",
            "name": "to_lng",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Buffer distance around corridor in km (default 5, max 50)",
            "in": "query",
            "name": "buffer_km",
            "schema": {
              "default": 5,
              "maximum": 50,
              "type": "number"
            }
          },
          {
            "description": "Vehicle height in meters — filters bridge clearances below this value",
            "in": "query",
            "name": "height",
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Vehicle weight in metric tons — filters weight limits below this value",
            "in": "query",
            "name": "weight",
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Filter by jurisdiction code (e.g., WA, NY, ON)",
            "in": "query",
            "name": "jurisdiction",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum results (default 500, max 2000)",
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 500,
              "maximum": 2000,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "corridor": {
                    "buffer_km": 5,
                    "distance_km": 58.3,
                    "from": [
                      43.65,
                      -79.38
                    ],
                    "to": [
                      43.25,
                      -79.87
                    ]
                  },
                  "data": [
                    {
                      "feature_type": "bridge_clearances",
                      "id": "on-br-qew-001",
                      "is_active": true,
                      "jurisdiction": "ON",
                      "latitude": 43.4521,
                      "longitude": -79.7032,
                      "name": "QEW Overpass at Trafalgar Rd",
                      "properties": {
                        "_distance_km": 1.2,
                        "clearance_m": 4.35,
                        "over_road": "Trafalgar Rd",
                        "road_name": "QEW"
                      },
                      "source": "on_arcgis"
                    },
                    {
                      "feature_type": "weight_restrictions",
                      "id": "on-wr-hwy6-003",
                      "is_active": true,
                      "jurisdiction": "ON",
                      "latitude": 43.3812,
                      "longitude": -79.7654,
                      "name": "Hwy 6 Spring Load Restriction",
                      "properties": {
                        "_distance_km": 3.8,
                        "max_weight_tonnes": 5,
                        "restriction_type": "spring_load"
                      },
                      "source": "on_arcgis"
                    }
                  ],
                  "limit": 500,
                  "total": 2
                },
                "schema": {
                  "properties": {
                    "corridor": {
                      "properties": {
                        "buffer_km": {
                          "type": "number"
                        },
                        "distance_km": {
                          "type": "number"
                        },
                        "from": {
                          "items": {
                            "type": "number"
                          },
                          "type": "array"
                        },
                        "to": {
                          "items": {
                            "type": "number"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Feature"
                      },
                      "type": "array"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Corridor restrictions with metadata"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "from_lat, from_lng, to_lat, to_lng are required"
                }
              }
            },
            "description": "Missing required coordinates"
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "API key required"
                }
              }
            },
            "description": "API key required"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "truck data access requires Pro plan or higher"
                }
              }
            },
            "description": "Plan does not include truck data access"
          }
        },
        "summary": "Query truck restrictions along a corridor",
        "tags": [
          "truck"
        ]
      }
    },
    "/truck/corridor/geojson": {
      "get": {
        "description": "Same query as `/truck/corridor` but returns results as a GeoJSON FeatureCollection\nfor direct map rendering. Requires Pro+ plan with truck data + GeoJSON access.\n",
        "parameters": [
          {
            "in": "query",
            "name": "from_lat",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "from_lng",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "to_lat",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "to_lng",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "buffer_km",
            "schema": {
              "default": 5,
              "maximum": 50,
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "jurisdiction",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 500,
              "maximum": 2000,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/geo+json": {
                "example": {
                  "features": [
                    {
                      "geometry": {
                        "coordinates": [
                          -79.7032,
                          43.4521
                        ],
                        "type": "Point"
                      },
                      "properties": {
                        "_distance_km": 1.2,
                        "clearance_m": 4.35,
                        "feature_type": "bridge_clearances",
                        "id": "on-br-qew-001",
                        "name": "QEW Overpass at Trafalgar Rd"
                      },
                      "type": "Feature"
                    },
                    {
                      "geometry": {
                        "coordinates": [
                          [
                            -79.76,
                            43.38
                          ],
                          [
                            -79.77,
                            43.39
                          ]
                        ],
                        "type": "LineString"
                      },
                      "properties": {
                        "_distance_km": 3.8,
                        "feature_type": "weight_restrictions",
                        "id": "on-wr-hwy6-003",
                        "max_weight_tonnes": 5,
                        "name": "Hwy 6 Spring Load Restriction"
                      },
                      "type": "Feature"
                    }
                  ],
                  "type": "FeatureCollection"
                },
                "schema": {
                  "properties": {
                    "features": {
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "type": {
                      "enum": [
                        "FeatureCollection"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "GeoJSON FeatureCollection"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "truck data access requires Pro plan or higher"
                }
              }
            },
            "description": "Plan does not include truck data or GeoJSON access"
          }
        },
        "summary": "Query truck restrictions along a corridor (GeoJSON)",
        "tags": [
          "truck"
        ]
      }
    }
  },
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "ApiKeyQuery": []
    }
  ],
  "servers": [
    {
      "description": "Production",
      "url": "https://api.napspan.com/api/v1"
    },
    {
      "description": "Local development",
      "url": "http://localhost:8080/api/v1"
    }
  ],
  "tags": [
    {
      "description": "Traffic events (incidents, construction, closures, etc.)",
      "name": "events"
    },
    {
      "description": "Point-of-interest features (cameras, rest areas, signs, etc.)",
      "name": "features"
    },
    {
      "description": "Map-optimized endpoints (compact mode, same data)",
      "name": "map"
    },
    {
      "description": "Historical analytics and trends (Pro+ plans)",
      "name": "analytics"
    },
    {
      "description": "Truck corridor restriction queries (Pro+ plans with truck data access)",
      "name": "truck"
    },
    {
      "description": "Truck routing — compute a hazard-enriched route, refetch / persist\n/ delete saved routes. Counts against monthly routing quota (subscription\nbucket first, then top-ups FIFO).\n",
      "name": "routing"
    },
    {
      "description": "Jurisdictions, stats, health",
      "name": "metadata"
    },
    {
      "description": "Data-source licensing and attribution",
      "name": "data-sources"
    },
    {
      "description": "Regional fuel prices — weekly US state-level (EIA), monthly Canadian province-level (StatCan)",
      "name": "fuel-prices"
    }
  ]
}