# API reference

The directions API answers walk, bike, drive and transit routes and isochrones from your backend.

All bodies are JSON. Every routing response carries `Cache-Control: no-store`.

## Auth

`/v1/route` and `/v1/isochrone` need the shared key from `MAPS_API_KEY`:

```
Authorization: Bearer <key>
```

A missing or wrong key answers `401 {"error": "Unauthorized."}`. The key is meant for a backend; never ship it to a browser or app.

## POST /v1/route

One route between two points.

```json
{
  "origin": {"lat": 41.5868, "lng": -93.625},
  "destination": {"lat": 41.6005, "lng": -93.6091},
  "mode": "walk"
}
```

`mode` is `walk`, `bike`, `drive` (Valhalla) or `transit` (OpenTripPlanner).

Success, `200`:

```json
{
  "route": {
    "mode": "transit",
    "duration_s": 1500,
    "distance_m": 4551,
    "path": [[41.5868, -93.625], [41.5871, -93.6249]],
    "steps": [
      {
        "instruction": "Walk to 6th Ave & Keo Way",
        "distance_m": 350,
        "duration_s": 300,
        "travel": "walk",
        "line": null,
        "depart_at": "2026-09-21T14:13:20+00:00",
        "arrive_at": "2026-09-21T14:18:20+00:00"
      },
      {
        "instruction": "Take 6 to DART Central Station",
        "distance_m": 4201,
        "duration_s": 1100,
        "travel": "bus",
        "line": "6",
        "depart_at": "2026-09-21T14:20:00+00:00",
        "arrive_at": "2026-09-21T14:38:20+00:00"
      }
    ]
  }
}
```

- `path` is `[[lat, lng], ...]`. `path` and `steps` are always arrays, possibly empty.
- Numbers are whole: seconds and meters, rounded half to even.
- `travel` is `walk`, `bike`, `drive`, `bus` or `rail`. A transit leg that is not a bus reads as `rail`.
- `line`, `depart_at` and `arrive_at` are `null` on walk, bike and drive routes. On transit, `line` is set on bus and rail steps (short name, else long name), and the times are ISO 8601 in UTC.
- Transit instructions are `Walk to <stop>` or `Take <line> to <stop>`, with `your destination` when a stop has no name.

## POST /v1/isochrone

Areas reachable from a point within each travel time.

```json
{
  "origin": {"lat": 41.5868, "lng": -93.625},
  "mode": "walk",
  "minutes": [15, 30]
}
```

`minutes` holds 1 to 4 different whole numbers from 1 to 120.

Success, `200`, largest area first:

```json
{
  "isochrones": [
    {"minutes": 30, "geometry": {"type": "Polygon", "coordinates": [[[-93.6, 41.5], ...]]}},
    {"minutes": 15, "geometry": {"type": "Polygon", "coordinates": [[[-93.6, 41.5], ...]]}}
  ]
}
```

`geometry` is a GeoJSON Polygon or MultiPolygon, as the engine returns it (`[lng, lat]` order). An area the engine returns without a geometry is left out, so the list can be shorter than `minutes` or empty.

## Errors

Every error body is `{"error": "<short message>"}`.

| Status | `error` | When |
| --- | --- | --- |
| 401 | Unauthorized. | Missing or wrong key |
| 405 | | Wrong method |
| 413 | Request body is too large. | Body over 4 KB |
| 422 | (names the bad field) | Body is not one JSON object, has an unknown field, a point is missing or out of range (lat -90..90, lng -180..180), unknown mode, or bad `minutes` |
| 422 | No route was found. | `/v1/route` only: the engine answered and found no route |
| 503 | Directions are unavailable right now. | The engine timed out (5 s for a route, 15 s for an isochrone), is down, or answered with an error |

## GET /healthz

No key. Always `200`, with whether each engine answers HTTP:

```json
{"valhalla": true, "otp": false}
```

## Map files

`GET` or `HEAD /map/<path>` serves files from `$MAPS_DATA_DIR/map`. No key. Range requests, `ETag`, CORS and `Cache-Control: public, max-age=86400` are supported. Folders are never listed.

## Logging

The server logs method, path, status and duration for each request except map files, viewer assets and `/healthz`, with the `error` text for any refusal. Every `error` text is the server's own fixed wording from the table above, never part of what the caller sent. When an engine fails it also logs the engine name with a status or failure kind. It never logs bodies, coordinates or query strings. A `401` is logged as `WARN`, a `5xx` as `ERROR`; see Logs in the [README](https://github.com/Carpathian-LLC/Carpathian-Maps#logs) for the files.
