# MountainSwitch > Real-time road conditions API for California mountain destinations and highways. Built for agents that need destination access, route status, and current restrictions in one place. MountainSwitch normalizes official transportation feeds into a consistent schema for chain controls, closures, lane restrictions, and destination access. Use it to answer questions like "Can I get there?", "Which route is affected?", and "How confident is this data right now?" ## Using This API ### Which endpoint to use - **Discover destinations** (for example, "What places can I plan for right now?"): Use `GET /v1/destinations` first to discover the live supported destination set before requesting destination access or route-specific drilldown. - **Check one destination** (for example, "Is any way into Big Bear confidently open?"): Use `GET /v1/destinations/{destination}/access` after discovery to answer whether a specific destination is confidently reachable. MountainSwitch currently supports California (`US-CA`) only in v1, so omit `jurisdiction` or pass `US-CA`. - **Inspect one route** (for example, "What is happening on SR-18 specifically?"): Use `GET /v1/routes/{route}/summary` when you already know the road you need to inspect. Start with destination access instead if the question is "Can I get there?" - **Filter detailed conditions** (for example, "Show all closed roads in California"): Use `GET /v1/conditions?jurisdiction=US-CA&overall_status={status}`. This returns the individual condition records you need for filtering, troubleshooting, or segment-level review. - **Single segment lookup**: Use `GET /v1/conditions/{id}` with the segment ID. - **System health**: Use `GET /v1/health` to check if upstream data sources are operational. ### Authentication Include an API key via the `Authorization` header: ``` Authorization: Bearer {token} ``` Anonymous access is allowed on all read endpoints at 60 requests/hour per IP. Authenticated access allows 1,000 requests/hour per key. ### Base URL ``` https://api.mountainswitch.co ``` > **Path convention:** All endpoint paths include the version prefix (e.g., `/v1/conditions`). The base URL does NOT include `/v1` — concatenating base + path yields `https://api.mountainswitch.co/v1/conditions`. ### Freshness — always check this Data endpoints (`GET /v1/conditions`, `GET /v1/conditions/{id}`, `GET /v1/destinations/{destination}/access`, and `GET /v1/routes/{route}/summary`) include a `_meta.sources[]` array. - Data endpoints use consumer data usability. `freshness: "stale"` means the source is older than 30 minutes but may still be usable when `live_eligible: true`. `freshness: "down"` means the source is unusable for this response. - `live_eligible` is `true` when this source has consumer-usable data for live results in the requested scope. Stale-but-usable sources (`freshness: "stale"`) may still be `live_eligible: true` through the 60-minute consumer window. `false` means the source has no usable live data for this request because it is consumer-down, bootstrap, or blocked by a semantic integrity/persistence failure. There are two `_meta` exceptions to keep in mind: - Health endpoints (`GET /v1/health` and `GET /v1/health/{adapterId}`) omit `sources`, `query_coverage`, and `pagination` because the health endpoint is the strict operator source-of-truth for adapter status. Do not use health status to override data endpoint usability. - `GET /v1/destinations` omits `sources` and `query_coverage` because it returns destination-registry metadata rather than live source-derived condition truth. It still returns `_meta.pagination = null`. - `live_aggregation_eligible` is the strict processor publish-safety bit from adapter health after strict read-time aging on health endpoints. It is operator-facing and is intentionally not the same as `_meta.sources[].live_eligible` on data endpoints, which represents consumer data usability. Clients must not use `/v1/health` to override data endpoint `_meta.sources[].live_eligible`, `_meta.sources[].freshness`, or `query_coverage`. - Health adapter objects include a nested `processor` object with last processor job timing/outcome fields (`last_queue_lag_ms`, `last_job_outcome`, etc.) for troubleshooting. Do not use `processor` fields to override data endpoint `live_eligible`, `freshness`, or `query_coverage`. If data endpoint `freshness` is `stale` and `live_eligible` is `true`, present the result as last-known usable data and include the age, for example "Last updated 34 min ago." If `freshness` is `down` or `live_eligible` is `false`, do not treat rows as proof of current conditions. Do not treat `last_successful_fetch`, health `status`, or timestamps alone as proof that live list/summary data is usable; check data endpoint `live_eligible`. Also check `_meta.query_coverage`: - `full` — covering sources exist, and every covering source is currently consumer `live_eligible: true`. A full response may include stale-but-usable sources. - `partial` — at least one covering source is currently `live_eligible: true`, but not every covering source qualifies for `full`; results may be incomplete. - `none` — no data available. Either no sources exist for this query, or no covering source is currently `live_eligible: true`. Important: an empty `data` array with `query_coverage: "full"` means **no active non-open conditions were reported** by every relevant consumer-live-eligible covering source. If any source freshness is `stale`, include the last-updated age instead of implying a brand-new check. An empty `data` array with `query_coverage: "none"` means **no data available** because no relevant sources exist or no covering source is currently consumer-live-eligible. If data is empty and `query_coverage` is `"partial"`, say that no restrictions were reported by available sources but no active restrictions can be verified for the route. ### Chain control severity scale The `chain_control_severity` field is an integer from 0 to 4: | Severity | Code | Meaning | |---|---|---| | 0 | `none` | No chain or traction requirements reported | | 1 | `traction_advisory` | Traction devices recommended but not required | | 2 | `traction_required` | Snow tires or traction devices required | | 3 | `chains_awd_exempt` | Chains required, AWD with snow tires exempt | | 4 | `chains_required` | Chains required on all vehicles, no exceptions | > Note: severity 4 covers both maximum non-closure restrictions (`overall_status: "restricted"`) and true closures (`overall_status: "closed"`). Always check `overall_status` rather than inferring closure from severity alone. The corresponding `chain_control_code` field is the string enum: `none`, `traction_advisory`, `traction_required`, `chains_awd_exempt`, `chains_required`. The `chain_control_raw` field preserves the original source value (e.g., "R-2", "Traction Law") for display purposes. ### Common route queries Current live California destinations and their route sets: - **Big Bear Lake**: `SR-18`, `SR-38`, `SR-138`, `SR-330` - **Lake Arrowhead Rim**: `SR-18`, `SR-189`, `SR-330` - **Idyllwild**: `SR-74`, `SR-243` - **Mammoth Lakes**: `US-395`, `SR-203` - **June Lake / Mono Basin**: `US-395`, `SR-158` - **Oakhurst / Bass Lake**: `SR-41` - **Shaver Lake**: `SR-168` - **Kern River Valley**: `SR-178`, `SR-155` Example — check all routes to Big Bear: ``` GET /v1/routes/SR-18/summary?jurisdiction=US-CA GET /v1/routes/SR-38/summary?jurisdiction=US-CA GET /v1/routes/SR-138/summary?jurisdiction=US-CA GET /v1/routes/SR-330/summary?jurisdiction=US-CA ``` Route-summary requests and route-scoped `GET /v1/conditions` default an omitted `jurisdiction` to `US-CA`. If `jurisdiction` is provided, it must be `US-CA`. Cross-jurisdiction route aggregation is not supported in v1. Or query multiple routes at once: ``` GET /v1/conditions?jurisdiction=US-CA&route=SR-18,SR-38,SR-138,SR-330 ``` ### Timestamps All timestamps are ISO 8601 format in UTC with a `Z` suffix (e.g., `2026-03-19T12:47:30Z`). ### Pagination List endpoints are always paginated. Use `cursor` and `limit` query parameters: - `limit`: Number of results per page. Default: 50. Max: 200. - `cursor`: Opaque cursor string from the previous response's `_meta.pagination.cursor`. Check `_meta.pagination.has_more` to determine if more pages exist. The response pagination object uses `cursor` as the field name (e.g., `_meta.pagination.cursor`). Pass this value as the `cursor` query parameter to fetch the next page. `GET /v1/conditions` pagination follows the stable list ordering: `overall_status` priority (`closed` > `restricted` > `advisory` > `open`), then `chain_control_severity` descending, then `id` ascending. The cursor is opaque, but the mirrored payload contract is `CursorPayload = { id: string, o: number, s: number }`, where `o` is `overall_status` rank (`closed=3`, `restricted=2`, `advisory=1`, `open=0`) and `s` is `chain_control_severity`. ### Error handling If the API returns an error, check the `error.code` field. ### Error Codes | HTTP Status | Code | When | |---|---|---| | 400 | `INVALID_PARAMETER` | Malformed query parameter | | 400 | `INVALID_JURISDICTION` | Unknown jurisdiction code | | 401 | `UNAUTHORIZED` | Bearer token present but invalid or expired | | 404 | `ROUTE_NOT_FOUND` | Canonical route value is unknown or unsupported for the requested jurisdiction | | 404 | `DESTINATION_NOT_FOUND` | Canonical destination slug is unknown or unsupported for the requested jurisdiction | | 404 | `CONDITION_NOT_FOUND` | Segment ID does not exist | | 404 | `ADAPTER_NOT_FOUND` | No adapter with this ID exists | | 429 | `RATE_LIMIT_EXCEEDED` | Too many requests | | 500 | `INTERNAL_ERROR` | Unexpected server error | | 503 | `SERVICE_UNAVAILABLE` | D1 or critical infrastructure is unavailable | Common handling guidance: - `ROUTE_NOT_FOUND` (HTTP 404) — the route value is unknown or unsupported. This is NOT the same as "no active conditions." Check `error.details.invalid_routes` to see which requested routes failed validation, and `error.details.known_routes` for valid route names. Retry with one of the known routes. Unknown routes always return 404; they never return 200 with empty data. - `DESTINATION_NOT_FOUND` (HTTP 404) — the canonical destination slug is unknown or unsupported. Use `GET /v1/destinations` to discover supported destination slugs before calling destination access. - **Known route with no data** — if the API returns HTTP 200 with an empty `data` array, the route IS known but has no active conditions. Check `_meta.query_coverage` to distinguish: `"full"` means no active restrictions are currently reported, `"none"` means data sources are unavailable (unknown conditions). Never confuse this with `ROUTE_NOT_FOUND`. - **Source unavailability** is NOT an HTTP error. If a known route's sources are all down, the API returns HTTP 200 with `data: []` and `query_coverage: "none"`. Check `GET /v1/health` for adapter status. 503 is reserved for infrastructure-level failures (e.g., D1 database unavailable), not source outages. - `RATE_LIMIT_EXCEEDED` — slow down requests. Check `X-RateLimit-Remaining` and `X-RateLimit-Reset` response headers. Rate limiting uses a fixed hourly window, and `X-RateLimit-Reset` is always the Unix timestamp for the top of the next hour. - `INVALID_PARAMETER` — a query parameter is malformed. Route-scoped requests may omit `jurisdiction` and default to `US-CA`; providing a non-`US-CA` jurisdiction for a supported California route returns `400 INVALID_PARAMETER`. - `INVALID_JURISDICTION` — unknown jurisdiction code. Use ISO 3166-2 format (e.g., `US-CA`). - `CONDITION_NOT_FOUND` — the segment ID does not exist. Returned by `GET /v1/conditions/{id}`. - `UNAUTHORIZED` — Bearer token was present but invalid or expired. - `ADAPTER_NOT_FOUND` — requested health adapter ID does not exist. - `INTERNAL_ERROR` — unexpected server failure. - `SERVICE_UNAVAILABLE` — critical infrastructure such as D1 is unavailable. Errors always include `_meta.request_id` for debugging. ### Multi-value filtering Comma-separate values for multi-value parameters: ``` GET /v1/conditions?jurisdiction=US-CA&route=SR-18,SR-38&overall_status=advisory,restricted,closed ``` Do not repeat parameter names. `?route=SR-18&route=SR-38` returns `400 INVALID_PARAMETER`; use comma separation instead. Boolean parameters accept only `true` or `false` (case-insensitive). Values like `1`, `0`, `yes`, or `no` are invalid. ### Overall status values The `overall_status` field is one of: `open`, `advisory`, `restricted`, `closed`. - `open` = no active restrictions are currently reported for that record. - `advisory` = informational warning (winter driving advisory, traction recommended). Not a restriction, but not all-clear either. - `restricted` = chains or traction devices required. - `closed` = road is closed. **Default behavior:** Live responses exclude rows where `overall_status === "open"`. Advisory, restricted, and closed conditions are returned by default. This includes lane closures that may have `chain_control_severity = 0` but still have `overall_status: "restricted"` or `"closed"`. To include live open/no-active-restrictions rows, pass `?include_open=true`. When `is_active=false`, inactive rows are returned regardless of current source health and `include_open` is ignored. ## API Reference - `GET /v1/conditions` — List active road conditions. Filter by jurisdiction, route, status, severity. - `GET /v1/conditions/:id` — Get a single road condition segment by ID. - `GET /v1/destinations` — Lists the currently supported destinations. It returns a non-paginated metadata envelope with `destination`, `label`, `jurisdiction`, `jurisdiction_slug`, `routes`, and `communities`. `_meta.sources` and `_meta.query_coverage` are omitted, `_meta.pagination` is `null`, and the endpoint is `no-store`. - `GET /v1/destinations/:destination/access` — Returns the destination access answer in a non-paginated response envelope with destination, path, and segment coverage fields. - `GET /v1/routes/:route/summary` — Returns the route summary after destination discovery or when you already know which route you need to inspect. Includes human-readable summary text. `active_restrictions` counts all non-open conditions, including advisory-only segments (`overall_status: "advisory"`). To determine whether traction or chain requirements exist, check `worst_chain_control_severity`: severity `>= 2` means equipment is required. - `GET /v1/health` — All strict adapter health statuses. Each health object includes `live_aggregation_eligible`, the strict processor publish-safety bit, plus nested `processor` troubleshooting fields. Clients must not use health status or processor fields to override data endpoint `_meta.sources[].live_eligible`, `_meta.sources[].freshness`, or `query_coverage`. - `GET /v1/health/:adapterId` — Get a single adapter's health status. Returns `{ data: , _meta: { request_id, response_generated_at } }`, where `data` has the same shape as one entry from the adapters array in `GET /v1/health`, including `live_aggregation_eligible` and `processor`. - `GET /llms.txt` — Compact agent-oriented API guide. - `GET /openapi.json` — Full OpenAPI 3.0 specification. ## Mirrored Query Parameter Rules | Endpoint | Parameter | Rule | |---|---|---| | `GET /v1/conditions` | `jurisdiction` | ISO 3166-2 code. MountainSwitch currently supports California only in v1, so omit this field or use `US-CA`. Comma-separated for multiple. When `route` is supplied and `jurisdiction` is omitted, default the effective jurisdiction to `US-CA`. | | `GET /v1/conditions` | `route` | Route name. Current live routes are `SR-18`, `SR-38`, `SR-138`, `SR-173`, `SR-189`, `SR-330`, `SR-74`, `SR-243`, `US-395`, `SR-203`, `SR-158`, `SR-41`, `SR-168`, `SR-178`, and `SR-155`. Comma-separated for multiple. | | `GET /v1/conditions` | `overall_status` | `open`, `advisory`, `restricted`, `closed`. Comma-separated for multiple. | | `GET /v1/conditions` | `chain_control_severity_gte` | Minimum chain control severity (0-4). Returns conditions >= this level. See Chain Control Severity Scale below. | | `GET /v1/conditions` | `include_open` | Default: `false`. Set `true` to include open/no-active-restrictions records in the response. Without this flag, only active non-open records are returned (`overall_status != 'open'`). If the caller explicitly includes `open` in the `overall_status` filter, treat `include_open` as effectively `true` for that request even when the query param is omitted. | | `GET /v1/conditions` | `is_active` | Default: `true`. Set `false` to return ONLY inactive records (inactive only, not all records). Inactive rows may be historical/cleared rows, future scheduled lane closures that have not started yet, and off-season chain-control checkpoints. v1 intentionally has no single-call "return active + inactive together" mode; clients that need both must make two calls and merge client-side. When `is_active=false`, current-health filtering does NOT suppress rows; inactive records remain queryable regardless of the source adapter's current health. | | `GET /v1/conditions` | `cursor` | Pagination cursor from previous response. Cursors are `base64(JSON.stringify(CursorPayload))` where `CursorPayload = { id: string, o: number, s: number }`. Invalid cursors return 400 `INVALID_PARAMETER`. | | `GET /v1/conditions` | `limit` | Default: 50. Max: 200. | | `GET /v1/destinations` | `jurisdiction` | Optional jurisdiction code filter. If omitted, return all live supported destinations. In v1, omit it or use `US-CA`. Any other provided known jurisdiction value after normalization returns 400 `INVALID_PARAMETER`. Repeated or unrecognized query parameters return 400 `INVALID_PARAMETER`. | | `GET /v1/routes/:route/summary` | `jurisdiction` | Optional in v1. If omitted, default to `US-CA`. If provided, it must resolve to `US-CA`. | | `GET /v1/routes/:route/summary` | `destination` | Optional canonical destination slug for additive route-row scoping. When provided, it must be a supported destination for the requested route. Unknown slugs return `404 DESTINATION_NOT_FOUND`; known but invalid-for-route slugs return `400 INVALID_PARAMETER`. | ## Coverage Current live coverage: - **Jurisdiction:** `US-CA` - **Destinations:** `big-bear`, `lake-arrowhead-rim`, `idyllwild`, `mammoth-lakes`, `june-lake-mono-basin`, `oakhurst-bass-lake`, `shaver-lake`, `kern-river-valley` - **Routes:** `SR-18`, `SR-38`, `SR-138`, `SR-173`, `SR-189`, `SR-330`, `SR-74`, `SR-243`, `US-395`, `SR-203`, `SR-158`, `SR-41`, `SR-168`, `SR-178`, `SR-155` If a route or jurisdiction is not listed here, treat it as unsupported. ## Optional - [OpenAPI spec](https://api.mountainswitch.co/openapi.json): Full machine-readable API specification with request/response schemas. - [Health endpoint](https://api.mountainswitch.co/v1/health): Check individual adapter status and upstream feed availability.