---
title: "Compute a truck route with hazard enrichment"
method: POST
path: "/routing/route"
tags: ["routing"]
---

# Compute a truck route with hazard enrichment

`POST /routing/route`

Returns a routed polyline annotated with `warnings[]` — every nearby
hazard, restriction, and operational note our database knows about
(active incidents, planned construction, bridge clearance/weight
problems for the requested truck, public at-grade rail crossings
with 49 CFR 392.10 hazmat-stop awareness, truck/weight restrictions,
weigh stations, weather, alerts, special events). When the load is
hazardous, segments restricted to hazardous materials surface as
`hazmat_restriction` warnings, and the routing engine is asked to avoid
them via the `hazardous`/`tunnel_category` profile. Invalid `hazardous`
or `tunnel_category` values are rejected with HTTP 400.

Every successful call:
  * Counts against the customer's monthly routing quota (subscription
    bucket first, then top-ups FIFO by expiry). Cache hits still
    charge — the route is the API call regardless of whether we
    re-used a cached payload.
  * Auto-saves the result to `saved_routes` with a 30-minute TTL.
    The returned `route_id` can be refetched (with re-evaluated
    warnings) via `GET /routing/route/saved/{id}` or persisted via
    `POST /routing/route/saved/{id}/persist`.

The `truck` block is required on every request. See the request
schema for the full set of vehicle parameters.

Unknown request fields are accepted, not rejected — but any key the
server did not bind is echoed back in `ignored_fields` (sorted dotted
paths, e.g. `truck.axles`) and in the `X-Routing-Ignored-Fields`
response header. Use it to catch a mis-spelled field: `axles` instead of
`axle_count`, or `origin.lon` instead of `origin.lng`, is silently
dropped otherwise and the route is computed without it.

Supplying `truck.hos` (the driver's Hours-of-Service clock) adds an
`hos[]` channel to each route: the points where the driver must take a
break or stop driving under the chosen regime (US, Canada, EU), each with
reachable truck parking / rest areas before the limit. It is free on every
plan. See `truck.hos` in the request schema.

## Request body

- object
  - `alternatives` integer
  - `arrival_time` string — Mutually exclusive with departure_time
  - `avoid` string[]
  - `avoid_areas` object[] — Hard-block routing through one or more geographic areas. Each entry sets `type` and the matching field group.
    - `east` number — bbox
    - `lat` number — circle centre
    - `lng` number — circle centre
    - `north` number — bbox
    - `points` array[] — polygon vertices as [lat, lng] pairs
      - number[]
    - `radius_m` number — circle radius in metres
    - `south` number — bbox
    - `type` 'bbox' | 'circle' | 'polygon', required
    - `west` number — bbox
  - `avoid_countries` string[] — Route around these countries entirely where possible (ISO 3166-1 alpha-3 codes, e.g. "USA", "CAN").
  - `avoid_truck_roads` string[] — Avoid these truck-road usage classes.
  - `avoid_zones` string[] — Avoid whole categories of zone along the route.
  - `cargo` object — Cargo metadata. Not proxied to the routing engine; stored on the saved route and available to future enrichment rules.
    - `hazmat_class` string — DOT placard class (e.g., "1.3D", "3", "8"). Finer-grained than the truck.hazardous bucket; future enrichment rules can use the class digit for stricter 49 CFR 392.10 rail-crossing logic.
  - `currency` string — ISO 4217; only honored when tolls is requested
  - `customer_route_id` string — Opaque client-side correlation key. Echoed verbatim in the response. Not used by enrichment.
  - `departure_time` string — ISO 8601 or 'now'; defaults to 'now'
  - `destination` object, required
    - `lat` number, required
    - `lng` number, required
  - `details` string[] — Per-span road-attribute detail channels to compute along the route (returned on the route's spans). Independent of `include`.
  - `enrichment` object — Per-request tuning for the post-routing truck-aware filter pipeline. None of these fields are sent to the routing engine — they shape what `warnings[]` contains and, via `include_features`, what `features[]` contains. Omit the block to keep defaults.
    - `buffer_m` number — Spatial-join buffer radius around the polyline, in metres.
    - `clearance_pad_m` number — Safety pad added to `truck.height_m` when evaluating bridge_clearances. A warning fires when `clearance_m < truck.height_m + clearance_pad_m`. The same pad governs the on-deck overhead check that emits `deck_clearance` warnings (NBI item 53).
    - `exclude_types` string[] — Drop these feature_types from the spatial-join entirely. Useful for trimming the response when a UI doesn't display, e.g., weigh stations.
    - `include_features` string[] — Opt the response into the `features[]` channel — amenity/POI matches along the route (truck parking, rest areas, service plazas, EV charging), distinct from hazard `warnings[]`. Each entry is a POI feature_type; `rest_areas` are surfaced only when truck-friendly. Parking/rest features carry `available_spots`, `capacity`, and `as_of` (the count's observation time) as first-class fields when the source feed publishes live occupancy (omitted when it doesn't; `available_spots: 0` means known full). A per-hour occupancy *trend* for a specific lot is available on demand from `GET /features/{id}/details` (`availability_trend[]`). Omitted/empty = no `features[]` (the default — no added cost). **Plan-gated by count.** Each plan caps how many distinct feature types you may select at once (Free: none; the cap rises with tier; Enterprise: unlimited). Selecting more types than your plan allows returns `403 feature_types_limit`, and a plan with no allowance returns `403 feature_channel_unavailable`. Your current cap is published as `routing_max_feature_types` on `GET /customer/plans`.
    - `max_distance_m` number — Only emit warnings within this distance from the route origin. 0 (default) = unlimited. Also bounds features[] when set.
    - `min_severity` 'info' | 'warning' | 'critical' — Filter out warnings below this threshold. Ranking is info < warning < critical. Critical warnings (e.g., bridge clearance violations) always pass. Weigh/inspection stations carry an operational `status` (`open`/`closed`/`unknown`) with an `as_of` observation time — when we last confirmed the station in an upstream poll, refreshed at least every 15 minutes, so treat it as accurate to the quarter hour rather than to the second. An **open** station is `warning` severity (you must pull in), while closed/unknown stay `info` — so `min_severity: "warning"` surfaces only stations you must stop at. `border_crossing` warnings (live land-border commercial waits on a cross-border route — CBP and CBSA in North America, the єЧерга electronic queue on Ukraine's EU frontier) carry a severity keyed on the commercial-lane wait: `info` under 15 min, `warning` from 15 min, `critical` past 45 min or when the port is closed. `chain_control` warnings carry a winter chain / traction law in effect on the route. Severity follows how much the control binds a **truck**, not how bad the weather is: `info` for an advisory (chains advised or to be carried), `warning` when chains are required on commercial vehicles, and `critical` when they are required on all vehicles with no exemption at all (California R-3), which accompanies conditions at the edge of a closure. `description` carries the road authority's own wording, prefixed with its own code where it publishes one — California's R-1/R-2 exemptions are written for passenger vehicles and do **not** release a commercial vehicle, so both map to `warning`. `as_of` reports when we last confirmed the reading, which matters because these flip within minutes on a storm front. Every feed that reports a wait sets `properties.commercial_wait_minutes` — always minutes, even where the human-readable `description` renders a multi-day queue as days and hours. **When a feed publishes no wait for a crossing, the field is absent — never `0`.** Several agencies encode "we do not report this" as a literal `--`, `N/A` or `Not Applicable` in the wait column, and those are parsed as *no reading*, not as a clear border; the warning still appears (so the crossing is on your route) at `info` severity with a description that claims no wait. Treat a missing `commercial_wait_minutes` as unknown: `0` is a published zero and means the agency reports no delay. Feed-specific extras: the CBP feed adds a `fast_lane_wait_minutes` / `fast_lane_savings_minutes` pair (the expedited-lane edge for FAST-enrolled carriers) and a `wait_trend` of `rising` / `falling` / `steady` (this poll's commercial wait versus the previous one); єЧерга adds `vehicles_in_queue` (trucks currently queued). Note єЧерга publishes each checkpoint once per vehicle type — only the truck queue drives severity, so a long coach queue never bands a freight route.
    - `provenance` string[] — Restrict `warnings[]` and `features[]` to observations of the listed classes. Omitted/empty = every class (the default). * `authoritative` — a road authority or infrastructure operator published it: a state DOT / 511 system, a national access point, a police CAD feed, a motorway concessionaire. This is the great majority of what we serve. * `commercial` — a commercial traffic-data vendor's probe or telematics observation, republished by the authority. * `crowdsourced` — reported or maintained by end users. `["authoritative"]` is the DOT-only view: use it when a dispatcher must be able to say a lane closure came from the road authority and not from another driver's phone. Filtering happens in the spatial query, so an excluded observation never occupies one of the response's warning slots. Every warning and feature reports its own class in the `provenance` field, so you can also decide per record instead of filtering. The specific upstream feed behind a record is not disclosed.
    - `skip_temporal_filter` boolean — When true, keeps traffic events whose end_time is already past projected arrival. Useful for planning-ahead runs.
  - `exclude_countries` string[] — Hard-exclude these countries from the route (ISO 3166-1 alpha-3). Stricter than avoid_countries — the route will fail rather than pass through.
  - `include` string[] — Optional route detail channels to compute. `polyline` and `summary` are always included. Request `tolls` to have the routing engine price the route's toll systems for your truck profile — the cost then appears as `routes[].sections[].tolls[]` (per toll system) and is rolled up per currency into `routes[].summary.toll_costs[]`. Set `currency` (ISO 4217) to control the fare currency. Toll pricing adds upstream cost, so it is off unless requested. Request `mileage_by_state` for a per-jurisdiction distance breakdown on `routes[].summary.mileage_by_state[]` — one entry per US state / Canadian province the route touches, ordered longest-first, computed by intersecting the route with fixed jurisdiction boundary polygons. A stable, reproducible figure for IFTA quarterly reporting, mileage-based billing, and driver pay. Off unless requested (it adds a spatial query). Request `toll_alternative` (paid plans only; implies `tolls`) to also compute a second, toll-free route and price the trade — extra distance/time vs. toll dollars saved — returned as the top-level `toll_alternative` block. Because this computes a second route, **it is billed as 2 routing calls** (or 1 when the whole response is served from cache). If the second route can't be produced the primary route still returns, with the reason in `toll_alternative_omitted`. Request `parking_trend` to add `typical_occupancy` to every parking/rest feature in `features[]` and to each `hos[].suggested_stops[]` — what that lot's recorded occupancy history says about the hour you are projected to reach it, rather than how many spaces are free right now. Off unless requested (it adds a history query), and a lot with no recorded history simply omits the block.
  - `language` string — BCP-47 (e.g. en-US)
  - `optimize` 'time' | 'distance'
  - `origin` object, required
    - `lat` number, required
    - `lng` number, required
  - `tags` string[] — Free-form labels for fleet/lane analytics rollup. Echoed and stored on the saved route. Max 16 tags, each up to 64 chars.
  - `traffic` 'live' | 'historical' | 'off'
  - `truck` object, required — Required block (the endpoint is truck-only).
    - `axle_count` integer
    - `axle_group_weight_t` object — Per-axle-group weight caps in tonnes (mapped to the router's weight-per-axle-group profile).
      - `single` number
      - `tandem` number
      - `triple` number
    - `axle_weight_t` number
    - `commercial` boolean
    - `fuel` 'diesel' | 'gasoline' | 'lpg' | 'cng'
    - `hazardous` string[]
    - `height_m` number
    - `hos` object — Driver's Hours-of-Service clock at departure. When present (with a `ruleset`), the response gains an `hos[]` projection of where the driver must take a break or stop driving under that regime, each annotated with reachable truck parking / rest areas before the limit. Where a parking feed publishes live occupancy, the reachable-parking suggestions are ordered by open spaces and a stop is flagged `feasible: false` when every reachable lot is reported full. The response also gains `routes[].hos_summary` with the arrival time once mandated rests are inserted, the delay they add, and — when you sent `arrival_time` — whether the trip still meets that window. **Free on every plan** — no quota beyond the routing call itself. Not proxied to the routing engine. A reusable truck profile should store only `ruleset` (your fleet's governing regime); supply the per-trip `*_remaining_s` counters inline — they win over the stored profile per field. Each counter is *seconds remaining* against the ruleset's limit; omit a counter to assume a fresh driver (the full limit).
      - `cycle_remaining_s` integer — Seconds left in the weekly/cycle budget.
      - `drive_remaining_s` integer — Seconds left on the driving limit.
      - `duty_remaining_s` integer — Seconds left in the on-duty window (US/Canada).
      - `elapsed_remaining_s` integer — Seconds left in the elapsed (wall-clock) window (Canada).
      - `ruleset` 'us' | 'canada_south' | 'canada_north' | 'eu' — Hours-of-Service regime. Omit to default from the deployment region (NA → `us`, EU → `eu`). Canadian operators must set `canada_south` (≤60°N) or `canada_north` (>60°N extended-limits tier) explicitly. (`aetr` is planned.)
      - `since_break_s` integer — Seconds of driving since the last qualifying break.
    - `length_m` number
    - `max_speed_kph` integer
    - `occupancy` integer
    - `permit_number` string — Oversize/overweight permit identifier carried by this truck. Not proxied to the routing engine — stored on the saved route and echoed in the response for client-side correlation.
    - `profile` 'tractor' | 'straight' | 'box'
    - `profile_id` string — ID of a reusable truck profile from your fleet library (managed under the developer portal). When set, the server loads that profile's stored truck block and deep-merges any inline `truck.*` fields supplied alongside it on top — inline values win per field. The fully-resolved block is echoed back as `resolved_truck` so you can audit exactly what was applied. A 400 (`profile_not_found`) is returned if the ID isn't one of your profiles.
    - `trailer_axle_count` integer
    - `trailer_count` integer
    - `tunnel_category` 'B' | 'C' | 'D' | 'E'
    - `weight_t` number — Gross combined weight in tonnes
    - `width_m` number
  - `units` 'metric' | 'imperial'
  - `waypoints` object[]
    - `lat` number
    - `lng` number
    - `stop_over` boolean

## Response `200`

Route computed with warnings enriched

## Other responses

- `400` — Validation failure — see `code` for the specific reason
- `403` — Plan does not include the requested capability
- `429` — Quota or rate limit hit. `routing_rate_limit` is the routing-only per-minute gate, separate from the data-API rate limit. Its value varies by plan and is subject to change — discover it from `X-Routing-RateLimit-Limit` / `-Remaining` / `-Reset` (returned on every routing response) and honour `Retry-After`, rather than hardcoding a figure. The live response message names your plan's actual limit.
- `502` — Upstream routing provider unavailable
- `503` — Routing is not configured on this deployment
- `504` — Upstream routing provider timed out

---

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