# POST /v1/distances

> Index: https://kartpunkt.se/llms.txt

operationId: `measureDistances`. Distances from many origins to their destinations.
Tag: `routing`.

Distances along the network from each origin to each of a few destinations, on a profile, each result with its own status, so an origin with no path does not fail the call. The facts asked for are summed over each path: metres and counts NVDB records, never a verdict on whether a route is safe. Use it instead of POST /v1/matrix when you need each path's facts, line or alternative; it costs a calculation per origin per destination.

## Request

`POST https://api.kartpunkt.se/v1/distances` with `Authorization: Bearer $KARTPUNKT_API_KEY` and `Content-Type: application/json`, from your backend (never browser code: the key would leak, and CORS is not enabled).

Example body:

```json
{
  "destinations": [
    {
      "point": {
        "lat": 63.72059,
        "lon": 19.342166,
        "street": "Skolvägen"
      }
    }
  ],
  "origins": [
    {
      "id": "skolvagen",
      "point": {
        "lat": 63.720434,
        "lon": 19.348226,
        "street": "Skolvägen"
      }
    },
    {
      "id": "norrgatan",
      "point": {
        "lat": 63.724508,
        "lon": 19.339523,
        "street": "Norrgatan"
      }
    },
    {
      "id": "wrong-street",
      "point": {
        "lat": 63.716325,
        "lon": 19.35388,
        "street": "Skolvägen"
      }
    },
    {
      "id": "no-road",
      "point": {
        "lat": 63.706412,
        "lon": 19.339788,
        "street": "Ödemarken"
      }
    },
    {
      "id": "disconnected",
      "point": {
        "lat": 63.725224,
        "lon": 19.282034,
        "street": "Skogsbyvägen"
      }
    },
    {
      "point": {
        "lat": 63.719246,
        "lon": 19.301609
      }
    }
  ],
  "profile": "walk",
  "facts": [
    "fast_road",
    "unlit_outside_built_up",
    "speed_limit"
  ]
}
```

JSON body:

- `destinations` (array of object, 1..5 items, required): The destinations, each answered for every origin. results holds one entry per origin per destination, each origin's together in the order of destinations: results[origin × destinations + destination]. Each result names its destination_id and destination_index.
  - `id` (string, max 64 chars, optional): Your own id for the destination, echoed in each result as destination_id.
  - `point` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required)
- `origins` (array of object, 1..1000 items, required): The points to measure from, each answered in results in the order sent. Never the homes of people with protected identities (skyddade personuppgifter).
  - `id` (string, max 64 chars, optional): Your own id for the origin, echoed back.
  - `point` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required)
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), required)
- `vehicle` ([Vehicle](https://api.kartpunkt.se/docs/shapes/Vehicle.md), optional)
- `avoid` ([Avoid](https://api.kartpunkt.se/docs/shapes/Avoid.md), optional)
- `prefer` (array of [Preference](https://api.kartpunkt.se/docs/shapes/Preference.md), 0..6 items, optional): Facts to prefer to keep off, on any profile, each once. Left out or [], the profile's own weighing.
- `alternatives` (array of "unrestricted", 0..1 items, optional): Other paths to answer beside the main one: 'unrestricted', the path without avoid. With avoid, ['unrestricted'] unless sent; send [] for the main path alone.
- `facts` (array of "speed_limit" | "road_owner" | "surface" | "road_class" | "network" | "gcm_type" | "municipality" | "road_name" | "road_number" | "traffic_safety_class" | "built_up" | "lit" | "separated" | "ferry" | "motorway" | "fast_road" | "speed_limit_varies" | "unlit_outside_built_up" | "traffic_volume" | "crossings_without_refuge" | "crossings_unknown_refuge" | "level_crossings" | "max_weight" | "max_axle_load" | "max_height" | "max_width" | "max_length" | "bearing_class" | "hgv_banned" | "hazmat_banned" | "hazmat_route" | "timed_restriction" | "structure" | "barriers", 0..34 items, optional): The facts to report along the path: see GET /v1/facts. None by default.
- `geometry` ("geojson" | "polyline6" | "none", optional, default "none"): Each ok path as a line, the alternatives' too, from where the origin meets the network to where the destination does. GeoJSON, an encoded polyline at 6 decimals, or none, the default. Simplified to within 1 m of the roads, for a map and for storing with the distance.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)

## Response 200

Example:

```json
{
  "avoid": null,
  "destinations": [
    {
      "id": null,
      "snap_m": 14.97,
      "street_matched": true
    }
  ],
  "results": [
    {
      "id": "skolvagen",
      "destination_id": null,
      "destination_index": 0,
      "status": "ok",
      "distance_m": 300,
      "duration_s": 216,
      "facts": {
        "fast_road": 0,
        "unlit_outside_built_up": 0,
        "speed_limit": {
          "40": 300
        }
      },
      "avoid_m": null,
      "snap_m": 11.96,
      "street_matched": true,
      "destination_snap_m": 14.97,
      "destination_street_matched": true,
      "blocked_by": null,
      "alternatives": null
    },
    {
      "id": "norrgatan",
      "destination_id": null,
      "destination_index": 0,
      "status": "ok",
      "distance_m": 560,
      "duration_s": 403,
      "facts": {
        "fast_road": 0,
        "unlit_outside_built_up": 0,
        "speed_limit": {
          "40": 560
        }
      },
      "avoid_m": null,
      "snap_m": 11.98,
      "street_matched": true,
      "destination_snap_m": 14.97,
      "destination_street_matched": true,
      "blocked_by": null,
      "alternatives": null
    },
    {
      "id": "wrong-street",
      "destination_id": null,
      "destination_index": 0,
      "status": "ok",
      "distance_m": 590,
      "duration_s": 425,
      "facts": {
        "fast_road": 0,
        "unlit_outside_built_up": 0,
        "speed_limit": {
          "40": 590
        }
      },
      "avoid_m": null,
      "snap_m": 450.44,
      "street_matched": true,
      "destination_snap_m": 14.97,
      "destination_street_matched": true,
      "blocked_by": null,
      "alternatives": null
    },
    {
      "id": "no-road",
      "destination_id": null,
      "destination_index": 0,
      "status": "no_road_near",
      "distance_m": null,
      "duration_s": null,
      "facts": {},
      "avoid_m": null,
      "snap_m": null,
      "street_matched": null,
      "destination_snap_m": 14.97,
      "destination_street_matched": true,
      "blocked_by": null,
      "alternatives": null
    },
    {
      "id": "disconnected",
      "destination_id": null,
      "destination_index": 0,
      "status": "no_path",
      "distance_m": null,
      "duration_s": null,
      "facts": {},
      "avoid_m": null,
      "snap_m": 9.98,
      "street_matched": true,
      "destination_snap_m": 14.97,
      "destination_street_matched": true,
      "blocked_by": null,
      "alternatives": null
    },
    {
      "id": null,
      "destination_id": null,
      "destination_index": 0,
      "status": "ok",
      "distance_m": 2310,
      "duration_s": 1663,
      "facts": {
        "fast_road": 1400,
        "unlit_outside_built_up": 1400,
        "speed_limit": {
          "40": 910,
          "70": 1400
        }
      },
      "avoid_m": null,
      "snap_m": 20.02,
      "street_matched": null,
      "destination_snap_m": 14.97,
      "destination_street_matched": true,
      "blocked_by": null,
      "alternatives": null
    }
  ],
  "provenance": {
    "synthetic": true,
    "network": {
      "id": "snap_01k7exampleexampleexample0",
      "dataset": "nvdb",
      "source_date": "2026-10-13",
      "built_at": "2026-10-13T07:00:00.000Z",
      "graph": "snap_01k7exampleexampleexample0",
      "profile": "walk",
      "experimental": false
    },
    "sources": [
      {
        "id": "nvdb",
        "name": "Trafikverket, NVDB",
        "licence": "CC0-1.0",
        "attribution": "Vägdata från NVDB, Trafikverket, CC0 1.0",
        "url": "https://creativecommons.org/publicdomain/zero/1.0/",
        "updated": "2026-10-13"
      }
    ]
  }
}
```

- `avoid` (object | null): What avoid matched; null without avoid.
  - `stretches` (array of object | null): Per avoid.stretches entry, in order; null when none was sent.
    - `matched` (boolean): Whether the stretch matched a road. false: none lies along it (drawn where NVDB no longer has a road): redraw it.
  - `values` (array of object | null | null): Per avoid.values entry, in order: with between, the road it matched, to check against the closure's wording; null for an entry without between. null when no avoid.values was sent.
    - `between` (array of object): Where each of the two points met the road, in the order sent.
      - `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md)): Where the point met the road: the nearest point of a piece with the value.
      - `snap_m` (number): Straight-line metres from the point sent to there.
    - `roads` (array of object): The road kept off between the points, from the first point's end of the run to the second's: the run's pieces and those beside it, each whole.
      - `ref` ([RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md) | null): The stretch kept off: an NVDB link and the part of it, measures ascending; null on a road with no NVDB link.
      - `length_m` (number): Metres of the stretch.
    - `length_m` (number): Metres of road kept off, all of roads together.
- `destinations` (array of object): Where each destination met the network, in the order sent, ignoring avoid. A destination with snap_m null answers every origin no_road_near.
  - `id` (string | null): The id sent with it, or null.
  - `snap_m` (number | null): Straight-line metres from the point sent to where it meets the profile's network; not included in distance_m. null when no road of the network lies within 500 m: the status is then no_road_near.
  - `street_matched` (boolean | null): true when the point met a road of the street sent, false when it met the nearest road instead. null when no street was sent or no road is near.
- `results` (array of object)
  - `id` (string | null): The id sent with this result's origin, or null.
  - `destination_id` (string | null): The id sent with this result's destination, or null.
  - `destination_index` (integer): This result's destination, its index in destinations from 0.
  - `status` ("ok" | "no_path" | "ferry_required" | "no_road_near" | "avoided_road_required"): Whether a path joins the two points, and why not: 'ok', a path. 'no_road_near': no road within 500 m of an end. 'no_path': the network joins none. 'ferry_required': only a ferry does. 'avoided_road_required': only what avoid keeps off does, or an end lies on it. Each is a 200 answer, not an error: see "When there is no path".
  - `distance_m` (number | null): Metres along the network between where the ends meet it; null unless status is 'ok'.
  - `duration_s` (number | null): Seconds at free flow: the posted limit, capped at 80 km/h on hgv, and 5 km/h on foot. No traffic. null unless status is 'ok'.
  - `facts` (object): The facts asked for, over the path.
  - `geometry` (object | string | null, optional): The path as one line, in the format asked; null unless status is 'ok'. Left out with geometry 'none'.
    - `type` ("LineString")
    - `coordinates` (array of array of number)
  - `avoid_m` (array of number | null): With avoid.stretches, the path's metres on each stretch, in the order sent, 0 for one it does not touch: metres, never a verdict. null without stretches, or unless status is 'ok'.
  - `snap_m` (number | null): Straight-line metres from the origin's point to where this path starts; without a path, to where the origin met the network. It is not included in distance_m. null only when the origin met no road within 500 m (no_road_near).
  - `street_matched` (boolean | null): With a street sent with the origin: true when this path starts on a road of that name, false when on another. null when no street was sent, or the origin met no road.
  - `destination_snap_m` (number | null): The same as snap_m, for the destination.
  - `destination_street_matched` (boolean | null): The same as street_matched, for the destination.
  - `blocked_by` (array of object | null): With 'avoided_road_required', the avoid entries in the way; null otherwise. Of those the path without avoid runs along or crosses, each that, lifted alone, lets a path keep off the rest; where none alone does, all of them. Where an end meets the network on a road kept off, with no other way on, those that keep off its road.
    - `field` ("stretches" | "facts" | "values"): The list in avoid the entry was sent in.
    - `index` (integer): Its index in that list, from 0.
    - `id` (string | null): The id sent with it; null where none was.
  - `alternatives` (object | null): The alternatives answered, by label: unrestricted, the path without avoid (the shortest, or with prefer the least costly, of all). null when none was.
    - `unrestricted` ([Path](https://api.kartpunkt.se/docs/shapes/Path.md), optional)
- `provenance` ([Provenance](https://api.kartpunkt.se/docs/shapes/Provenance.md))

## Errors

Every error is `{ error, message, param?, doc_url }`; `message` says what to do next. All codes: [https://api.kartpunkt.se/errors](https://api.kartpunkt.se/errors). What to do on each, with retries: [https://api.kartpunkt.se/docs/recipes/errors-and-retries.md](https://api.kartpunkt.se/docs/recipes/errors-and-retries.md).

- [`invalid_request`](https://api.kartpunkt.se/errors/invalid_request) (400): The request does not match the API schema. Fix the fields named in this message; see /openapi.json.
- [`unauthorized`](https://api.kartpunkt.se/errors/unauthorized) (401): Missing or unknown API key. Send it as 'Authorization: Bearer <key>' (or 'x-api-key'), read from the KARTPUNKT_API_KEY environment variable. The user creates a kp_test_ key on their Kartpunkt account page (https://kartpunkt.se/konto) and puts it in KARTPUNKT_API_KEY themselves; never ask them to paste a key into chat.
- [`live_access_required`](https://api.kartpunkt.se/errors/live_access_required) (403): This account is not approved for live data: kp_live_ keys are for customers under contract. Use a kp_test_ key. To ask for live access, the account holder writes to hej@kartpunkt.se, or orders a plan at https://kartpunkt.se/konto#plan.
- [`use_not_permitted`](https://api.kartpunkt.se/errors/use_not_permitted) (403): This operation would use a data source in a way its licence or approval does not allow (see GET /v1/sources). It cannot be answered for this key.
- [`network_unavailable`](https://api.kartpunkt.se/errors/network_unavailable) (404): No network snapshot matches that id or as_of. List the snapshots with GET /v1/networks and send one of their ids, or leave network out for the active one.
- [`network_not_active`](https://api.kartpunkt.se/errors/network_not_active) (409): That network snapshot is not the one served now, and answers on another snapshot are computed as jobs. Send the same input to POST /v1/jobs with this operation, then read GET /v1/jobs/{id}.
- [`profile_unavailable`](https://api.kartpunkt.se/errors/profile_unavailable) (409): This profile cannot be answered on the network snapshot asked for: it was built without the data the profile needs (see GET /v1/profiles and the snapshot's manifest at GET /v1/networks/{id}). Use another profile or a newer snapshot.
- [`network_pruned`](https://api.kartpunkt.se/errors/network_pruned) (410): That network snapshot was pruned: its tables and graph are deleted, so nothing can be answered on it again, as a job or otherwise. Leave network out to answer on the active network, and store that answer's provenance.network.id; GET /v1/networks lists the snapshots that are not pruned.
- [`too_large`](https://api.kartpunkt.se/errors/too_large) (413): Too much for one synchronous call. Send the same input to POST /v1/jobs with this operation and read the result from GET /v1/jobs/{id}, or split it.
- [`quota_exceeded`](https://api.kartpunkt.se/errors/quota_exceeded) (429): Request limit or plan quota reached (per minute, per day, or the account plan's monthly quota). Wait the number of seconds in the Retry-After header, then retry; without Retry-After, no wait helps: tell the account holder what this message says.
- [`internal_error`](https://api.kartpunkt.se/errors/internal_error) (500): Something failed on our side. Retry after a few seconds; if it keeps failing, contact hej@kartpunkt.se.
- [`live_data_unavailable`](https://api.kartpunkt.se/errors/live_data_unavailable) (503): Live data is not available yet: it opens when Kartpunkt's import of Lantmäteriet's address register completes. Use a kp_test_ key until then.
- [`routing_unavailable`](https://api.kartpunkt.se/errors/routing_unavailable) (503): Road distances and routes are not available yet: Kartpunkt's road graph from Trafikverket's NVDB has not been built. Try again later.

## Limits

| Limit | Synchronous | As a job (`POST /v1/jobs`, `operation: "distances"`) |
| --- | --- | --- |
| `origins` | 1000 | 50000 |
| `destinations` | 5 | 5 |
| Origins × destinations | 200 | 50000 |
| Origins × destinations with an `avoid` entry in `mode: "barrier"` | 20 | 50000 |
| Origins × destinations with `geometry` | 200 | 5000 |
| Ways of the network `avoid` keeps off | 20000 | 20000 |
| `prefer` (items) | 6 | 6 |
| `alternatives` (items) | 1 | 1 |
| `facts` (items) | 34 | 34 |
| Request body | 1 MB | 8 MB |

Above a synchronous limit the call is refused (413 `too_large`, or 400 `invalid_request` naming the field); send the same input as a job.

## Metering

Calculations: 1 per origin per destination.
