# Kartpunkt
> Official Swedish addresses as an API, from Lantmäteriet's address register
> (Belägenhetsadress): search and autocomplete, reverse geocoding,
> validation, change tracking and postnummer lookup. Trafikverket's road
> network (NVDB) as primitives: snap, roads with their facts, routes,
> distances, matrices, reach, GPS traces and route optimization. Each answer
> names the network snapshot it was computed on. Hosted in the EU.
Status: live. `kp_live_` keys answer from Lantmäteriet's address register:
about 3.85 million current addresses, published weekly by Lantmäteriet
and checked for daily. A live key answers once its account is
approved for live access and has a plan (`/konto`). Otherwise it is refused
with a code and a message that says what to do. `kp_test_` keys answer from
a synthetic dataset with the same shapes, on NVDB's real roads, and their
answers carry `provenance.synthetic: true`. Build against a test key;
switching to a live key changes nothing else. On the free Utvecklare plan
an account's test keys share 2000 calculations a month and cannot submit
jobs. On a paid plan (Bas, Pro or Avtal) they submit jobs and use the
plan's calculations, as live keys do.
## When to use Kartpunkt
Use it when your code needs Swedish street addresses from the official
register, or facts and routes on Sweden's road network:
- Address search and autocomplete in a Swedish address form
(`POST /v1/geocode`).
- Turning a coordinate into an address, for example a vehicle's position
(`POST /v1/reverse-geocode`; see the recipe "A vehicle's position").
- Checking an address a customer typed, at checkout or sign-up, before you
ship to it or store it (`POST /v1/addresses/validate`).
- Postnummer to postort and kommun (`GET /v1/postcodes/{postcode}`).
- Keeping addresses you stored current (`POST /v1/addresses/changes`).
- Reading the address a stored id names, now or as it was at a date
(`POST /v1/addresses/lookup`).
- Distances along the roads from many origins to one or a few destinations
(`POST /v1/distances`), and the route itself between two points to draw on
a map (`POST /v1/route`), for example for trip logs (körjournal).
- Routes on several profiles, lorries and buses included, with facts such
as speed limits, owner or bearing class along them.
- Distance matrices (`POST /v1/matrix`), the area reachable within minutes
or metres (`POST /v1/reach`), matching a GPS trace to the roads it
travelled (`POST /v1/trace`), the roads at a point
(`POST /v1/roads/nearby`), and NVDB vector tiles for a map
(`GET /v1/tilesets`).
- Planning vehicles' routes over stops and shipments: which vehicle visits
which, in what order and when, with capacities, time windows and skills
(`POST /v1/optimize`), and checking a plan you have, or one a person
changed, on the same terms (`POST /v1/optimize/check`).
Do not use it for:
- Addresses outside Sweden. Only Swedish addresses are in the register.
- Place, business or point-of-interest names, turn-by-turn directions, or base
map tiles (the only tiles are NVDB's roads, `GET /v1/tilesets`).
- Box addresses. A postnummer used only for PO boxes is not in the register.
- People. There are no owner, resident or tenure data.
- Copying the register. Bulk or sequential harvesting is not allowed.
- Calls from browser code. Call it from your backend.
## Terms
Words used in one sense throughout:
- **Answers.** What a call returns with a 200; a refused call returns an
error instead, a 4xx or 5xx with a code from the error table.
- **A status is an answer.** Many results carry a `status`, and only `ok`
carries a path or a value. A 200 with another status, such as `no_path`
or `avoided_road_required`, is an answer, not an error.
- **Meets the network.** Every point routed from or to is moved onto a road
of the profile: the road its `street` names when one is near, else the
nearest. The path starts or ends where it meets the network.
- **Kept off.** A road `avoid` keeps off is never travelled along; an entry
in `mode: "barrier"` is not crossed at grade either.
- **Snapshot.** One immutable build of NVDB's road network, named by
`provenance.network.id` with its NVDB `source_date`. Sending that id as
`network` answers on the same roads while that snapshot is served.
- **Calculation.** The unit plans meter routing and planning in, counted on
every key of an account, `kp_test_` keys included. Each operation's page
says how it counts under Metering.
- **Synthetic.** A `kp_test_` key's addresses come from a synthetic dataset:
they look real and do not exist. Its roads are NVDB's real ones.
- **Stretch.** A length of one road. In `avoid.stretches` it is a GeoJSON
LineString you draw along a road; a `ref` (RoadRef) names one by its NVDB
link and measures.
- **Segment.** One piece of a route or a matched trace, on one stretch, with
its `length_m`, `ref`, facts and `geometry`. A route's `segments` run in
order from origin to destination.
- **Points.** Every point is `{ lat, lon }`, an address's `point` included.
Every end routed from or to is a StreetPoint, `{ lat, lon, street? }`; a
trace's points are `{ lat, lon, time? }`.
- **Lengths.** Every length is metres, and its name says what it measures:
- `distance_m` on a route, a distances result, a trace, a nearest bus stop
or a matrix cell (`distances_m`) is metres along the network.
- `distance_m` on an address or a road found near a point you sent is the
straight line to it: geocode with `near`, reverse geocoding, roads
nearby.
- `snap_m` is the straight line from a point you sent to where it meets
the network, `/v1/snap` included. It is never part of `distance_m`.
- `length_m` is a segment's or a stretch's own length.
- `radius_m` bounds a straight-line search round a point you sent: snap,
roads nearby, reverse geocoding. `max_distance_m` bounds a distance
along the network.
## Rules for agents
### Before the first call
- Read the key from the environment variable `KARTPUNKT_API_KEY`. Never ask
the user to paste it into chat, and never hardcode it.
- Check auth first: `GET /v1/postcodes/75331` must return 200 before you
write integration code. A 401 means the key is missing or wrong, and a 403
means a `kp_live_` key's account is not approved for live data. A 429
without `Retry-After` means its account has no plan with live calls yet.
- Build and test with a `kp_test_` key. Synthetic addresses look real but
do not exist: never store them as real addresses or show them to end
users as real. A route or a distance marked synthetic is a real one, on
the network a `kp_live_` key routes on, and a synthetic address far from
any road answers `no_road_near`.
- Every answer to a `kp_test_` key says `provenance.synthetic: true`, a
route with no address in it too. `provenance.sources[].id` names the data
an answer rests on: `synthetic` for synthetic addresses, `lantmateriet`
for live ones, `nvdb` for the road network.
- Read the registries before you hard-code a name: `GET /v1/profiles`,
`GET /v1/facts` (every fact with its kind and unit), `GET /v1/sources`
(each source's licence, attribution and allowed uses) and
`GET /v1/networks` (the snapshots). They take a key like every call.
### Every answer
- Every answer from the network or the address register carries
`provenance`: `synthetic`, `network` and `sources[]`. `network` is the
snapshot's `id`, its NVDB `source_date`, `built_at`, `graph`, `profile`
and `experimental`; each source has `licence`, `attribution` and
`updated`, the date of the data you were given. Store `provenance` with
the answer.
- Show every `sources[].attribution` wherever the data is shown: CC BY 4.0
requires it for Lantmäteriet's addresses. The `X-Data-Attribution` header
carries the same texts in ASCII (å→a, ä→a, ö→o, ©→(c)), so show the
body's exact text, not the header's. `X-Kartpunkt-Network` carries the
snapshot's id.
- When a `status` is not `ok`, draw nothing and show the status, and read
an unknown status the same way. Do not retry it: it is an answer.
- Every `POST` takes a JSON body with `Content-Type: application/json`, so a
position or the text a person typed never sits in a URL, which proxies
and logs keep. `GET` paths carry only ids, a road reference or a
postnummer.
- Every error is `{ error, message, param?, doc_url }`: `message` says what
to do next, and `doc_url` is the code's page. Invalid input is 400
`invalid_request`, whose message names each field by its path
(`origins.0.point.lat`) and the rule it breaks, and `param` the field
refused: fix those fields. A field an operation does not take is refused
the same way, never ignored.
- On 429, wait `Retry-After` seconds when it is a minute or less, then send
the call again. A longer `Retry-After` is a daily or monthly quota, and a
429 without `Retry-After` is not fixed by waiting: tell the user what the
message says. A plan is ordered by the account holder, never by you.
- One calculation runs at a time: on 503 `routing_unavailable` with
`Retry-After`, wait that many seconds and send the call again. Every
code's handling is the recipe
[Errors and retries](https://api.kartpunkt.se/docs/recipes/errors-and-retries.md).
### Addresses
- Addresses are single answers, as Lantmäteriet's approval requires: one
search, one point, one address or a few stored ids per call, within each
page's Limits. Your own list is the one exception, as an `address_list`
job; there is no address export and no sweep of addresses you were not
sent. A use a source's terms do not allow is 403 `use_not_permitted`.
- Send your own address list (a register, collection points, a month of
trips) as one job, never as a loop of synchronous calls: `POST /v1/jobs`
with `operation: "address_list"` and
`input: { kind: "validate" | "geocode" | "reverse", rows }`, up to 50000
rows. The result has one row per row sent, in order, with your `id`, a
`status` and the address only when `matched`; never candidates. Every row
counts as one live call and one address, held at submit, so split a list
longer than today's address limit across days.
- Store `id` and `label` from address results; `id` is stable across weekly
refreshes. Store the address source's `provenance.sources[].updated` with
them too.
- Keep stored addresses current with `POST /v1/addresses/changes`, weekly
at most: what to send and what to do with each status is the recipe
[Keeping stored addresses current](https://api.kartpunkt.se/docs/recipes/keeping-stored-addresses-current.md).
- To try every status with a `kp_test_` key, send as `since` the day before
the `updated` date of any answer to that key, and the ids
`se.syn.synh-0380-0001` (answers `changed`: its postnummer changed),
`se.syn.synh-riv-0002` (`closed`) and `se.syn.synh-riv-0001` (`unchanged`).
- `position_type` says what an address's point marks, or `null` where the
register does not say. An `approximate` point was placed only roughly: do
not rely on it where the metre matters. Treat an unknown value as `other`.
- `match_type` on a geocode or validate address: accept `exact`, have a
person confirm `corrected`, a word read as a misspelling, and show
`partial` as a suggestion. Treat an unknown value as `partial`; it is
`null` on reverse geocoding, so read `distance_m` there.
- Neither changes ranking, validation or `score`. Report per län or
distrikt from `county_code` and `district_code`, either may be `null`;
never derive a län from the kommunkod.
- For a vehicle's position, send
`{"point": {"lat": …, "lon": …}, "radius_m": 5000, "distinct": "street", "limit": 3}`
to `POST /v1/reverse-geocode`. Your code then chooses the street it drove
on from the results and `road`, as the recipe
[A vehicle's position](https://api.kartpunkt.se/docs/recipes/a-vehicle-s-position.md)
says. On `road.fallback`, store the road (its `name`, else its
`road_number`) with its `road.municipality_code`: near a kommun border it
can differ from `results[0].municipality_code`.
- `{"point": {"lat": 59.8586, "lon": 17.6389}}` (Svartbäcksgatan 1, Uppsala)
shows both: its three nearest addresses lie on two streets, and `road` is
Svartbäcksgatan, so the choice stores Svartbäcksgatan 1. With a `kp_test_`
key the addresses are synthetic and `road` is a real road from NVDB, so
`provenance.sources` credits NVDB after Kartpunkt's synthetic source.
### Roads and routing
- Profiles: `walk`, `car_shortest`, `car_fastest`, `hgv` and `bike`.
`bike`, the shortest way by bicycle timed at 16 km/h, is experimental and
not yet checked on the national network: its answers say
`provenance.network.experimental: true`, so show that its routes are
experimental.
- On `hgv`, send the vehicle as `vehicle`, the Vehicle shape: weight, axle
load, height, width, length, hazmat, kind and bearing class.
- Reach's budget is `{ "duration_s": [...] }` or `{ "distance_m": [...] }`,
one area per value. A `duration_s` budget needs a profile weighed by time or at one
pace (`car_fastest`, `hgv`, `walk`, `bike`); `car_shortest` takes
`distance_m` only.
- Facts: send the keys you need as `facts`, none by default, on
`/v1/route`, `/v1/distances`, `/v1/trace` and `/v1/roads/nearby`. Each
segment carries each fact's value on it, and `facts` sums each one over
the path by the fact's kind: metres per value, a `{min, max, unknown_m}`
range, metres or a count.
- A fact states what NVDB records, never a verdict. `fast_road` or
`unlit_outside_built_up` is metres, not whether a route is safe, which is
your assessment, and `bearing_class` is a class, not a weight a road may
carry. Timed restrictions (`timed_restriction`, with its window) are
reported, never routed around.
- `separated` counts walk or cycle links NVDB records as separated from
traffic. It never says a road has a path beside it.
- To draw the route between two points, such as a trip by car or a walk,
send `origin`, `destination`, any `via` and a `profile` to
`POST /v1/route`, and draw its `segments` in order when `status` is `ok`.
`duration_s` is free-flow at the speed limits, 5 km/h on foot, not a
prediction. The steps are the recipe
[Drawing a route](https://api.kartpunkt.se/docs/recipes/drawing-a-route.md).
- Route, distances and matrix take the same routing options: `profile`,
`vehicle`, `avoid` and `prefer`. `prefer: [{ fact: "fast_road" }]`, on any
profile, answers the route whose cost is least when each metre with the
fact costs 2, or `cost` above 1 and at most 10, and every other metre 1.
Without `prefer` the call answers the shortest, so ask both and show both.
- `avoid` keeps a path off stretches you draw, off facts and off roads by
number; the answer is the path that keeps off all of it, beside
`alternatives.unrestricted`. Where only what is kept off joins the two
ends, the status is `avoided_road_required`, with `blocked_by` naming each
entry in the way: show it to whoever decides rather than guessing. Each
kind, barriers and a closure between two places included, is in the
recipe [Keeping paths off roads](https://api.kartpunkt.se/docs/recipes/keeping-paths-off-roads.md).
- Every StreetPoint takes an optional `street`: it then meets the road of
that name when one is near, else the nearest road. The answer gives
`snap_m` and `street_matched` for each end: on the path on route and
distances (`destination_snap_m` and `destination_street_matched` for the
destination), per point on snap and nearest. Send the street whenever you
know it, since an address point is often nearer another street's road.
- For distances along the roads from many origins to one to five
destinations, send `destinations`, `origins` and a `profile` to
`POST /v1/distances`. Origins are points: the API takes no address ids for
distances. Every origin is answered in `results`, one entry per
destination, each with its own `status`; how to read, match and store them
is the recipe
[Many origins to a few destinations](https://api.kartpunkt.se/docs/recipes/many-origins-to-a-few-destinations.md).
- To find what is nearest each point along the network, bus stops today,
send `kind: "bus_stop"` and `points` to `POST /v1/nearest`. Its distances
are metres along the network by the profile, `walk` unless sent, never a
straight line.
- Road references: every segment and road has `ref`, a string
`@-`: an NVDB link sequence and the measures 0-1 it covers,
six decimals, e.g. `1000123:45@0.250000-0.500000`. It names the same
stretch in every snapshot that holds its link, so store it as the road's
identity and read it back with `GET /v1/roads/{ref}`, URL-encoded, its
facts as one query parameter per key: `?facts=speed_limit&facts=surface`.
Never parse it or derive anything from its parts; it is `null` on a road
with no NVDB link, as on the synthetic dataset's own roads.
- Leave `network` out to answer on the snapshot served now. Send
`network: {"id": ""}` to answer on the network an
earlier answer used while it is served, or
`network: {"as_of": ""}` for the one served at a moment,
never both. A synchronous call on a snapshot no longer served is 409
`network_not_active`, and a job on it answers only while its graph runs
on the current engine, else the job fails `snapshot_engine_unavailable`, as the recipe
[Errors and retries](https://api.kartpunkt.se/docs/recipes/errors-and-retries.md)
says with the other network refusals.
### Jobs and limits
- Each operation's page has a Limits table, synchronous and as a job. Above
the synchronous limits a call answers 413 `too_large`, or 400
`invalid_request` naming the field: send the same input as a job. Never
split an input into many synchronous calls to avoid a job, whatever the
operation.
- Any of the eight job operations, the ones the `POST /v1/jobs` page
lists, runs as a job with the same input. Submit it, wait for it and read
it as the recipe
[Running a job](https://api.kartpunkt.se/docs/recipes/running-a-job.md)
says, always with an `idempotency_key`. Keys are per account, and an
account reads its own jobs only.
- Jobs need a paid plan: a `kp_test_` key on the free Utvecklare plan is
refused 403 `live_access_required`, whatever the operation. Tell the
account holder, who orders a plan on https://kartpunkt.se/konto#plan or
at hej@kartpunkt.se.
- Jobs run behind synchronous calls on the same routing engine: a job's
next chunk waits while a synchronous calculation runs, so a job slows down
under load but never fails for it. Keep interactive calls synchronous and
send batches as jobs.
- Send every body as plain JSON; a compressed body is not accepted.
`POST /v1/jobs` takes up to 8 MB and every other call up to 1 MB: 50000
origins fit with an id and a street of a few dozen characters each. A
larger body is 413 `too_large`: split the origins over several jobs.
- Each account has a daily request limit, and a `kp_live_` key the quotas
of the account's plan: live calls a month, counted by every address
operation, and returned addresses a day, by every address operation but
postnummer lookup. Past a quota a call is 429 `quota_exceeded`, with
`Retry-After` to 00:00 UTC, or to 00:00 UTC on the 1st for a monthly one.
- Calculations a month count every key of the account, as each operation's
page says under Metering. A job counts its input's, once, when it is
submitted. The free Utvecklare
plan has 2000 a month, Bas 5000, Pro 50000, and the Routes & distances
add-on 50000 more (https://kartpunkt.se/konto#plan).
### What not to send
- Do not send protected identities (skyddade personuppgifter) to the API.
Send coordinates only, never who lives there, and leave out anyone with a
protected identity.
- Coordinates go in JSON bodies only. A trace is used for its answer and
never stored or logged.
- Call the API from server-side code only, never from browser code: the key
would leak, and CORS is not enabled. Proxy autocomplete through your
backend.
- Never loop over a list of addresses to geocode it in bulk: sequential or
bulk harvesting is not allowed, and keys that sweep will be revoked. Call
the API only for addresses a user actually needs.
## Getting a key
The user signs in at [kartpunkt.se/konto](https://kartpunkt.se/konto) (with
Google), creates a `kp_test_` key there and puts it in the project's `.env` as
`KARTPUNKT_API_KEY=…` themselves. Ask them to do that; never ask them to paste
the key into chat, and never write a key into code or commit it. The same
page shows each key's last use, the account's usage per day and operation,
and revokes keys. (Running Kartpunkt locally:
[127.0.0.1:3110/konto](http://127.0.0.1:3110/konto).)
For the person you work for, the same reference is at
[api.kartpunkt.se/docs](https://api.kartpunkt.se/docs), with try-it: they
enter a `kp_test_` key in the page's authentication field, in their own
browser. You do not need it: the pages this index links and
[openapi.json](https://api.kartpunkt.se/openapi.json) say everything it
does.
## Operations
One Markdown page per operation: its request, fields, response, errors, limits and metering. Base URL https://api.kartpunkt.se. Auth: `Authorization: Bearer $KARTPUNKT_API_KEY` or `x-api-key: $KARTPUNKT_API_KEY`, from your backend only. Coordinates are WGS84 `lat` and `lon` decimals. The same operations, machine-readable: [openapi.json](https://api.kartpunkt.se/openapi.json).
Response headers: `X-Data-Attribution` (the attribution texts in ASCII: å→a, ä→a, ö→o, ©→(c); show the body's exact `provenance.sources[].attribution`), `RateLimit-Policy` and `RateLimit` (draft-ietf-httpapi-ratelimit-headers: `"minute";q=;w=60` and `"minute";r=;t=`), `Retry-After` (on 429, and on 503 `routing_unavailable` when a distance calculation is running). Every answer computed on the network or the address register carries `provenance` and the header `X-Kartpunkt-Network` when a network snapshot was used.
### registries
What the API answers on: profiles, facts, sources and their terms, vector tiles, and the network snapshots.
- [GET /v1/profiles](https://api.kartpunkt.se/docs/operations/listProfiles.md): The routing profiles.
- [GET /v1/facts](https://api.kartpunkt.se/docs/operations/listFacts.md): The facts catalog.
- [GET /v1/sources](https://api.kartpunkt.se/docs/operations/listSources.md): The data sources and their terms.
- [GET /v1/tilesets](https://api.kartpunkt.se/docs/operations/listTilesets.md): NVDB vector tiles.
- [GET /v1/networks](https://api.kartpunkt.se/docs/operations/listNetworks.md): Network snapshots.
- [GET /v1/networks/{id}](https://api.kartpunkt.se/docs/operations/getNetwork.md): One network snapshot.
### addresses
Swedish addresses from Lantmäteriet's register: search, reverse geocoding, validation, change tracking and postnummer.
- [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md): Search and autocomplete locations.
- [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md): The nearest addresses to a point.
- [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md): Validate an address.
- [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md): What changed for stored addresses.
- [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md): One address by its id.
- [GET /v1/postcodes/{postcode}](https://api.kartpunkt.se/docs/operations/lookupPostcode.md): A postnummer's postorter and kommuner.
### roads
The road network (NVDB) as roads: where points meet it, roads near a point and a road by its reference, with facts.
- [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md): Meet points to the road network.
- [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md): Roads near a point, with facts.
- [GET /v1/roads/{ref}](https://api.kartpunkt.se/docs/operations/getRoad.md): A road by its reference.
### routing
Paths along the network: a route, distances, nearest points, matrices, reachable areas and GPS traces.
- [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md): A route, with facts along it.
- [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md): Distances from many origins to their destinations.
- [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md): What is nearest each point along the network.
- [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md): An origins × destinations matrix.
- [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md): What can be reached within a budget.
- [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md): The roads a GPS trace travelled, with facts.
### planning
Vehicles' routes over stops and shipments: a plan optimized, or a given plan checked.
- [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md): Plan vehicles' routes over stops and shipments.
- [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md): Check a given plan of vehicles' routes.
### jobs
Any routing or planning call as a job, for inputs above the synchronous limits.
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
- [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md): A job, with its result once done.
- [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md): Cancel a job.
## Shapes
The named shapes the operations use, one page each.
- [ErrorBody](https://api.kartpunkt.se/docs/shapes/ErrorBody.md): Every error response: a stable code, what to do next, the field refused, and the code's page.
- [Profile](https://api.kartpunkt.se/docs/shapes/Profile.md): A routing profile: who travels, which roads they may use and how a path is weighed.
- [Provenance](https://api.kartpunkt.se/docs/shapes/Provenance.md): What an answer was computed from: the network snapshot, the sources read, and whether its data is synthetic.
- [SourceRef](https://api.kartpunkt.se/docs/shapes/SourceRef.md): A data source an answer read, with how to credit it.
- [Address](https://api.kartpunkt.se/docs/shapes/Address.md): An address point or a street-level hit.
- [Point](https://api.kartpunkt.se/docs/shapes/Point.md): A WGS84 coordinate.
- [AddressChange](https://api.kartpunkt.se/docs/shapes/AddressChange.md): What happened to one id sent, comparing what was served at `since` with what is served now.
- [StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md): A WGS84 coordinate, with the street it lies on when known.
- [NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md): The network snapshot to answer on, by id or as_of, one of the two.
- [RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md)
- [Vehicle](https://api.kartpunkt.se/docs/shapes/Vehicle.md): The vehicle's weight, dimensions and kind, on a profile whose `takes_vehicle` is true in GET /v1/profiles (hgv).
- [Avoid](https://api.kartpunkt.se/docs/shapes/Avoid.md): What the path keeps off, on any profile; left out or empty, nothing.
- [AvoidFact](https://api.kartpunkt.se/docs/shapes/AvoidFact.md): A fact to keep off, by its key among those GET /v1/facts marks avoidable.
- [AvoidValue](https://api.kartpunkt.se/docs/shapes/AvoidValue.md): A road kept off by its value, with no line drawn: { fact: 'road_number', values: ['E20', '47'], mode: 'barrier' }, or only between two points on it: { fact: 'road_number', values: ['113'], between:…
- [Preference](https://api.kartpunkt.se/docs/shapes/Preference.md): A fact to prefer to keep off: { fact: 'fast_road', cost: 2 }.
- [FactSummary](https://api.kartpunkt.se/docs/shapes/FactSummary.md): A fact summed over a path: metres per value for a category, {min, max, unknown_m} for a measure, metres for a flag, or a count.
- [SnapResult](https://api.kartpunkt.se/docs/shapes/SnapResult.md): Where a point sent met the network.
- [Segment](https://api.kartpunkt.se/docs/shapes/Segment.md): A stretch of a path: its metres, its road reference, the facts asked for and its line.
- [Path](https://api.kartpunkt.se/docs/shapes/Path.md): A path between two ends, and why there is none where there is none.
- [TracePoint](https://api.kartpunkt.se/docs/shapes/TracePoint.md): A GPS fix: a WGS84 coordinate, with its time when known.
- [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md): fields `{ address_id, as_of }`.
- [Unreachable](https://api.kartpunkt.se/docs/shapes/Unreachable.md): Why a task no vehicle takes is out of reach, and where its place met the network.
- [Unresolved](https://api.kartpunkt.se/docs/shapes/Unresolved.md): fields `{ place, cause }`.
- [Job](https://api.kartpunkt.se/docs/shapes/Job.md): A job: its operation, status and times, and its result once succeeded.
- [JobCheck](https://api.kartpunkt.se/docs/shapes/JobCheck.md): A job checked and not queued (dry_run): a submit of the same body now would be accepted.
## Recipes
Kartpunkt's operations are general. These are the calls for common uses,
built only from the rules above, one page each.
- [Errors and retries: what to do with each refusal](https://api.kartpunkt.se/docs/recipes/errors-and-retries.md)
- [Keeping paths off roads: avoid, barriers and closures](https://api.kartpunkt.se/docs/recipes/keeping-paths-off-roads.md)
- [Drawing a route: segments and geometry](https://api.kartpunkt.se/docs/recipes/drawing-a-route.md)
- [Address ids: store the id, read the address when you need it](https://api.kartpunkt.se/docs/recipes/address-ids.md)
- [Many origins to a few destinations: distances along the roads](https://api.kartpunkt.se/docs/recipes/many-origins-to-a-few-destinations.md)
- [Trip logs (körjournal): a trip's start and end address](https://api.kartpunkt.se/docs/recipes/trip-logs.md)
- [Route optimization (ruttoptimering): vehicles over stops](https://api.kartpunkt.se/docs/recipes/route-optimization.md)
- [Check or compare a plan](https://api.kartpunkt.se/docs/recipes/check-or-compare-a-plan.md)
- [Running a job: submit, wait, read, cancel](https://api.kartpunkt.se/docs/recipes/running-a-job.md)
- [A vehicle's position](https://api.kartpunkt.se/docs/recipes/a-vehicle-s-position.md)
- [Keeping stored addresses current](https://api.kartpunkt.se/docs/recipes/keeping-stored-addresses-current.md)
- [Your own address list: one job, one row per row](https://api.kartpunkt.se/docs/recipes/your-own-address-list.md)
- [When there is no path: a path's status and what to send next](https://api.kartpunkt.se/docs/recipes/when-there-is-no-path.md)
## Errors
Every error is JSON `{ error, message, param?, doc_url }`. `error` is a stable code; `message` says what to do next; `param` names the refused field of an invalid request; `doc_url` is the code's page. Every code, with its status and message: [https://api.kartpunkt.se/errors](https://api.kartpunkt.se/errors). Each operation's page lists the codes it answers.
## Optional
- [Account and keys](https://kartpunkt.se/konto): sign in, create a `kp_test_` key, see usage, revoke keys (Swedish).
- [Developers](https://kartpunkt.se/en/developers.md): the developer page, with the first three steps and the plan limits.
- [Pricing](https://kartpunkt.se/en/pricing.md): the plans and what each includes.
- [Utvecklare](https://kartpunkt.se/utvecklare.md): the developer page in Swedish, with the prompt to paste into an agent.
- [API reference for people](https://api.kartpunkt.se/docs): the OpenAPI document rendered, with try-it for a `kp_test_` key.
- [llms-full.txt](https://kartpunkt.se/llms-full.txt): this index, every page above and the site's pages in Markdown, in one file.
- [Contact](mailto:hej@kartpunkt.se): Zephyr Valley AB, hej@kartpunkt.se.
---
# GET /v1/profiles
> Index: https://kartpunkt.se/llms.txt
operationId: `listProfiles`. The routing profiles.
Tag: `registries`.
Every routing profile: its mode and metric, whether it takes a vehicle, and whether it is experimental. Read it before you hard-code a profile name. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/profiles` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
## Response 200
Example:
```json
{
"profiles": [
{
"name": "walk",
"mode": "walk",
"metric": "shortest",
"description": "On foot, the shortest way by length: every network but motorways, motortrafikleder and ferries, undirected.",
"takes_vehicle": false,
"experimental": false
},
{
"name": "car_shortest",
"mode": "car",
"metric": "shortest",
"description": "By car, the shortest way by length, keeping every forbidden direction and turn and never turning back on an edge: the shortest legal road distance.",
"takes_vehicle": false,
"experimental": false
},
{
"name": "car_fastest",
"mode": "car",
"metric": "fastest",
"description": "By car, the quickest way at free flow: NVDB's posted limit per direction, else the functional class's default; no traffic, no time of day.",
"takes_vehicle": false,
"experimental": false
},
{
"name": "hgv",
"mode": "car",
"metric": "fastest",
"description": "A heavy goods vehicle or, with vehicle.kind bus, a bus, quickest at free flow capped at 80 km/h, kept off roads and turns NVDB closes to its kind in the direction travelled (a road closed to through traffic only is driven only in the zone an end lies in, marked destination_only) and, given the vehicle, off weight, axle-load, height, width and length limits below it, roads whose bearing class carries less than its own, and dangerous-goods bans. Verified on the national NVDB delivery by the restriction tests (scripts/hgv-production.ts); a lorry ban NVDB records one way only is kept off in both directions, and time windows are reported, not routed around.",
"takes_vehicle": true,
"experimental": false
},
{
"name": "bike",
"mode": "bike",
"metric": "shortest",
"description": "By bicycle, the shortest way by length, timed at one pace of 16 km/h: the cycle network, walk and cycle paths NVDB calls cycleable and every road but motorways, motortrafikleder and ferries, keeping each forbidden direction and turn and every traffic ban NVDB records for vehicles or bicycles unless it exempts bicycles (a road closed to through traffic only is ridden only in the zone an end lies in, marked destination_only). No barrier stops it. Experimental until checked on the national network.",
"takes_vehicle": false,
"experimental": true
}
],
"provenance": {
"synthetic": true,
"sources": []
}
}
```
- `profiles` (array of object): Every profile the API answers on.
- `name` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md))
- `mode` ("walk" | "bike" | "car"): Who travels: 'walk' on foot, 'bike' by bicycle, 'car' a motor vehicle, hgv's lorries and buses included.
- `metric` ("shortest" | "fastest"): What a path minimises: 'shortest' its length, 'fastest' its free-flow duration.
- `description` (string): Which roads the profile uses and how it weighs a path.
- `takes_vehicle` (boolean): true when the profile takes a vehicle; any other profile refuses one.
- `experimental` (boolean): true while the profile is not yet verified on the national network; its answers then say provenance.network.experimental: true.
- `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 ' (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.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# GET /v1/facts
> Index: https://kartpunkt.se/llms.txt
operationId: `listFacts`. The facts catalog.
Tag: `registries`.
Every fact a path, a trace or a road can report, with its kind, unit, values, NVDB layers and whether a path can be kept off it. Read it before you hard-code a fact key. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/facts` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
## Response 200
Example:
```json
{
"facts": [
{
"key": "speed_limit",
"kind": "category",
"description": "The posted speed limit in the direction travelled, in km/h; metres where none is recorded (or gångfart) are under \"none\".",
"unit": "km/h",
"values": null,
"avoidable": "range",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_48_Hastighetsgrans"
]
}
},
{
"key": "road_owner",
"kind": "category",
"description": "Who maintains the road (väghållare): the state, a municipality or a private owner.",
"unit": null,
"values": [
"state",
"municipal",
"private",
"unknown"
],
"avoidable": "values",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_2_Vaghallare"
]
}
},
{
"key": "surface",
"kind": "category",
"description": "The wearing course (slitlager): paved or gravel.",
"unit": null,
"values": [
"paved",
"gravel",
"unknown"
],
"avoidable": "values",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_30_Slitlager"
]
}
},
{
"key": "road_class",
"kind": "category",
"description": "Trafikverket's functional road class (funktionell vägklass), 0 (most important) to 9.",
"unit": null,
"values": null,
"avoidable": "range",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_38_FunkVagklass"
]
}
},
{
"key": "network",
"kind": "category",
"description": "The network the path is on: the road network (bil), a cycle path (cykel) or a footpath (gang).",
"unit": null,
"values": [
"bil",
"cykel",
"gang"
],
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_88_Vagtrafiknat"
]
}
},
{
"key": "gcm_type",
"kind": "category",
"description": "The kind of walk or cycle way NVDB records (GCM-vägtyp), e.g. walk_and_cycle_path (gång- och cykelbana), footway (gångbana), cycle_lane (cykelfält), pedestrian_crossing (övergångsställe), cycle_crossing (cykelpassage), cycle_priority_crossing (cykelöverfart), unmarked_crossing (gatupassage utan utmärkning), steps (trappa), square (torg/öppen yta); metres with none recorded, as on most roads, are under \"none\", and a value the catalog does not map under \"unknown\".",
"unit": null,
"values": [
"walk_and_cycle_path",
"walk_and_cycle_path_divided",
"mandatory_cycle_path",
"cycle_lane",
"footway",
"pedestrian_crossing",
"cycle_crossing",
"cycle_and_pedestrian_crossing",
"cycle_priority_crossing",
"cycle_priority_and_pedestrian_crossing",
"unmarked_crossing",
"steps",
"lift",
"square",
"other_cycleable",
"other_not_cycleable",
"none",
"unknown"
],
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_99_GCM_vagtyp"
]
}
},
{
"key": "municipality",
"kind": "category",
"description": "The kommun the road lies in, by its four-digit code.",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"AGGREGAT_O_5_KommunLanReg"
]
}
},
{
"key": "road_name",
"kind": "category",
"description": "The road's name: its gatunamn, else its övrigt vägnamn; metres on roads with neither are under \"none\".",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_20_Gatunamn",
"NVDB_DK_O_21_Ovrigt_vagnamn"
]
}
},
{
"key": "road_number",
"kind": "category",
"description": "The road's number as signed (vägnummer), e.g. E4 or 364; metres on unnumbered roads are under \"none\".",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_111_Vagnummer"
]
}
},
{
"key": "traffic_safety_class",
"kind": "category",
"description": "Trafikverket's traffic safety class for the stretch (TS-klass), as delivered.",
"unit": null,
"values": null,
"avoidable": "range",
"source": {
"source": "nvdb",
"layers": [
"VIS_DK_O_62_Trafiksakerhet_Stracka"
]
}
},
{
"key": "built_up",
"kind": "flag",
"description": "Metres inside a built-up area (tättbebyggt område).",
"unit": "m",
"values": null,
"avoidable": "flag",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_40_TattbebyggtOmrade"
]
}
},
{
"key": "lit",
"kind": "flag",
"description": "Metres on a lit walk or cycle path (GCM belyst); roads carry no lighting record.",
"unit": "m",
"values": null,
"avoidable": "flag",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_97_GCM_belyst"
]
}
},
{
"key": "separated",
"kind": "flag",
"description": "Metres on a walk or cycle path NVDB records as separated from motor traffic (GCM-separation). NVDB records separation on the path's own link, so it says whether the link walked or cycled is separated; it never says whether a road has a path beside it, so metres along a road's carriageway are not counted because a separate path runs beside it.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_98_GCM_separation"
]
}
},
{
"key": "ferry",
"kind": "flag",
"description": "Metres on a ferry route (färjeled).",
"unit": "m",
"values": null,
"avoidable": "flag",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_23_Farjeled"
]
}
},
{
"key": "motorway",
"kind": "flag",
"description": "Metres on a motorway or a motortrafikled.",
"unit": "m",
"values": null,
"avoidable": "flag",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_19_Motorvag",
"NVDB_DK_O_18_Motortrafikled"
]
}
},
{
"key": "fast_road",
"kind": "flag",
"description": "Road exposure: metres along the carriageway of a road whose limit is 70 km/h or more in either direction, functional road class 9 left out; a separate walk or cycle path beside it does not count.",
"unit": "m",
"values": null,
"avoidable": "flag",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_48_Hastighetsgrans",
"NVDB_DK_O_38_FunkVagklass"
]
}
},
{
"key": "speed_limit_varies",
"kind": "flag",
"description": "Metres where NVDB records a deviating limit (by time, season or vehicle) beside the base one.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_48_Hastighetsgrans"
]
}
},
{
"key": "unlit_outside_built_up",
"kind": "metres",
"description": "Road exposure: exact metres outside a built-up area (tättbebyggt område); on a road every such metre, on a walk or cycle path only those not lit.",
"unit": "m",
"values": null,
"avoidable": "flag",
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_40_TattbebyggtOmrade",
"NVDB_DK_O_97_GCM_belyst"
]
}
},
{
"key": "traffic_volume",
"kind": "measure",
"description": "Annual average daily traffic (ÅDT) of the road travelled or crossed, in vehicles a day.",
"unit": "vehicles/day",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"TRAFIK_DK_O_105_Trafik"
]
}
},
{
"key": "crossings_without_refuge",
"kind": "count",
"description": "Road exposure: crossings of a road whose limit is 60 km/h or more where NVDB records no refuge (refug); a crossing counts where the path covers at least half the edge it lies on.",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_27_GCM_passage"
]
}
},
{
"key": "crossings_unknown_refuge",
"kind": "count",
"description": "Road exposure: crossings of a road whose limit is 60 km/h or more where NVDB records the refuge as unknown, or not at all; a crossing counts where the path covers at least half the edge it lies on.",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_27_GCM_passage"
]
}
},
{
"key": "level_crossings",
"kind": "count",
"description": "Level crossings of a railway (plankorsning).",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"AGGREGAT_O_76_Plankorsning_vag_jarnvag"
]
}
},
{
"key": "max_weight",
"kind": "measure",
"description": "The lowest gross-weight limit on the path (Begränsat bruttovikt, or a ForbudTrafik row with a Totalvikt), in tonnes.",
"unit": "t",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_BegrBruttovikt",
"NVDB_DK_O_12_ForbudTrafik"
]
}
},
{
"key": "max_axle_load",
"kind": "measure",
"description": "The lowest axle-load limit on the path (Begränsat axeltryck), in tonnes.",
"unit": "t",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_BegrAxelBoggiTryck"
]
}
},
{
"key": "max_height",
"kind": "measure",
"description": "The lowest free height under an obstacle on the path (Höjdhinder), in metres.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_Hojdhinder45dm"
]
}
},
{
"key": "max_width",
"kind": "measure",
"description": "The lowest vehicle-width limit on the path, in metres.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_BegrFordBredd"
]
}
},
{
"key": "max_length",
"kind": "measure",
"description": "The lowest vehicle-length limit on the path, in metres.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_BegrFordLangd"
]
}
},
{
"key": "bearing_class",
"kind": "category",
"description": "Bärighetsklass (BK1-BK4): metres per class. A class limits gross weight through axle-spacing tables (Trafikförordningen 4 kap.), so it is reported, never turned into one weight.",
"unit": null,
"values": [
"BK1",
"BK2",
"BK3",
"BK4",
"unknown"
],
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_1_Barighet"
]
}
},
{
"key": "hgv_banned",
"kind": "flag",
"description": "Metres where NVDB's traffic bans close the road to a lorry (lastbil, tung lastbil) in the direction travelled: closed, or closed but to line traffic. A road closed to a lorry's through traffic only is not counted: a lorry may drive it to or from an end there.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_12_ForbudTrafik"
]
}
},
{
"key": "hazmat_banned",
"kind": "flag",
"description": "Metres where transport of dangerous goods (farligt gods) is banned.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_InskrTranspFarligtGods"
]
}
},
{
"key": "hazmat_route",
"kind": "flag",
"description": "Metres on a recommended route for dangerous goods.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_*_RekomVagFarligtGods"
]
}
},
{
"key": "timed_restriction",
"kind": "flag",
"description": "Metres under a traffic ban with a time window (ForbudTrafik): routes are not kept off these; the caller checks the window.",
"unit": "m",
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_12_ForbudTrafik"
]
}
},
{
"key": "structure",
"kind": "category",
"description": "Whether the path is on a bridge (överfart), passes under something (underfart) or is in a tunnel (Bro och tunnel): metres per value, \"none\" off any.",
"unit": null,
"values": [
"bridge",
"underpass",
"tunnel",
"none"
],
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_25_Bro_och_tunnel"
]
}
},
{
"key": "barriers",
"kind": "count",
"description": "Barriers across the road (Väghinder: a bollard, a gate, a cycle barrier, blocks) the path passes; one that stops the profile is never on its route.",
"unit": null,
"values": null,
"avoidable": null,
"source": {
"source": "nvdb",
"layers": [
"NVDB_DK_O_37_Vaghinder"
]
}
}
],
"provenance": {
"synthetic": true,
"sources": []
}
}
```
- `facts` (array of object): Every fact in the catalog.
- `key` ("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"): The fact's key, as facts and avoid.facts take it.
- `kind` ("category" | "measure" | "flag" | "metres" | "count"): How the fact is summed over a path: 'category' as metres per value, 'measure' as its min and max, 'flag' and 'metres' as metres, 'count' as a count.
- `description` (string): What NVDB records for the fact.
- `unit` (string | null): The value's unit, e.g. 'km/h'; null where the value has none.
- `values` (array of string | null): The values the fact can take, where the catalog fixes them; null otherwise.
- `avoidable` ("flag" | "range" | "values" | null): How avoid.facts keeps a path off the fact: 'flag' as { fact }, 'range' as { fact, min, max }, 'values' as { fact, values }. A flag can also be preferred against in prefer. null where a path cannot be kept off it (ADR-0021 §5).
- `source` (object): Where the fact is read from.
- `source` ("nvdb"): The source the fact is read from: always 'nvdb'.
- `layers` (array of string): The NVDB layers the fact is read from.
- `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 ' (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.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# GET /v1/sources
> Index: https://kartpunkt.se/llms.txt
operationId: `listSources`. The data sources and their terms.
Tag: `registries`.
Every data source: its licence, its attribution, the uses its terms allow and how long job results derived from it are kept. Read it before you hard-code a source id or a retention time. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/sources` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
## Response 200
Example:
```json
{
"sources": [
{
"id": "lantmateriet",
"name": "Lantmäteriet, Belägenhetsadress",
"licence": "CC-BY-4.0",
"attribution": "Belägenhetsadress Nedladdning, vektor ©Lantmäteriet, bearbetad av Kartpunkt, CC BY 4.0",
"url": "https://creativecommons.org/licenses/by/4.0/",
"terms": "Lantmäteriet decision LM2026/143671 of 2026-10-13 (docs/compliance.md §6): single answers via API, no extraction, no joins with owner or resident data, history at most 24 months; supplement LM2026/159999 of 2026-10-13 (docs/compliance.md §2b): route optimization from stored address ids (use 2), at most 1 000 a call and 50 000 a job, answering routes, times and the caller's own ids, never the addresses; and the customer's own list (use 3) validated, geocoded or reverse geocoded as a job of at most 50 000 rows, one row out per row in, only to that customer, its input deleted when the job ends and its result kept at most 7 days",
"uses": [
"single_answer",
"id_resolution",
"own_list"
],
"result_ttl_hours": 24
},
{
"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/",
"terms": "CC0 1.0: no restriction",
"uses": [
"single_answer",
"bulk",
"aggregate",
"derived_product"
],
"result_ttl_hours": 168
},
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"terms": "Kartpunkt's own, made up: no restriction",
"uses": [
"single_answer",
"bulk",
"aggregate",
"derived_product",
"id_resolution",
"own_list"
],
"result_ttl_hours": 168
}
],
"provenance": {
"synthetic": true,
"sources": []
}
}
```
- `sources` (array of object): Every source an answer can read.
- `id` (string): The source's id, as provenance.sources[].id names it.
- `name` (string): The source's name.
- `licence` (string): The source's licence, as an SPDX id.
- `attribution` (string): The credit to show wherever the source's data is shown or passed on.
- `url` (string): The licence's URL.
- `terms` (string): The licence or approval the allowed uses rest on.
- `uses` (array of "single_answer" | "bulk" | "aggregate" | "derived_product" | "id_resolution" | "own_list"): What the terms allow: 'single_answer' a few records per answer, 'bulk' many in one answer or job result, 'aggregate' counts or statistics over them, 'derived_product' a published dataset made from them. 'id_resolution' resolves stored address ids inside an answer that never returns the addresses, and 'own_list' matches the caller's own list row by row.
- `result_ttl_hours` (number): Hours a job result derived from the source is kept.
- `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 ' (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.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# GET /v1/tilesets
> Index: https://kartpunkt.se/llms.txt
operationId: `listTilesets`. NVDB vector tiles.
Tag: `registries`.
The NVDB vector tiles of the network served now: a PMTiles archive for MapLibre, with speed limit, owner, surface, class, bearing class, lighting, fast_road and vehicle limits on each road and path. Use it to draw the network on a map; for one road's facts, use GET /v1/roads/{ref}. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/tilesets` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
## Response 200
Example:
```json
{
"tilesets": [],
"provenance": {
"synthetic": true,
"sources": []
}
}
```
- `tilesets` (array of object): The tilesets of the network served now; empty while none is built.
- `name` (string): The tileset's name.
- `url` (string): The PMTiles archive: read it with the pmtiles protocol in MapLibre. The URL names its snapshot and never changes; CORS is open (NVDB is CC0).
- `network` (string): The network snapshot the tiles were built from.
- `source_date` (string | null): The NVDB delivery date (YYYY-MM-DD) of that snapshot.
- `minzoom` (number): The lowest zoom level the archive holds.
- `maxzoom` (number): The highest zoom level the archive holds.
- `layers` (array of object): The archive's vector layers.
- `id` (string): The vector layer's id.
- `attributes` (array of string): The attributes its features carry.
- `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 ' (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.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# GET /v1/networks
> Index: https://kartpunkt.se/llms.txt
operationId: `listNetworks`. Network snapshots.
Tag: `registries`.
The road network's snapshots, newest first, a page at a time: each NVDB build, its status, when it was served and which profiles it answers. Read it to choose a snapshot for network.id; a pruned one is not listed. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/networks` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
Parameters:
- `limit` (query, integer 1..100, optional): Snapshots per page; 50 when left out.
- `cursor` (query, string, max 200 chars, optional): The previous page's next_cursor, unchanged. Left out, the first page.
## Response 200
Example:
```json
{
"networks": [
{
"id": "snap_01k7exampleexampleexample0",
"status": "active",
"source": "synthetic",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:00:00.000Z",
"active_from": "2026-10-13T07:01:00.000Z",
"active_until": null,
"profiles": [
"walk",
"car_shortest",
"car_fastest",
"bike"
]
}
],
"next_cursor": null,
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01k7exampleexampleexample0",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:02:00.000Z",
"graph": "snap_01k7exampleexampleexample0"
},
"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"
}
]
}
}
```
- `networks` (array of object): The snapshots on this page, newest first.
- `id` (string): The snapshot's id, as network.id takes it.
- `status` ("building" | "staged" | "validated" | "active" | "retired" | "archived" | "failed"): Where the snapshot is in its life (ADR-0010): 'building', 'staged' and 'validated' before it serves; 'active' the one served now; 'retired' and 'archived' served before, answered as jobs; 'failed' a build that stopped.
- `source` (string): The dataset the snapshot is built from: 'nvdb'.
- `source_date` (string | null): The NVDB delivery date (YYYY-MM-DD) the snapshot was built from.
- `built_at` (string): When the snapshot was built (RFC 3339).
- `active_from` (string | null): When the snapshot was first served (RFC 3339); null if it never was.
- `active_until` (string | null): When the snapshot stopped being served (RFC 3339); null while it is served, or if it never was.
- `profiles` (array of [Profile](https://api.kartpunkt.se/docs/shapes/Profile.md)): The profiles this snapshot can answer.
- `next_cursor` (string | null): Send it as cursor for the next page; null on the last page.
- `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 ' (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.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# GET /v1/networks/{id}
> Index: https://kartpunkt.se/llms.txt
operationId: `getNetwork`. One network snapshot.
Tag: `registries`.
One network snapshot with its manifest: the NVDB deliveries, counts, layers read and code version it was built from. Use it to see what a stored provenance.network.id was built from; GET /v1/networks lists them all. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/networks/{id}` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
Parameters:
- `id` (path, string, pattern `^snap_[a-z0-9]+$`, required): The network snapshot's id, from GET /v1/networks or an answer's provenance.network.id.
## Response 200
Example:
```json
{
"id": "snap_01k7exampleexampleexample0",
"status": "active",
"source": "synthetic",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:00:00.000Z",
"active_from": "2026-10-13T07:01:00.000Z",
"active_until": null,
"profiles": [
"walk",
"car_shortest",
"car_fastest",
"bike"
],
"manifest": {
"counts": {
"edges": 17824,
"stops": 405,
"turns": 1,
"vertices": 17827
},
"test_network": "synthetic-v20:150000"
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01k7exampleexampleexample0",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:02:00.000Z",
"graph": "snap_01k7exampleexampleexample0"
},
"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"
}
]
}
}
```
- `id` (string): The snapshot's id, as network.id takes it.
- `status` ("building" | "staged" | "validated" | "active" | "retired" | "archived" | "failed"): Where the snapshot is in its life (ADR-0010): 'building', 'staged' and 'validated' before it serves; 'active' the one served now; 'retired' and 'archived' served before, answered as jobs; 'failed' a build that stopped.
- `source` (string): The dataset the snapshot is built from: 'nvdb'.
- `source_date` (string | null): The NVDB delivery date (YYYY-MM-DD) the snapshot was built from.
- `built_at` (string): When the snapshot was built (RFC 3339).
- `active_from` (string | null): When the snapshot was first served (RFC 3339); null if it never was.
- `active_until` (string | null): When the snapshot stopped being served (RFC 3339); null while it is served, or if it never was.
- `profiles` (array of [Profile](https://api.kartpunkt.se/docs/shapes/Profile.md)): The profiles this snapshot can answer.
- `manifest` (object): How the snapshot was built: deliveries, counts, the layers read, the code version.
- `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 ' (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.
- [`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_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.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# POST /v1/geocode
> Index: https://kartpunkt.se/llms.txt
operationId: `searchAddresses`. Search and autocomplete locations.
Tag: `addresses`.
Typo-tolerant search and autocomplete of Swedish addresses from the text a person types: street-level hits until a house number is typed, then address points. To check a whole address a person entered, use `POST /v1/addresses/validate`; to turn a coordinate into an address, `POST /v1/reverse-geocode`.
## Request
`POST https://api.kartpunkt.se/v1/geocode` 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
{
"q": "Östra Ågatan, Uppsala"
}
```
JSON body:
- `q` (string, max 200 chars, required): Free text as typed, street first, e.g. 'Östra Ågatan 12B, Uppsala' or 'ostra agatan 12 b'. Typos, å/ä/ö without diacritics and abbreviations such as sthlm and gbg are handled. Only 8 distinct words are read, a postnummer as one; past them every hit is match_type 'partial'.
- `limit` (integer 1..20, optional, default 10): The most results to answer, best first.
- `near` (object, optional): A focus point: nearer same-named streets rank first, and every result gets distance_m.
- `lat` (number -90..90, required): Latitude, WGS84 decimal degrees, -90 to 90.
- `lon` (number -180..180, required): Longitude, WGS84 decimal degrees, -180 to 180.
- `municipality_codes` (array of string, pattern `^\d{4}$`, 1..10 items, optional): Kommunkoder, SCB's four-digit codes, e.g. ['0380']: only hits whose municipality_code is one of them are answered. They narrow before ranking, so a small kommun's street is found past same-named streets elsewhere. Only Sweden's 290 codes are accepted, a repeated one read once. A kommun with no address in the key's dataset answers results: []. Left out: all of Sweden.
- `types` (array of "address", at least 1, optional, default ["address"]): What to look for: addresses today, from Lantmäteriet's Belägenhetsadress or, for a kp_test_ key, the synthetic dataset. Place names and POIs join later.
## Response 200
Example:
```json
{
"results": [
{
"id": "street:218126d23ec3b9e4df37b27ef664b288",
"type": "street",
"label": "Östra Ågatan, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": null,
"letter": null,
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.922464897740404,
"lon": 17.582517826500723
},
"position_type": null,
"match_type": "exact",
"score": 6.596714830317912
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-13"
}
]
}
}
```
- `results` (array of [Address](https://api.kartpunkt.se/docs/shapes/Address.md)): Best matches first; street-level hits until a house number is typed.
- `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 ' (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.
- [`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.
## Limits
| Limit | Synchronous |
| --- | --- |
| `limit` (at most) | 20 |
| `municipality_codes` (items) | 10 |
| Request body | 1 MB |
## Metering
With a `kp_live_` key, one live call per request; each address in the answer counts toward the plan's daily address limit.
---
# POST /v1/reverse-geocode
> Index: https://kartpunkt.se/llms.txt
operationId: `reverseGeocode`. The nearest addresses to a point.
Tag: `addresses`.
The nearest current addresses to a WGS84 point, nearest first with their `distance_m`, and the nearest named road beside them as `road`. For a vehicle's position, choose the street it drove on from the results yourself: see the recipe "A vehicle's position". To find addresses from text, use `POST /v1/geocode`.
## Request
`POST https://api.kartpunkt.se/v1/reverse-geocode` 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
{
"point": {
"lat": 59.922385,
"lon": 17.582745
},
"limit": 3
}
```
JSON body:
- `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md), required)
- `types` (array of "address", at least 1, optional, default ["address"]): What to look for: addresses today, from Lantmäteriet's Belägenhetsadress or, for a kp_test_ key, the synthetic dataset. Place names and POIs join later.
- `limit` (integer 1..10, optional, default 1): How many addresses to return, nearest first. With distinct 'street' it counts streets: limit 3 answers the three nearest streets, one address each.
- `radius_m` (number <= 50000, optional, default 2000): Ignore addresses farther away than this, in straight-line metres. For a vehicle's position send "radius_m": 5000, "distinct": "street", "limit": 3, as the recipe "A vehicle's position" does: 5000 m reaches the nearest address from a rural road.
- `distinct` ("street", optional): Send 'street' for one address per street: the nearest address of each street within radius_m, nearest first, with limit counting streets. A street is one street name in one postort and one kommun, as a search street hit is. Left out, several results can lie on one street.
## Response 200
Example:
```json
{
"results": [
{
"id": "se.syn.syn-0000t9b",
"type": "address",
"label": "Östra Ågatan 12B, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 0.03
},
{
"id": "se.syn.syn-0000t9a",
"type": "address",
"label": "Östra Ågatan 12A, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "A",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238919581831,
"lon": 17.58281664402885
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 4.03
},
{
"id": "se.syn.syn-0000t99",
"type": "address",
"label": "Östra Ågatan 12, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": null,
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92239328453523,
"lon": 17.582887649142425
},
"position_type": "building",
"match_type": null,
"score": 1,
"distance_m": 8.03
}
],
"road": {
"name": "Östra Ågatan",
"road_number": null,
"road_class": 6,
"speed_limit_kmh": 50,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 22.02,
"fallback": false
},
"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"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-13"
},
{
"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"
}
]
}
}
```
- `results` (array of [Address](https://api.kartpunkt.se/docs/shapes/Address.md)): The nearest addresses, nearest first.
- `road` (object | null): The nearest road a car can use with a name or number, within 100 m of the point; null when none is, or no road graph is built yet. It is NVDB's real road for every key. It has no id and is not counted in results. To choose it or an address, see the recipe "A vehicle's position".
- `name` (string | null): The road's street name (gatunamn), the name addresses on it are formed with, else its other official name. null when the road has only a number.
- `road_number` (string | null): The road's number as signed, e.g. 'E6' for a europaväg or '158'; null when it has none. Never null together with name.
- `road_class` (integer 0..9 | null): Functional road class (funktionell vägklass) as Trafikverket delivers it, from 0 for the roads most important to the whole network, such as motorways, to 9 for the least. 0-2 is a through road and 6-9 a local street; null when the road has none.
- `speed_limit_kmh` (integer | null): The base speed limit (hastighetsgräns) in km/h, e.g. 50, where the road is nearest the point, whatever the time of the call. A point has no direction of travel, so where the two directions have different limits this is the lower. null where no limit is recorded or the limit is gångfart (walking pace), which has no figure.
- `speed_limit_varies` (boolean): true when the stretch also has a limit that applies only at certain times or to certain vehicles, which this answer does not carry: a summer limit, a lower limit on weekday mornings or one for heavy vehicles. false otherwise, and when no limit is recorded.
- `owner` ("state" | "municipal" | "private" | null): The road owner (väghållare), who keeps the road where it is nearest the point: 'state' (Trafikverket), 'municipal' (a kommun) or 'private' (a road association, a company or a person). The kind only, never who; null where none is recorded.
- `surface` ("paved" | "gravel" | null): The surface (slitlager) where the road is nearest the point: 'paved' or 'gravel'; null where none is recorded.
- `municipality_code` (string | null): Kommunkod, four digits, e.g. '1080': the kommun NVDB registers the road's stretch in, never the kommun's name; null where none is recorded. It is a fact about the road, so near a kommun border it can differ from results[0].municipality_code.
- `distance_m` (number): Straight-line metres (spheroidal) from the point sent to the nearest point of the road.
- `fallback` (boolean): true when no address in results is within 50 m of the point: results is empty, or its first address is farther away.
- `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 ' (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.
- [`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.
## Limits
| Limit | Synchronous |
| --- | --- |
| `limit` (at most) | 10 |
| `radius_m` (at most) | 50000 |
| Request body | 1 MB |
## Metering
With a `kp_live_` key, one live call per request; each address in the answer counts toward the plan's daily address limit.
---
# POST /v1/addresses/validate
> Index: https://kartpunkt.se/llms.txt
operationId: `validateAddress`. Validate an address.
Tag: `addresses`.
Whether an address a person entered exists as given, with the closest match, what differs in `issues` and other candidates; it is never silently corrected. For autocomplete while the person types, use `POST /v1/geocode`.
## Request
`POST https://api.kartpunkt.se/v1/addresses/validate` 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
{
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797"
}
```
JSON body:
- `street` (string, max 200 chars, required): Street name or address area, e.g. 'Östra Ågatan'. Case and å/ä/ö are ignored.
- `number` (integer 0..99999 | null, optional): House number, e.g. 12.
- `letter` (string, max 2 chars | null, optional): Letter after the number, e.g. 'B'.
- `postcode` (string, max 10 chars | null, optional): Postnummer, e.g. '75331' or '753 31'. Scopes the match; strongly recommended.
- `locality` (string, max 100 chars | null, optional): Postort or kommun, e.g. 'Uppsala'. Scopes the match when postcode is missing.
## Response 200
Example:
```json
{
"valid": true,
"match": {
"id": "se.syn.syn-0000t9b",
"type": "address",
"label": "Östra Ågatan 12B, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"position_type": "entrance",
"match_type": "exact",
"score": 1
},
"issues": [],
"suggestions": [],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-13"
}
]
}
}
```
- `valid` (boolean): true only if the address exists exactly as given (no issues).
- `match` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md) | null): The closest existing address, or null when the street was not found; on number_not_found, the nearest number on the street. It is never silently corrected: compare it with the input.
- `issues` (array of "street_not_found" | "number_not_found" | "letter_not_found" | "postcode_mismatch" | "locality_mismatch" | "ambiguous"): What is wrong with the input; empty when valid.
- `suggestions` (array of [Address](https://api.kartpunkt.se/docs/shapes/Address.md)): Other candidates: on street_not_found, search hits for the input; on number_not_found, the nearest house numbers on the street. Otherwise empty.
- `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 ' (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.
- [`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.
## Limits
| Limit | Synchronous |
| --- | --- |
| `number` (at most) | 99999 |
| Request body | 1 MB |
## Metering
With a `kp_live_` key, one live call per request; each address in the answer counts toward the plan's daily address limit.
---
# POST /v1/addresses/changes
> Index: https://kartpunkt.se/llms.txt
operationId: `checkAddressChanges`. What changed for stored addresses.
Tag: `addresses`.
For address ids stored from earlier answers, what happened since a date: each is `unchanged`, `changed` with the fields that differ, `closed` or `unknown`. It answers only for the ids sent; there is no feed of the register. See the recipe "Keeping stored addresses current".
## Request
`POST https://api.kartpunkt.se/v1/addresses/changes` 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
{
"ids": [
"se.syn.syn-0000t9b",
"se.syn.not-an-address",
"se.lm.not-in-this-dataset"
],
"since": "2026-10-13"
}
```
JSON body:
- `ids` (array of string, max 100 chars, 1..20 items, required): The address ids to check, as stored from earlier answers' `id`; split a longer list into several calls. A kp_test_ key's ids are checked against the synthetic dataset only, a kp_live_ key's against the live one. An id sent twice is answered once.
- `since` (string, date YYYY-MM-DD, required): The date (YYYY-MM-DD) the stored addresses were last known current: the `provenance.sources[].updated` of the answer they came from, not the day they were stored. Changes dated after it are reported. It is at most today (UTC) and at most 24 months back, the history kept; with no stored date, send the earliest accepted.
## Response 200
Example:
```json
{
"results": [
{
"id": "se.syn.syn-0000t9b",
"status": "unchanged"
},
{
"id": "se.syn.not-an-address",
"status": "unknown"
},
{
"id": "se.lm.not-in-this-dataset",
"status": "unknown"
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-13"
}
]
}
}
```
- `results` (array of [AddressChange](https://api.kartpunkt.se/docs/shapes/AddressChange.md)): One result per distinct id sent, in the order sent. Never empty: an id with nothing to report is 'unchanged' or 'unknown'.
- `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 ' (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.
- [`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.
## Limits
| Limit | Synchronous |
| --- | --- |
| `ids` (items) | 20 |
| Request body | 1 MB |
## Metering
With a `kp_live_` key, one live call per request; each address in the answer counts toward the plan's daily address limit.
---
# POST /v1/addresses/lookup
> Index: https://kartpunkt.se/llms.txt
operationId: `lookupAddress`. One address by its id.
Tag: `addresses`.
The address one stored id names, as served now or at as_of: the id an earlier answer gave you, which is Lantmäteriet's own object identity of the address (se.lm.…), not Kartpunkt's. One id per call. A closed id is 404 address_closed, an id this key's dataset never held 404 address_unknown. To check many stored ids at once, use POST /v1/addresses/changes.
## Request
`POST https://api.kartpunkt.se/v1/addresses/lookup` 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
{
"id": "se.syn.syn-0000t9b"
}
```
JSON body:
- `id` (string, max 100 chars, pattern `^se\.(lm|syn)\.[A-Za-z0-9-]{1,80}$`, required): One address id, as an earlier answer's id gave it: 'se.lm.' and Lantmäteriet's object identity of the belägenhetsadress for a kp_live_ key, 'se.syn.' and a code for a kp_test_ key. A street hit's id ('street:…') names no address.
- `as_of` (string, date-time (RFC 3339, with offset), optional): The version of the address served at this instant (RFC 3339 with offset), e.g. when your decision was made, so the same call answers the same again; left out, or later than now, the version served now. Not more than 24 months back: versions are kept that long.
## Response 200
Example:
```json
{
"address": {
"id": "se.syn.syn-0000t9b",
"type": "address",
"label": "Östra Ågatan 12B, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"position_type": "entrance",
"match_type": null,
"score": 1
},
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-13",
"as_of": "2026-10-13T07:00:00.000Z"
}
]
}
}
```
- `address` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md)): The address the id names, as served at the instant read (score 1, match_type null: no words were sent).
- `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 ' (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.
- [`address_closed`](https://api.kartpunkt.se/errors/address_closed) (404): This address id is not served on the date asked (as_of, else now): a delivery of the register closed it (the address was removed from the register, or is no longer current there). The register names no successor, so find the address again with POST /v1/addresses/validate or POST /v1/geocode and store the new id; mark the old one closed rather than deleting it, since the register can serve the same id again. For the version served before it closed, send as_of an instant when it was served.
- [`address_unknown`](https://api.kartpunkt.se/errors/address_unknown) (404): No version of this address id in this key's dataset on the date asked. It was never issued, is past the 24 months of history kept, opened only after as_of, or is the other dataset's (a kp_test_ key reads only synthetic se.syn. ids, a kp_live_ key only Lantmäteriet's se.lm. ids). Send an id an earlier answer of this key gave; after going live, look your addresses up again with the kp_live_ key.
- [`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.
## Limits
| Limit | Synchronous |
| --- | --- |
| Request body | 1 MB |
## Metering
With a `kp_live_` key, one live call per request; each address in the answer counts toward the plan's daily address limit.
---
# GET /v1/postcodes/{postcode}
> Index: https://kartpunkt.se/llms.txt
operationId: `lookupPostcode`. A postnummer's postorter and kommuner.
Tag: `addresses`.
The postorter and kommuner a postnummer is used in, with how many addresses each has, from the address register. To find or check a whole address, use `POST /v1/geocode` or `POST /v1/addresses/validate`.
## Request
`GET https://api.kartpunkt.se/v1/postcodes/{postcode}` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
Parameters:
- `postcode` (path, string, pattern `^\d{3} ?\d{2}$`, required): Postnummer, five digits, optionally with a space after the third (URL-encode the space).
## Response 200
Example:
```json
{
"postcode": "75797",
"localities": [
{
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"addresses": 30
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-13"
}
]
}
}
```
- `postcode` (string): Postnummer, five digits without space.
- `localities` (array of object): Postorter and kommuner the postnummer is used in, most addresses first. Usually one.
- `locality` (string | null): Postort; null for the addresses with this postnummer that have none.
- `municipality_code` (string): Kommunkod, four digits.
- `municipality` (string): Kommun name.
- `county_code` (string | null): Länskod, two digits as the register delivers it (e.g. '03' for Uppsala län); null when the address has none.
- `county` (string | null): Län name as SCB writes it, e.g. 'Uppsala län'; null when county_code is null or is not one of Sweden's 21 län codes.
- `addresses` (integer): Number of current addresses with this postnummer in this postort and kommun.
- `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 ' (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.
- [`not_found`](https://api.kartpunkt.se/errors/not_found) (404): Nothing matches. Check the value; postnummer used only for PO boxes (box addresses) are not in the address register.
- [`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.
## Limits
none
## Metering
With a `kp_live_` key, one live call per request; its postorter are counted as results but toward no address limit.
---
# POST /v1/snap
> Index: https://kartpunkt.se/llms.txt
operationId: `snapPoints`. Meet points to the road network.
Tag: `roads`.
Where each point meets the profile's network: the nearest point of a road within radius_m, with snap_m, the road's name and number, and its road reference there. Use it to see where a point meets the network before you route from it; for the roads around a point and their facts, use POST /v1/roads/nearby. It costs a request and no calculation.
## Request
`POST https://api.kartpunkt.se/v1/snap` 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
{
"points": [
{
"lat": 63.72059,
"lon": 19.342166,
"street": "Skolvägen"
},
{
"lat": 63.720632,
"lon": 19.345215
},
{
"lat": 63.724508,
"lon": 19.339523,
"street": "Skolvägen"
},
{
"lat": 56,
"lon": 19.5
}
],
"profile": "car_shortest"
}
```
JSON body:
- `points` (array of [StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), 1..100 items, required): The points to meet to the network, answered in results in the same order.
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), optional, default "car_shortest")
- `radius_m` (number <= 2000, optional, default 500): The straight-line search bound round each point, in metres.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"results": [
{
"point": {
"lat": 63.7207239,
"lon": 19.3421866
},
"snap_m": 14.97,
"street_matched": true,
"ref": "0:17733@0.016646-0.016646",
"road": {
"name": "Skolvägen",
"number": null
}
},
{
"point": {
"lat": 63.7206325,
"lon": 19.3452151
},
"snap_m": 0.05,
"street_matched": null,
"ref": "0:17733@0.266710-0.266710",
"road": {
"name": "Skolvägen",
"number": null
}
},
{
"point": {
"lat": 63.7208214,
"lon": 19.3389572
},
"snap_m": 411.98,
"street_matched": true,
"ref": "0:17732@0.499991-0.499991",
"road": {
"name": "Skolvägen",
"number": null
}
},
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": "car_shortest",
"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"
}
]
}
}
```
- `results` (array of object | null): One per point sent, in order; null where no road of the profile's network lies within radius_m.
- `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md)): Where the point meets the network: the nearest point of the road.
- `snap_m` (number): Straight-line metres from the point sent to where it meets the network; never part of a distance_m.
- `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.
- `ref` ([RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md) | null): The road's reference at that point, its two measures equal; null on a road with no NVDB link.
- `road` (object): The road the point meets.
- `name` (string | null): The road's name in NVDB; null where it has none.
- `number` (string | null): The road's number (vägnummer): 'E6' for a europaväg, else digits; null where it has none.
- `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 ' (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 |
| --- | --- |
| `points` | 100 |
| `radius_m` (at most) | 2000 |
| Request body | 1 MB |
## Metering
A request only: no calculations, no live call, no address limit.
---
# POST /v1/roads/nearby
> Index: https://kartpunkt.se/llms.txt
operationId: `listRoadsNearby`. Roads near a point, with facts.
Tag: `roads`.
The nearest stretches of drivable road to a point within radius_m, nearest first, each with its road reference, distance_m and the facts asked for. Use it to say which road a point lies on and what NVDB records there; to meet a point to a profile's network, use POST /v1/snap. It costs a request and no calculation.
## Request
`POST https://api.kartpunkt.se/v1/roads/nearby` 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
{
"point": {
"lat": 63.720632,
"lon": 19.345215
},
"radius_m": 200,
"limit": 3,
"facts": [
"speed_limit",
"surface",
"road_owner",
"road_class"
]
}
```
JSON body:
- `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md), required)
- `radius_m` (number <= 500, optional, default 50): The straight-line search bound round the point, in metres.
- `limit` (integer 1..20, optional, default 5): The most roads to answer.
- `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 on each road: see GET /v1/facts. None by default.
- `geometry` ("geojson" | "polyline6" | "none", optional, default "geojson"): How lines are returned: GeoJSON, an encoded polyline at 6 decimals, or not at all.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"roads": [
{
"ref": "0:17733@0.000000-1.000000",
"distance_m": 0.05,
"name": "Skolvägen",
"number": null,
"length_m": 600,
"facts": {
"speed_limit": "40",
"surface": "paved",
"road_owner": "municipal",
"road_class": "7"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.341985,
63.72073
],
[
19.354096,
63.720364
]
]
}
},
{
"ref": "0:17742@0.000000-1.000000",
"distance_m": 128.03,
"name": "Torggatan",
"number": null,
"length_m": 500,
"facts": {
"speed_limit": "40",
"surface": "paved",
"road_owner": "municipal",
"road_class": "7"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.341985,
63.72073
],
[
19.348591,
63.724127
]
]
}
},
{
"ref": "0:17732@0.000000-1.000000",
"distance_m": 160,
"name": "Skolvägen",
"number": null,
"length_m": 300,
"facts": {
"speed_limit": "40",
"surface": "paved",
"road_owner": "municipal",
"road_class": "7"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.335929,
63.720913
],
[
19.341985,
63.72073
]
]
}
}
],
"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"
},
"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"
}
]
}
}
```
- `roads` (array of object): The roads found, nearest first; empty when none lies within radius_m.
- `ref` ([RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md) | null): The stretch's road reference; null on a road with no NVDB link.
- `distance_m` (number): Straight-line metres, spheroidal, from the point sent to the nearest point of the road.
- `name` (string | null): The road's name in NVDB; null where it has none.
- `number` (string | null): The road's number (vägnummer): 'E6' for a europaväg, else digits; null where it has none.
- `length_m` (number): Metres of the stretch.
- `facts` (object): Each fact asked for, by key, with its value on this stretch.
- `geometry` (object | string, optional): The stretch's line, in the geometry format asked; left out with geometry 'none'.
- `type` ("LineString")
- `coordinates` (array of array of number)
- `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 ' (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 |
| --- | --- |
| `radius_m` (at most) | 500 |
| `limit` (at most) | 20 |
| `facts` (items) | 34 |
| Request body | 1 MB |
## Metering
A request only: no calculations, no live call, no address limit.
---
# GET /v1/roads/{ref}
> Index: https://kartpunkt.se/llms.txt
operationId: `getRoad`. A road by its reference.
Tag: `roads`.
The stretches of the served network that a road reference covers, with the facts asked for. Use it to read back a ref stored from any answer; for the roads around a point, use POST /v1/roads/nearby. It costs a request and no calculation.
## Request
`GET https://api.kartpunkt.se/v1/roads/{ref}` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
Parameters:
- `ref` (path, [RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md), required): A stretch of road: an NVDB link sequence id and the part of it covered, as measures 0 to 1 in the direction travelled. It names the same stretch in every network snapshot that holds the link (ADR-0013).
- `facts` (query, 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 on each stretch, one query parameter per key: ?facts=speed_limit&facts=surface. See GET /v1/facts. None by default.
- `geometry` (query, "geojson" | "polyline6" | "none", optional, default "geojson"): How lines are returned: GeoJSON, an encoded polyline at 6 decimals, or not at all.
## Response 200
Example:
```json
{
"roads": [
{
"ref": "0:17733@0.000000-1.000000",
"name": "Skolvägen",
"number": null,
"length_m": 600,
"facts": {
"speed_limit": "40",
"surface": "paved"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.341985,
63.72073
],
[
19.354096,
63.720364
]
]
}
}
],
"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"
},
"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"
}
]
}
}
```
- `roads` (array of object): The stretches of the served network that cover the ref, in measure order. Empty when that snapshot holds no road on the ref's link.
- `ref` ([RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md) | null): The stretch's road reference; null on a road with no NVDB link.
- `name` (string | null): The road's name in NVDB; null where it has none.
- `number` (string | null): The road's number (vägnummer): 'E6' for a europaväg, else digits; null where it has none.
- `length_m` (number): Metres of the stretch.
- `facts` (object): Each fact asked for, by key, with its value on this stretch.
- `geometry` (object | string, optional): The stretch's line, in the geometry format asked; left out with geometry 'none'.
- `type` ("LineString")
- `coordinates` (array of array of number)
- `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 ' (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
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# POST /v1/route
> Index: https://kartpunkt.se/llms.txt
operationId: `planRoute`. A route, with facts along it.
Tag: `routing`.
The route between two points, through any via points, on a profile: its distance, free-flow duration, segments with their road references, and the facts asked for summed over it. Its status says whether there is a route: a 200 with another status is an answer, not an error. For many origins to a few destinations use POST /v1/distances; for a GPS trace already travelled, POST /v1/trace.
## Request
`POST https://api.kartpunkt.se/v1/route` 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
{
"origin": {
"lat": 63.724247,
"lon": 19.347595
},
"destination": {
"lat": 63.72059,
"lon": 19.342166
},
"profile": "walk",
"facts": [
"speed_limit",
"road_class",
"surface",
"fast_road"
]
}
```
JSON body:
- `origin` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required)
- `destination` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required)
- `via` (array of [StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), 0..10 items, optional): Points the route passes through, in the order sent.
- `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 (what the main path is measured against, e.g. a closure's detour).
- `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 "geojson"): How lines are returned: GeoJSON, an encoded polyline at 6 decimals, or not at all.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"status": "ok",
"distance_m": 560,
"duration_s": 403,
"facts": {
"speed_limit": {
"40": 560
},
"road_class": {
"7": 560
},
"surface": {
"paved": 560
},
"fast_road": 0
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.347581,
63.724157
],
[
19.348591,
63.724127
],
[
19.341985,
63.72073
],
[
19.342187,
63.720724
]
]
},
"avoid_m": null,
"snap_m": 10.05,
"street_matched": null,
"destination_snap_m": 14.97,
"destination_street_matched": null,
"blocked_by": null,
"via": null,
"segments": [
{
"length_m": 50,
"ref": "0:17736@0.833269-1.000000",
"facts": {
"speed_limit": "40",
"road_class": "7",
"surface": "paved",
"fast_road": false
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.347581,
63.724157
],
[
19.348591,
63.724127
]
]
}
},
{
"length_m": 500,
"ref": "0:17742@1.000000-0.000000",
"facts": {
"speed_limit": "40",
"road_class": "7",
"surface": "paved",
"fast_road": false
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.348591,
63.724127
],
[
19.341985,
63.72073
]
]
}
},
{
"length_m": 10,
"ref": "0:17733@0.000000-0.016646",
"facts": {
"speed_limit": "40",
"road_class": "7",
"surface": "paved",
"fast_road": false
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.341985,
63.72073
],
[
19.342187,
63.720724
]
]
}
}
],
"avoid": 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"
}
]
}
}
```
- `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.
- `via` (array of [SnapResult](https://api.kartpunkt.se/docs/shapes/SnapResult.md) | null): Where each via point met the network, in order; null when none was sent.
- `segments` (array of [Segment](https://api.kartpunkt.se/docs/shapes/Segment.md))
- `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.
- `alternatives` (object | null): The alternatives asked for, by label: unrestricted, the route without avoid with its own segments. null when none was.
- `unrestricted` (object, optional)
- `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.
- `segments` (array of [Segment](https://api.kartpunkt.se/docs/shapes/Segment.md))
- `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 ' (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: "route"`) |
| --- | --- | --- |
| `via` | 10 | 10 |
| 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 call.
---
# 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 ' (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.
---
# POST /v1/nearest
> Index: https://kartpunkt.se/llms.txt
operationId: `findNearest`. What is nearest each point along the network.
Tag: `routing`.
For each point, the nearest places of a kind, bus stops today, by the profile's distance along the network, each with its metres and free-flow seconds. Use it instead of POST /v1/distances when you do not know the destinations: it finds them within max_distance_m. It costs a calculation per point.
## Request
`POST https://api.kartpunkt.se/v1/nearest` 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
{
"kind": "bus_stop",
"points": [
{
"id": "unrefuged",
"point": {
"lat": 63.733246,
"lon": 19.344314,
"street": "Passvägen"
}
},
{
"id": "skolvagen",
"point": {
"lat": 63.720434,
"lon": 19.348226,
"street": "Skolvägen"
}
},
{
"id": "river-bank",
"point": {
"lat": 63.716325,
"lon": 19.35388,
"street": "Åvägen"
}
}
],
"profile": "walk",
"limit": 2
}
```
JSON body:
- `kind` ("bus_stop", required): What to find: 'bus_stop', NVDB's bus stops (hållplatser) of the network snapshot.
- `points` (array of object, 1..1000 items, required)
- `id` (string, max 64 chars, optional): Your own id for the point, echoed back.
- `point` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required)
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), optional, default "walk"): The profile whose network and distance measure what is nearest (GET /v1/profiles); walk unless given.
- `limit` (integer 1..5, optional, default 3): How many per point, nearest by the profile's distance first (1-5, default 3).
- `max_distance_m` (number 1..5000, optional, default 2000): Only what is within this many metres along the network by the profile, not in a straight line (1-5000, default 2000).
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"results": [
{
"id": "unrefuged",
"origin": {
"snap_m": 20.02,
"street_matched": true
},
"nearest": [
{
"kind": "bus_stop",
"id": "synr-stop-2",
"name": "Norrgatan",
"point": {
"lat": 63.7245493,
"lon": 19.3405435
},
"distance_m": 1100,
"duration_s": 792
},
{
"kind": "bus_stop",
"id": "synr-stop-1",
"name": "Ruttby skola",
"point": {
"lat": 63.7205328,
"lon": 19.342563
},
"distance_m": 1430,
"duration_s": 1030
}
]
},
{
"id": "skolvagen",
"origin": {
"snap_m": 11.96,
"street_matched": true
},
"nearest": [
{
"kind": "bus_stop",
"id": "synr-stop-1",
"name": "Ruttby skola",
"point": {
"lat": 63.7205328,
"lon": 19.342563
},
"distance_m": 280,
"duration_s": 202
},
{
"kind": "bus_stop",
"id": "synr-stop-2",
"name": "Norrgatan",
"point": {
"lat": 63.7245493,
"lon": 19.3405435
},
"distance_m": 810,
"duration_s": 583
}
]
},
{
"id": "river-bank",
"origin": {
"snap_m": 20.03,
"street_matched": true
},
"nearest": [
{
"kind": "bus_stop",
"id": "synr-stop-3",
"name": "Åbacken",
"point": {
"lat": 63.710061,
"lon": 19.3529152
},
"distance_m": 700,
"duration_s": 504
},
{
"kind": "bus_stop",
"id": "synr-stop-1",
"name": "Ruttby skola",
"point": {
"lat": 63.7205328,
"lon": 19.342563
},
"distance_m": 1020,
"duration_s": 734
}
]
}
],
"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"
}
]
}
}
```
- `results` (array of object)
- `id` (string | null)
- `origin` ([SnapResult](https://api.kartpunkt.se/docs/shapes/SnapResult.md)): Where the point met the profile's network; snap_m null when it met none, and nearest is then [].
- `nearest` (array of object): Nearest first; [] when none is within max_distance_m or the point met no road.
- `kind` ("bus_stop"): What to find: 'bus_stop', NVDB's bus stops (hållplatser) of the network snapshot.
- `id` (string): Its id in the network snapshot.
- `name` (string | null)
- `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md)): Its position.
- `distance_m` (number): Metres along the network by the profile, between where the point and it meet the network.
- `duration_s` (number | null): Free-flow seconds of that route by the profile.
- `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 ' (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: "nearest"`) |
| --- | --- | --- |
| `points` | 1000 | 50000 |
| `limit` (at most) | 5 | 5 |
| `max_distance_m` (at most) | 5000 | 5000 |
| 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 point.
---
# POST /v1/matrix
> Index: https://kartpunkt.se/llms.txt
operationId: `buildMatrix`. An origins × destinations matrix.
Tag: `routing`.
Metres and free-flow seconds along the network from every origin to every destination, as grids with a status per cell; with avoid or prefer each cell is the path POST /v1/distances answers for it. Use it for a table of travel times between many places; for each path's facts or line, use POST /v1/distances. It costs at most a calculation per two cells, rounded up; Metering has the formula.
## Request
`POST https://api.kartpunkt.se/v1/matrix` 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
{
"origins": [
{
"lat": 63.72059,
"lon": 19.342166
},
{
"lat": 63.720434,
"lon": 19.348226
},
{
"lat": 63.675987,
"lon": 19.335126
}
],
"destinations": [
{
"lat": 63.724508,
"lon": 19.339523
},
{
"lat": 63.718529,
"lon": 19.418702
},
{
"lat": 63.725224,
"lon": 19.282034
}
],
"profile": "car_fastest"
}
```
JSON body:
- `origins` (array of [StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), 1..100 items, required): The points to measure from: the matrix's rows.
- `destinations` (array of [StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), 1..100 items, required): The points to measure to: the matrix's columns.
- `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.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"distances_m": [
[
560,
3790,
null
],
[
860,
3490,
null
],
[
null,
null,
null
]
],
"durations_s": [
[
50,
169,
null
],
[
77,
142,
null
],
[
null,
null,
null
]
],
"statuses": [
[
"ok",
"ok",
"no_path"
],
[
"ok",
"ok",
"no_path"
],
[
"no_road_near",
"no_road_near",
"no_road_near"
]
],
"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": "car_fastest",
"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"
}
]
}
}
```
- `distances_m` (array of array of number | null): [origin][destination]: metres along the network, null where no path joins them.
- `durations_s` (array of array of number | null): [origin][destination]: seconds at free flow, null where no path joins them.
- `statuses` (array of array of "ok" | "no_path" | "ferry_required" | "no_road_near" | "avoided_road_required"): [origin][destination]: each cell's status, as a path's.
- `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 ' (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: "matrix"`) |
| --- | --- | --- |
| Cells (origins × destinations) | 625 | 10000 |
| Cells with an `avoid` entry in `mode: "barrier"` | 20 | 10000 |
| Origins with `vehicle`, `prefer` or `avoid` (a search per origin) | 8 | within the cells (a job runs in chunks) |
| Ways of the network `avoid` keeps off | 20000 | 20000 |
| `origins` (items) | 100 | 100 |
| `destinations` (items) | 100 | 100 |
| `prefer` (items) | 6 | 6 |
| 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: max(1, min(ceil(origins × destinations / 2), the longer list × 10)), so 100 × 100 is 1000.
---
# POST /v1/reach
> Index: https://kartpunkt.se/llms.txt
operationId: `measureReach`. What can be reached within a budget.
Tag: `routing`.
The area reachable from a point within each budget, seconds of travel or metres along the network, as one polygon per budget. Use it to show what lies within reach; to measure to places you know, use POST /v1/distances. It costs a calculation per budget value.
## Request
`POST https://api.kartpunkt.se/v1/reach` 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
{
"origin": {
"lat": 63.72059,
"lon": 19.342166
},
"profile": "walk",
"budget": {
"duration_s": [
120,
300
]
}
}
```
JSON body:
- `origin` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required)
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), required)
- `budget` (object, required): How far to reach: duration_s or distance_m, one list or the other.
- Either `{ duration_s }`:
- `duration_s` (array of number <= 7200, 1..4 items, required): Seconds of travel, one area per value: free flow by car or hgv, the profile's own pace on foot or by bike. Refused on car_shortest, which takes metres.
- Or `{ distance_m }`:
- `distance_m` (array of number <= 100000, 1..4 items, required): Metres along the network, one area per value, on any profile.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"areas": [
{
"duration_s": 120,
"distance_m": null,
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
19.338783582223183,
63.71902855423934
],
[
19.347314732223182,
63.715174854239336
],
[
19.348141132223184,
63.72054395423933
],
[
19.345388732223185,
63.722425204239336
],
[
19.342360632223183,
63.72251670423934
],
[
19.34225985,
63.722519750000004
],
[
19.33923175,
63.72261105
],
[
19.33895725,
63.7208213
],
[
19.33882685,
63.7199712
],
[
19.33881315,
63.7198817
],
[
19.3386828,
63.7190316
],
[
19.338783582223183,
63.71902855423934
]
]
]
}
},
{
"duration_s": 300,
"distance_m": null,
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
19.3331758,
63.72279355
],
[
19.3329017,
63.7210038
],
[
19.3357991,
63.7200625
],
[
19.33882685,
63.7199712
],
[
19.33881315,
63.7198817
],
[
19.3386828,
63.7190316
],
[
19.338783582223183,
63.71902855423934
],
[
19.347314732223182,
63.715174854239336
],
[
19.348141132223184,
63.72054395423933
],
[
19.345388732223185,
63.722425204239336
],
[
19.345562800000003,
63.72421799999999
],
[
19.342940300000002,
63.7269493
],
[
19.34143285,
63.72703975
],
[
19.3395066,
63.7244008
],
[
19.33923175,
63.72261105
],
[
19.336204,
63.72270235
],
[
19.3331758,
63.72279355
]
]
]
}
}
],
"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"
}
]
}
}
```
- `areas` (array of object)
- `duration_s` (number | null): This area's budget in seconds when the budget was duration_s; else null.
- `distance_m` (number | null): This area's budget in metres when the budget was distance_m; else null.
- `geometry` (object)
- `type` ("Polygon")
- `coordinates` (array of array of array of number)
- `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 ' (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: "reach"`) |
| --- | --- | --- |
| Budget values (`budget.duration_s` or `budget.distance_m`) | 4 | 4 |
| `budget.duration_s[]` (each at most) | 7200 | 7200 |
| `budget.distance_m[]` (each at most) | 100000 | 100000 |
| 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 budget value (one per area drawn).
---
# POST /v1/trace
> Index: https://kartpunkt.se/llms.txt
operationId: `matchTrace`. The roads a GPS trace travelled, with facts.
Tag: `routing`.
A GPS trace or a drawn line matched to the road network: the stretches travelled, with their road references and the facts asked for, summed. Use it for a path already travelled; for the path to travel, use POST /v1/route. It costs one calculation.
## Request
`POST https://api.kartpunkt.se/v1/trace` 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
{
"points": [
{
"lat": 63.71824,
"lon": 19.423726,
"time": "2026-10-13T07:00:00.000Z"
},
{
"lat": 63.718058,
"lon": 19.429781,
"time": "2026-10-13T07:01:00.000Z"
},
{
"lat": 63.717876,
"lon": 19.435836,
"time": "2026-10-13T07:02:00.000Z"
},
{
"lat": 63.717804,
"lon": 19.437852,
"time": "2026-10-13T07:03:00.000Z"
}
],
"profile": "car_shortest",
"facts": [
"speed_limit",
"road_number",
"traffic_safety_class"
]
}
```
JSON body:
- `points` (array of [TracePoint](https://api.kartpunkt.se/docs/shapes/TracePoint.md), 2..2000 items, required)
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), optional, default "car_shortest")
- `gps_accuracy_m` (number <= 200, optional, default 20): How far, in metres, a fix may lie from the road it was recorded on.
- `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 "geojson"): How lines are returned: GeoJSON, an encoded polyline at 6 decimals, or not at all.
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"status": "ok",
"distance_m": 700,
"segments": [
{
"length_m": 250,
"ref": "0:17778@0.166714-1.000000",
"facts": {
"speed_limit": "80",
"road_number": "364",
"traffic_safety_class": "god"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.423726,
63.718239
],
[
19.428771,
63.718084
]
]
}
},
{
"length_m": 300,
"ref": "0:17779@0.000000-1.000000",
"facts": {
"speed_limit": "60",
"road_number": "364",
"traffic_safety_class": "mindre god"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.428771,
63.718084
],
[
19.434825,
63.717898
]
]
}
},
{
"length_m": 150,
"ref": "0:17780@0.000000-0.749966",
"facts": {
"speed_limit": "60",
"road_number": "364",
"traffic_safety_class": "mindre god"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.434825,
63.717898
],
[
19.437852,
63.717804
]
]
}
}
],
"facts": {
"speed_limit": {
"60": 450,
"80": 250
},
"road_number": {
"364": 700
},
"traffic_safety_class": {
"god": 250,
"mindre god": 450
}
},
"geometry": {
"type": "LineString",
"coordinates": [
[
19.423726,
63.718239
],
[
19.428771,
63.718084
],
[
19.434825,
63.717898
],
[
19.437852,
63.717804
]
]
},
"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": "car_shortest",
"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"
}
]
}
}
```
- `status` ("ok" | "not_matched"): 'ok' when the trace was matched to the network; 'not_matched' when no path along it was found.
- `distance_m` (number | null): Metres along the network of the path matched; null when not matched.
- `segments` (array of [Segment](https://api.kartpunkt.se/docs/shapes/Segment.md))
- `facts` (object): The facts asked for, over the path matched.
- `geometry` (object | string | null, optional): The whole matched path as one line, in the format asked; null when not matched. Left out with geometry 'none'.
- `type` ("LineString")
- `coordinates` (array of array of number)
- `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 ' (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: "trace"`) |
| --- | --- | --- |
| `points` | 2000 | 2000 |
| `gps_accuracy_m` (at most) | 200 | 200 |
| `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 call.
---
# POST /v1/optimize
> Index: https://kartpunkt.se/llms.txt
operationId: `optimizeRoutes`. Plan vehicles' routes over stops and shipments.
Tag: `planning`.
Which vehicle visits which stops and shipments, in what order and at what time, each leg timed on the network as POST /v1/route answers it. A stop or shipment no vehicle can take is answered in unassigned with a reason, never refused. To time a plan you already have, use POST /v1/optimize/check; above the synchronous limits, send the same input as a job.
## Request
`POST https://api.kartpunkt.se/v1/optimize` 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
{
"vehicles": [
{
"id": "bus1",
"start": {
"lat": 63.720434,
"lon": 19.348226,
"street": "Skolvägen"
},
"end": {
"lat": 63.72059,
"lon": 19.342166,
"street": "Skolvägen"
},
"capacity": {
"seats": 20
},
"time_window": {
"start": "2026-10-13T07:00:00.000Z",
"end": "2026-10-13T07:01:00.000Z"
}
}
],
"stops": [
{
"id": "s1",
"at": {
"lat": 63.724508,
"lon": 19.339523,
"street": "Norrgatan"
},
"pickup": {
"seats": 4
},
"service_s": 30
},
{
"id": "s2",
"at": {
"lat": 63.718529,
"lon": 19.418702,
"street": "Östbyvägen"
},
"pickup": {
"seats": 2
},
"service_s": 30
},
{
"id": "s3",
"at": {
"lat": 63.725224,
"lon": 19.282034,
"street": "Skogsbyvägen"
},
"pickup": {
"seats": 1
}
}
],
"profile": "car_fastest"
}
```
JSON body:
- `vehicles` (array of object, 1..5 items, required)
- `id` (string, max 64 chars, required): Your own id for the vehicle, echoed in the answer and never stored. Unique among the vehicles, stops and shipments of one request.
- `start` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required): Where the vehicle starts: a depot, a garage.
- `end` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), optional): Where it must end; left out, its route ends at its last task.
- `capacity` (object, optional): What the vehicle carries at most, per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `dimensions` ([Vehicle](https://api.kartpunkt.se/docs/shapes/Vehicle.md), optional): The vehicle's weight, axle load, height, width, length and hazmat, on a profile whose takes_vehicle is true in GET /v1/profiles (hgv). A road whose recorded limit is below it is not used for this vehicle. The plan's matrix is computed once per distinct dimensions sent.
- `time_window` (object, optional): When the vehicle is available: it leaves start no earlier and reaches end no later. Left out, any time: its visits' arrival and departure are null, and only the _s fields time them.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `max_travel_s` (integer <= 86400, optional): At most this many seconds driving, from start to end; waiting and service not counted.
- `skills` (array of string, max 64 chars, 0..20 items, optional): Your own strings: what this vehicle can do.
- `stops` (array of object, 0..25 items, optional, default [])
- `id` (string, max 64 chars, required): Your own id for the stop, echoed in the answer and never stored. Unique among the vehicles, stops and shipments of one request.
- `at` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md) | [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md), required): Where: { lat, lon, street? } as every point, or { address_id, as_of? } to route from the address's own point. Each address id resolved is metered as an address read and the call as a live call (kp_live_ keys); a stop whose id resolves to no address served is answered unassigned_address_id, not refused.
- `pickup` (object, optional): Loaded at this stop and carried to the vehicle's end (passengers boarding, bins emptied into the truck), per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `delivery` (object, optional): Loaded at the vehicle's start and set down here (meals, parcels from the depot), per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `service_s` (integer 0..86400, optional, default 0): Seconds spent at the place, after arrival and any wait.
- `time_windows` (array of object, 1..4 items, optional): When service may start here: one or more windows. Left out, any time within the vehicle's.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `skills` (array of string, max 64 chars, 0..20 items, optional): Your own strings, e.g. 'wheelchair_lift', 'crane': a task goes only to a vehicle with every skill it names.
- `shipments` (array of object, 0..12 items, optional, default [])
- `id` (string, max 64 chars, required): Your own id for the shipment, echoed in the answer and never stored. Unique among the vehicles, stops and shipments of one request.
- `pickup` (object, required): Where the amount is picked up.
- `at` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md) | [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md), required): Where: { lat, lon, street? } as every point, or { address_id, as_of? } to route from the address's own point. Each address id resolved is metered as an address read and the call as a live call (kp_live_ keys); a stop whose id resolves to no address served is answered unassigned_address_id, not refused.
- `service_s` (integer 0..86400, optional, default 0): Seconds spent at the place, after arrival and any wait.
- `time_windows` (array of object, 1..4 items, optional): When service may start here: one or more windows. Left out, any time within the vehicle's.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `delivery` (object, required): Where it is set down: by the same vehicle, after the pickup.
- `at` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md) | [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md), required): Where: { lat, lon, street? } as every point, or { address_id, as_of? } to route from the address's own point. Each address id resolved is metered as an address read and the call as a live call (kp_live_ keys); a stop whose id resolves to no address served is answered unassigned_address_id, not refused.
- `service_s` (integer 0..86400, optional, default 0): Seconds spent at the place, after arrival and any wait.
- `time_windows` (array of object, 1..4 items, optional): When service may start here: one or more windows. Left out, any time within the vehicle's.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `amount` (object, optional): What travels from pickup to delivery, per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `max_ride_s` (integer <= 86400, optional): At most this many seconds from leaving the pickup to arriving at the delivery. Checked on the plan made without it: a shipment over it is taken out and answered unassigned_max_ride, never re-planned.
- `skills` (array of string, max 64 chars, 0..20 items, optional): Your own strings, e.g. 'wheelchair_lift', 'crane': a task goes only to a vehicle with every skill it names.
- `objective` ("duration" | "vehicles_then_duration", optional, default "duration"): What is made least, after serving as many stops and shipments as the vehicles can: 'duration', the vehicles' driving time together; 'vehicles_then_duration', the vehicles used first, then their driving time.
- `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.
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), required): How the legs are weighed: see GET /v1/profiles. A plan's durations are each leg's as /v1/route answers it.
- `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 over each leg: see GET /v1/facts. With facts or geometry, the answer has legs.
- `geometry` ("geojson" | "polyline6" | "none", optional, default "none"): Each leg's line and its stretches with road references: GeoJSON, an encoded polyline at 6 decimals, or not at all (the default).
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
## Response 200
Example:
```json
{
"routes": [
{
"vehicle_id": "bus1",
"visits": [
{
"kind": "start",
"id": null,
"snap_m": 11.96,
"street_matched": true,
"arrival": "2026-10-13T07:00:00.000Z",
"departure": "2026-10-13T07:01:00.000Z",
"arrival_s": 0,
"waiting_s": 0,
"service_s": 0,
"load": {
"seats": 0
},
"distance_m": 0,
"duration_s": 0
},
{
"kind": "stop",
"id": "s2",
"snap_m": 15.02,
"street_matched": true,
"arrival": "2026-10-13T07:02:00.000Z",
"departure": "2026-10-13T07:03:00.000Z",
"arrival_s": 142,
"waiting_s": 0,
"service_s": 30,
"load": {
"seats": 2
},
"distance_m": 3490,
"duration_s": 142.3
},
{
"kind": "stop",
"id": "s1",
"snap_m": 11.98,
"street_matched": true,
"arrival": "2026-10-13T07:04:00.000Z",
"departure": "2026-10-13T07:05:00.000Z",
"arrival_s": 392,
"waiting_s": 0,
"service_s": 30,
"load": {
"seats": 6
},
"distance_m": 4350,
"duration_s": 219.7
},
{
"kind": "end",
"id": null,
"snap_m": 14.97,
"street_matched": true,
"arrival": "2026-10-13T07:06:00.000Z",
"departure": "2026-10-13T07:07:00.000Z",
"arrival_s": 472,
"waiting_s": 0,
"service_s": 0,
"load": {
"seats": 6
},
"distance_m": 560,
"duration_s": 50.4
}
],
"legs": null,
"distance_m": 8400,
"duration_s": 472,
"waiting_s": 0,
"service_s": 60
}
],
"unassigned": [
{
"kind": "stop",
"id": "s3",
"reason": "unassigned_unreachable",
"message": "This place was met on the road in unreachable.met, but the network joins no path from a vehicle's start to it (in a checked plan, from the visit before or to the visit after it), or only a ferry does: the road lies on an island of the profile's network. Send a point on a road joined to the rest, or a vehicle that starts where it can reach it.",
"blocked_by": null,
"unreachable": {
"place": "stop",
"met": {
"point": {
"lat": 63.72523,
"lon": 19.2818325
},
"snap_m": 9.98,
"street_matched": true,
"ref": "0:17763@0.500043-0.500043",
"road": {
"name": "Skogsbyvägen",
"number": null
}
},
"cause": "no_path"
}
}
],
"totals": {
"vehicles_used": 1,
"distance_m": 8400,
"duration_s": 472,
"waiting_s": 0,
"service_s": 60
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01k7exampleexampleexample0",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:08:00.000Z",
"graph": "snap_01k7exampleexampleexample0",
"profile": "car_fastest",
"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"
}
]
}
}
```
- `routes` (array of object): One per vehicle used, in the order of vehicles sent; a vehicle given no task is left out.
- `vehicle_id` (string): The vehicle's id, as sent.
- `visits` (array of object): In the order driven: start, each task, end (where one was sent).
- `kind` ("start" | "stop" | "pickup" | "delivery" | "end"): 'start' and 'end' are the vehicle's own; 'stop' a stop; 'pickup' and 'delivery' a shipment's two places.
- `id` (string | null): The stop's or shipment's id; null at the vehicle's start and end.
- `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): Whether the place met the road of the street sent; null without a street, and at a place sent as an address id (the answer holds nothing of the address).
- `arrival` (string | null): RFC 3339, in the offset of the vehicle's time_window; null without one.
- `departure` (string | null): RFC 3339: arrival, any wait and service_s later; null without a time_window.
- `arrival_s` (number): Seconds from leaving start to arriving here.
- `waiting_s` (number): Seconds waited here for a time window to open.
- `service_s` (number): Seconds of service here, as sent.
- `load` (object): On board when leaving here, per dimension named in the request.
- `distance_m` (number): Metres along the network from the previous visit (0 at start).
- `duration_s` (number): Seconds driving from the previous visit at free flow (0 at start).
- `legs` (array of object | null): With facts or geometry, one leg from each visit to the next, in order: visits.length − 1 of them. null without either.
- `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.
- `segments` (array of [Segment](https://api.kartpunkt.se/docs/shapes/Segment.md)): The leg's stretches with their road references (ADR-0013), in order; with geometry, each with its line.
- `distance_m` (number): Metres along the network over the whole route.
- `duration_s` (number): Seconds from leaving start to the last visit: driving, waiting and service.
- `waiting_s` (number): Seconds waited for time windows to open, over the route.
- `service_s` (number): Seconds of service, over the route.
- `unassigned` (array of object): Every stop and shipment no vehicle takes, with why. [] when every one is in a route.
- `kind` ("stop" | "shipment"): Whether id names a stop or a shipment.
- `id` (string): The stop's or shipment's id, as sent.
- `reason` ("unassigned_address_id" | "unassigned_unreachable" | "unassigned_skills" | "unassigned_capacity" | "unassigned_time_window" | "unassigned_max_travel" | "unassigned_max_ride" | "unassigned_no_room"): Why, as a code from ERRORS: its page at /errors/{code} says what to change.
- `message` (string): The code's message from ERRORS, saying what to change; with unassigned_unreachable, the message for its cause.
- `blocked_by` (array of object | null): With unassigned_unreachable where only roads kept off by avoid reach the task: what in avoid is in the way, as route's blocked_by. null otherwise.
- `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.
- `unreachable` ([Unreachable](https://api.kartpunkt.se/docs/shapes/Unreachable.md), optional): With unassigned_unreachable: which place, where it met the network and why no vehicle reaches it. Absent with every other reason. met is null for a place sent as an address id: the answer holds no point of the address.
- `unresolved` ([Unresolved](https://api.kartpunkt.se/docs/shapes/Unresolved.md), optional): With unassigned_address_id: which place's address id resolved to no address served, and why. Absent with every other reason.
- `totals` (object): The routes summed.
- `vehicles_used` (integer): Vehicles with at least one task.
- `distance_m` (number): Metres, every route's together.
- `duration_s` (number): Seconds, every route's duration_s together.
- `waiting_s` (number): Seconds waited, every route's together.
- `service_s` (number): Seconds of service, every route's together.
- `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 ' (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: "optimize"`) |
| --- | --- | --- |
| `stops` | 25 | 500 |
| `shipments` | 12 | 250 |
| `vehicles` | 5 | 50 |
| Distinct locations (stops, shipments' pickups and deliveries, vehicles' starts and ends) | 30 | 600 |
| An `avoid` entry with `mode: "barrier"` | refused 413 `too_large` | allowed |
| Ways of the network `avoid` keeps off | 20000 | 20000 |
| `prefer` (items) | 6 | 6 |
| `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 stop and per shipment sent.
---
# POST /v1/optimize/check
> Index: https://kartpunkt.se/llms.txt
operationId: `checkPlan`. Check a given plan of vehicles' routes.
Tag: `planning`.
Your own routes, timed and loaded on the network as POST /v1/optimize times its own and in its shapes, so the two compare field for field. What a route breaks is answered in violations and a task in no route in unassigned: a broken plan is answered, never refused. To have the plan made for you, use POST /v1/optimize; above the synchronous limits, send the same input as a job.
## Request
`POST https://api.kartpunkt.se/v1/optimize/check` 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
{
"vehicles": [
{
"id": "bus1",
"start": {
"lat": 63.720434,
"lon": 19.348226,
"street": "Skolvägen"
},
"end": {
"lat": 63.72059,
"lon": 19.342166,
"street": "Skolvägen"
},
"capacity": {
"seats": 20
},
"time_window": {
"start": "2026-10-13T07:00:00.000Z",
"end": "2026-10-13T07:01:00.000Z"
}
}
],
"stops": [
{
"id": "s1",
"at": {
"lat": 63.724508,
"lon": 19.339523,
"street": "Norrgatan"
},
"pickup": {
"seats": 4
},
"service_s": 30
},
{
"id": "s2",
"at": {
"lat": 63.718529,
"lon": 19.418702,
"street": "Östbyvägen"
},
"pickup": {
"seats": 2
},
"service_s": 30
},
{
"id": "s3",
"at": {
"lat": 63.725224,
"lon": 19.282034,
"street": "Skogsbyvägen"
},
"pickup": {
"seats": 1
}
}
],
"profile": "car_fastest",
"routes": [
{
"vehicle_id": "bus1",
"visits": [
{
"kind": "stop",
"id": "s1"
},
{
"kind": "stop",
"id": "s2"
}
]
}
]
}
```
JSON body:
- `vehicles` (array of object, 1..5 items, required)
- `id` (string, max 64 chars, required): Your own id for the vehicle, echoed in the answer and never stored. Unique among the vehicles, stops and shipments of one request.
- `start` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), required): Where the vehicle starts: a depot, a garage.
- `end` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md), optional): Where it must end; left out, its route ends at its last task.
- `capacity` (object, optional): What the vehicle carries at most, per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `dimensions` ([Vehicle](https://api.kartpunkt.se/docs/shapes/Vehicle.md), optional): The vehicle's weight, axle load, height, width, length and hazmat, on a profile whose takes_vehicle is true in GET /v1/profiles (hgv). A road whose recorded limit is below it is not used for this vehicle. The plan's matrix is computed once per distinct dimensions sent.
- `time_window` (object, optional): When the vehicle is available: it leaves start no earlier and reaches end no later. Left out, any time: its visits' arrival and departure are null, and only the _s fields time them.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `max_travel_s` (integer <= 86400, optional): At most this many seconds driving, from start to end; waiting and service not counted.
- `skills` (array of string, max 64 chars, 0..20 items, optional): Your own strings: what this vehicle can do.
- `stops` (array of object, 0..25 items, optional, default [])
- `id` (string, max 64 chars, required): Your own id for the stop, echoed in the answer and never stored. Unique among the vehicles, stops and shipments of one request.
- `at` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md) | [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md), required): Where: { lat, lon, street? } as every point, or { address_id, as_of? } to route from the address's own point. Each address id resolved is metered as an address read and the call as a live call (kp_live_ keys); a stop whose id resolves to no address served is answered unassigned_address_id, not refused.
- `pickup` (object, optional): Loaded at this stop and carried to the vehicle's end (passengers boarding, bins emptied into the truck), per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `delivery` (object, optional): Loaded at the vehicle's start and set down here (meals, parcels from the depot), per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `service_s` (integer 0..86400, optional, default 0): Seconds spent at the place, after arrival and any wait.
- `time_windows` (array of object, 1..4 items, optional): When service may start here: one or more windows. Left out, any time within the vehicle's.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `skills` (array of string, max 64 chars, 0..20 items, optional): Your own strings, e.g. 'wheelchair_lift', 'crane': a task goes only to a vehicle with every skill it names.
- `shipments` (array of object, 0..12 items, optional, default [])
- `id` (string, max 64 chars, required): Your own id for the shipment, echoed in the answer and never stored. Unique among the vehicles, stops and shipments of one request.
- `pickup` (object, required): Where the amount is picked up.
- `at` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md) | [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md), required): Where: { lat, lon, street? } as every point, or { address_id, as_of? } to route from the address's own point. Each address id resolved is metered as an address read and the call as a live call (kp_live_ keys); a stop whose id resolves to no address served is answered unassigned_address_id, not refused.
- `service_s` (integer 0..86400, optional, default 0): Seconds spent at the place, after arrival and any wait.
- `time_windows` (array of object, 1..4 items, optional): When service may start here: one or more windows. Left out, any time within the vehicle's.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `delivery` (object, required): Where it is set down: by the same vehicle, after the pickup.
- `at` ([StreetPoint](https://api.kartpunkt.se/docs/shapes/StreetPoint.md) | [AddressIdPlace](https://api.kartpunkt.se/docs/shapes/AddressIdPlace.md), required): Where: { lat, lon, street? } as every point, or { address_id, as_of? } to route from the address's own point. Each address id resolved is metered as an address read and the call as a live call (kp_live_ keys); a stop whose id resolves to no address served is answered unassigned_address_id, not refused.
- `service_s` (integer 0..86400, optional, default 0): Seconds spent at the place, after arrival and any wait.
- `time_windows` (array of object, 1..4 items, optional): When service may start here: one or more windows. Left out, any time within the vehicle's.
- `start` (string, date-time (RFC 3339, with offset), required): From (RFC 3339 with offset).
- `end` (string, date-time (RFC 3339, with offset), required): Until (RFC 3339 with offset).
- `amount` (object, optional): What travels from pickup to delivery, per dimension you name, e.g. { "seats": 4, "wheelchair": 1 }: whole numbers. A dimension left out is 0.
- `max_ride_s` (integer <= 86400, optional): At most this many seconds from leaving the pickup to arriving at the delivery. Checked on the plan made without it: a shipment over it is taken out and answered unassigned_max_ride, never re-planned.
- `skills` (array of string, max 64 chars, 0..20 items, optional): Your own strings, e.g. 'wheelchair_lift', 'crane': a task goes only to a vehicle with every skill it names.
- `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.
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), required): How the legs are weighed: see GET /v1/profiles. A plan's durations are each leg's as /v1/route answers it.
- `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 over each leg: see GET /v1/facts. With facts or geometry, the answer has legs.
- `geometry` ("geojson" | "polyline6" | "none", optional, default "none"): Each leg's line and its stretches with road references: GeoJSON, an encoded polyline at 6 decimals, or not at all (the default).
- `network` ([NetworkSelector](https://api.kartpunkt.se/docs/shapes/NetworkSelector.md), optional)
- `routes` (array of object, 0..5 items, required): The plan to check: per vehicle, its visits in order. A task in no route is answered in unassigned; an id naming nothing sent, a visit of the wrong kind, a task visited twice or a vehicle twice is refused 400 invalid_request.
- `vehicle_id` (string, max 64 chars, required): The vehicle's id, as sent in vehicles.
- `visits` (array of object, 0..49 items, required): The vehicle's tasks in the order it visits them, its start and end left out: they are the vehicle's own. The kind and id of an optimize answer's visits, without start and end, are a plan.
- `kind` ("stop" | "pickup" | "delivery", required): 'stop' for a stop; 'pickup' or 'delivery' for a shipment's two places.
- `id` (string, max 64 chars, required): The stop's or shipment's id, as sent in stops or shipments.
## Response 200
Example:
```json
{
"feasible": false,
"routes": [
{
"vehicle_id": "bus1",
"visits": [
{
"kind": "start",
"id": null,
"snap_m": 11.96,
"street_matched": true,
"arrival": "2026-10-13T07:00:00.000Z",
"departure": "2026-10-13T07:01:00.000Z",
"arrival_s": 0,
"waiting_s": 0,
"service_s": 0,
"load": {
"seats": 0
},
"distance_m": 0,
"duration_s": 0
},
{
"kind": "stop",
"id": "s1",
"snap_m": 11.98,
"street_matched": true,
"arrival": "2026-10-13T07:02:00.000Z",
"departure": "2026-10-13T07:03:00.000Z",
"arrival_s": 77,
"waiting_s": 0,
"service_s": 30,
"load": {
"seats": 4
},
"distance_m": 860,
"duration_s": 77.4
},
{
"kind": "stop",
"id": "s2",
"snap_m": 15.02,
"street_matched": true,
"arrival": "2026-10-13T07:04:00.000Z",
"departure": "2026-10-13T07:05:00.000Z",
"arrival_s": 327,
"waiting_s": 0,
"service_s": 30,
"load": {
"seats": 6
},
"distance_m": 4350,
"duration_s": 219.7
},
{
"kind": "end",
"id": null,
"snap_m": 14.97,
"street_matched": true,
"arrival": "2026-10-13T07:06:00.000Z",
"departure": "2026-10-13T07:07:00.000Z",
"arrival_s": 526,
"waiting_s": 0,
"service_s": 0,
"load": {
"seats": 6
},
"distance_m": 3790,
"duration_s": 169.3
}
],
"legs": null,
"distance_m": 9000.1,
"duration_s": 526,
"waiting_s": 0,
"service_s": 60
}
],
"unassigned": [
{
"kind": "stop",
"id": "s3",
"reason": "unassigned_not_in_plan",
"message": "The plan you sent places this task in no route. Add it to a vehicle's visits, or leave it out of stops and shipments if it is planned elsewhere.",
"blocked_by": null
}
],
"violations": [],
"totals": {
"vehicles_used": 1,
"distance_m": 9000.1,
"duration_s": 526,
"waiting_s": 0,
"service_s": 60
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01k7exampleexampleexample0",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:08:00.000Z",
"graph": "snap_01k7exampleexampleexample0",
"profile": "car_fastest",
"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"
}
]
}
}
```
- `feasible` (boolean): true when no route breaks anything and every task is in a route: violations and unassigned are both [].
- `routes` (array of object): One per vehicle in routes with a visit, in the order of vehicles sent: optimize's route shape, timed and loaded as optimize times and loads a plan.
- `vehicle_id` (string): The vehicle's id, as sent.
- `visits` (array of object): In the order driven: start, each task, end (where one was sent).
- `kind` ("start" | "stop" | "pickup" | "delivery" | "end"): 'start' and 'end' are the vehicle's own; 'stop' a stop; 'pickup' and 'delivery' a shipment's two places.
- `id` (string | null): The stop's or shipment's id; null at the vehicle's start and end.
- `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): Whether the place met the road of the street sent; null without a street, and at a place sent as an address id (the answer holds nothing of the address).
- `arrival` (string | null): RFC 3339, in the offset of the vehicle's time_window; null without one.
- `departure` (string | null): RFC 3339: arrival, any wait and service_s later; null without a time_window.
- `arrival_s` (number): Seconds from leaving start to arriving here.
- `waiting_s` (number): Seconds waited here for a time window to open.
- `service_s` (number): Seconds of service here, as sent.
- `load` (object): On board when leaving here, per dimension named in the request.
- `distance_m` (number): Metres along the network from the previous visit (0 at start).
- `duration_s` (number): Seconds driving from the previous visit at free flow (0 at start).
- `legs` (array of object | null): With facts or geometry, one leg from each visit to the next, in order: visits.length − 1 of them. null without either.
- `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.
- `segments` (array of [Segment](https://api.kartpunkt.se/docs/shapes/Segment.md)): The leg's stretches with their road references (ADR-0013), in order; with geometry, each with its line.
- `distance_m` (number): Metres along the network over the whole route.
- `duration_s` (number): Seconds from leaving start to the last visit: driving, waiting and service.
- `waiting_s` (number): Seconds waited for time windows to open, over the route.
- `service_s` (number): Seconds of service, over the route.
- `unassigned` (array of object): Every stop and shipment in no route, with why. [] when the plan places every one.
- `kind` ("stop" | "shipment"): Whether id names a stop or a shipment.
- `id` (string): The stop's or shipment's id, as sent.
- `reason` ("unassigned_not_in_plan" | "unassigned_address_id" | "unassigned_unreachable"): Why, as a code from ERRORS. unassigned_not_in_plan: no route places it. unassigned_address_id: its address id resolves to no address served, so its visits leave the route and the rest is checked as sent. unassigned_unreachable: no road joins it to the visit before or after.
- `message` (string): The code's message from ERRORS, saying what to change; with unassigned_unreachable, the message for its cause.
- `blocked_by` (array of object | null): With unassigned_unreachable where only roads kept off by avoid reach the task: what in avoid is in the way, as route's blocked_by. null otherwise.
- `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.
- `unreachable` ([Unreachable](https://api.kartpunkt.se/docs/shapes/Unreachable.md), optional): With unassigned_unreachable: which place, where it met the network and why no vehicle reaches it. Absent with every other reason. met is null for a place sent as an address id: the answer holds no point of the address.
- `unresolved` ([Unresolved](https://api.kartpunkt.se/docs/shapes/Unresolved.md), optional): With unassigned_address_id: which place's address id resolved to no address served, and why. Absent with every other reason.
- `violations` (array of object): Everything the routes break, by route and visit in order. [] when the plan holds.
- `code` ("plan_capacity" | "plan_time_window" | "plan_vehicle_time_window" | "plan_skills" | "plan_max_travel" | "plan_shipment_order" | "plan_max_ride"): What is broken, as a code from ERRORS: its page at /errors/{code} says what to change.
- `message` (string): The code's message from ERRORS.
- `vehicle_id` (string): The vehicle whose route breaks it.
- `visit` (integer): Where: the index in the answered route's visits, 0 being start. It is the visit late or over capacity, the end for the vehicle's time_window and max_travel_s, and a shipment's delivery, else its pickup.
- `kind` ("stop" | "pickup" | "delivery" | "end"): The kind of the visit at that index.
- `id` (string | null): The stop's or shipment's id; null at the end.
- `dimension` (string | null): With plan_capacity, the dimension over capacity; null otherwise.
- `over` (number | null): By how much: seconds late or over for plan_time_window, plan_vehicle_time_window, plan_max_travel and plan_max_ride; the amount over capacity for plan_capacity; null for plan_skills and plan_shipment_order.
- `totals` (object): The routes summed.
- `vehicles_used` (integer): Vehicles with at least one task.
- `distance_m` (number): Metres, every route's together.
- `duration_s` (number): Seconds, every route's duration_s together.
- `waiting_s` (number): Seconds waited, every route's together.
- `service_s` (number): Seconds of service, every route's together.
- `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 ' (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: "check_plan"`) |
| --- | --- | --- |
| `stops` | 25 | 500 |
| `shipments` | 12 | 250 |
| `vehicles` | 5 | 50 |
| Distinct locations (stops, shipments' pickups and deliveries, vehicles' starts and ends) | 30 | 600 |
| An `avoid` entry with `mode: "barrier"` | refused 413 `too_large` | allowed |
| Ways of the network `avoid` keeps off | 20000 | 20000 |
| `prefer` (items) | 6 | 6 |
| `facts` (items) | 34 | 34 |
| `routes` (items) | 5 | 50 |
| 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 stop and per shipment sent.
---
# POST /v1/jobs
> Index: https://kartpunkt.se/llms.txt
operationId: `submitJob`. Run an operation as a job.
Tag: `jobs`.
Any routing, planning or address-list operation run in the background, with the same input as its synchronous call up to the job limits below, on any network snapshot, retired ones included. Use it above an operation's synchronous limits or for a snapshot no longer served, then read the job with GET /v1/jobs/{id}. Jobs run behind synchronous calls and slow down under load, but never fail for it. A job is checked when it is submitted, an optimize or check_plan job's vehicle starts and ends on the network included, and refused at once as the job would fail; with dry_run: true it is only checked, answered 200 and not queued. An address_list job matches your own list of up to 50000 addresses to the register, one result row per row sent and never a list of candidates. Every row counts as one live call and one address, held when the job is submitted. A list longer than what is left of the day's address limit is 429 quota_exceeded, so split it across days. Its input is deleted when the job ends, and its result within 24 hours.
## Request
`POST https://api.kartpunkt.se/v1/jobs` 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
{
"operation": "route",
"input": {
"origin": {
"lat": 63.724247,
"lon": 19.347595
},
"destination": {
"lat": 63.72059,
"lon": 19.342166
},
"profile": "walk",
"facts": [
"speed_limit"
],
"geometry": "none"
}
}
```
JSON body:
One of 9 forms, by `operation` ("route", "distances", "matrix", "reach", "trace", "nearest", "optimize", "check_plan", "address_list").
Every form:
- `idempotency_key` (string, max 128 chars, optional): Your id for this submission, per account: the same key with the same input answers the job already made, and with another input 409 idempotency_conflict.
- `dry_run` (boolean, optional): true: check the job as a submit would and answer 200 without queueing it. No job is made, nothing sent is kept, idempotency_key is ignored, and it counts as one request, with no calculation or address metered. Left out or false, the job is queued.
By `operation`:
- `operation`: "route":
- `input` (object, required): the body of [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), within the job limits on this page.
- `operation`: "distances":
- `input` (object, required): the body of [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), within the job limits on this page.
- `operation`: "matrix":
- `input` (object, required): the body of [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), within the job limits on this page. A matrix: origins × destinations at most 625 cells synchronously, refused 413 too_large above it; up to 10000 as a job (POST /v1/jobs).
- `operation`: "reach":
- `input` (object, required): the body of [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), within the job limits on this page.
- `operation`: "trace":
- `input` (object, required): the body of [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), within the job limits on this page.
- `operation`: "nearest":
- `input` (object, required): the body of [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), within the job limits on this page.
- `operation`: "optimize":
- `input` (object, required): the body of [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), within the job limits on this page.
- `operation`: "check_plan":
- `input` (object, required): the body of [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), within the job limits on this page.
- `operation`: "address_list":
- `input` (object, required)
- Either `{ kind, rows }`:
- `kind` ("validate", required): Each row validated as POST /v1/addresses/validate validates it.
- `rows` (array of object, 1..50000 items, required): Your own list, 1 to 50000 rows, each a structured address as POST /v1/addresses/validate takes it, with your id. The result has one row per row sent, in the order sent.
- `street` (string, max 200 chars, required): Street name or address area, e.g. 'Östra Ågatan'. Case and å/ä/ö are ignored.
- `number` (integer 0..99999 | null, optional): House number, e.g. 12.
- `letter` (string, max 2 chars | null, optional): Letter after the number, e.g. 'B'.
- `postcode` (string, max 10 chars | null, optional): Postnummer, e.g. '75331' or '753 31'. Scopes the match; strongly recommended.
- `locality` (string, max 100 chars | null, optional): Postort or kommun, e.g. 'Uppsala'. Scopes the match when postcode is missing.
- `id` (string, max 64 chars, optional): Your own id for the row, echoed back as its result row's id.
- Or `{ kind, rows, municipality_codes }`:
- `kind` ("geocode", required): Each row's free text read as POST /v1/geocode reads q, answered with its single best match.
- `rows` (array of object, 1..50000 items, required): Your own list, 1 to 50000 rows, each free text as POST /v1/geocode's q, with your id. The result has one row per row sent, in the order sent.
- `id` (string, max 64 chars, optional): Your own id for the row, echoed back as its result row's id.
- `q` (string, max 200 chars, required): Free text as typed, street first, e.g. 'Östra Ågatan 12B, Uppsala' or 'ostra agatan 12 b'. Typos, å/ä/ö without diacritics and abbreviations such as sthlm and gbg are handled. Only 8 distinct words are read, a postnummer as one; past them every hit is match_type 'partial'.
- `municipality_codes` (array of string, pattern `^\d{4}$`, 1..10 items, optional): Kommunkoder, SCB's four-digit codes, e.g. ['0380']: only hits whose municipality_code is one of them are answered. They narrow before ranking, so a small kommun's street is found past same-named streets elsewhere. Only Sweden's 290 codes are accepted, a repeated one read once. A kommun with no address in the key's dataset answers results: []. Left out: all of Sweden.
- Or `{ kind, rows, radius_m }`:
- `kind` ("reverse", required): Each row's point answered with the nearest current address, as POST /v1/reverse-geocode with limit 1.
- `rows` (array of object, 1..50000 items, required): Your own list, 1 to 50000 rows, each a WGS84 point, with your id. The result has one row per row sent, in the order sent.
- `id` (string, max 64 chars, optional): Your own id for the row, echoed back as its result row's id.
- `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md), required)
- `radius_m` (number <= 50000, optional, default 2000): Ignore addresses farther away than this, in straight-line metres. For a vehicle's position send "radius_m": 5000, "distinct": "street", "limit": 3, as the recipe "A vehicle's position" does: 5000 m reaches the nearest address from a rural road.
## Response 200
The shape [JobCheck](https://api.kartpunkt.se/docs/shapes/JobCheck.md).
- `operation` ("route" | "distances" | "matrix" | "reach" | "trace" | "nearest" | "optimize" | "check_plan" | "address_list"): The job's operation, as sent.
- `dry_run` (true): The job was checked, not queued: no job was made.
- `accepted` (true): A submit of the same body now passes every check made before the queue: the job's limits, its network and profile and, for optimize and check_plan, each vehicle's start and end on a road. Not checked: quotas (the submit may be 429 quota_exceeded) and idempotency_key (a key already used answers its job, or 409 idempotency_conflict).
- `provenance` ([Provenance](https://api.kartpunkt.se/docs/shapes/Provenance.md)): The network snapshot the job would run on if submitted now, or the one network names, and its source. No network, sources empty and X-Data-Attribution empty, for address_list, which reads no register when checked, and when no network is named and none is served yet.
## Response 202
Example:
```json
{
"operation": "route",
"id": "job_01k7exampleexampleexample0",
"status": "queued",
"progress": null,
"created_at": "2026-10-13T07:00:00.000Z",
"started_at": null,
"finished_at": null,
"expires_at": null,
"error": null,
"result": null
}
```
The shape [Job](https://api.kartpunkt.se/docs/shapes/Job.md).
One of 9 forms, by `operation` ("route", "distances", "matrix", "reach", "trace", "nearest", "optimize", "check_plan", "address_list").
Every form:
- `id` (string): The job's id: read it with GET /v1/jobs/{id}.
- `status` ("queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"): Where the job is: 'queued' or 'running' until it ends 'succeeded', 'failed' or 'cancelled', then 'expired' once its result is deleted at expires_at.
- `progress` (object | null): How far the running job has come. null until it first reports.
- `done` (number): Units of work done.
- `total` (number): Units of work in all.
- `created_at` (string): When the job was submitted, RFC 3339.
- `started_at` (string | null): When it last started running, RFC 3339; null until it has.
- `finished_at` (string | null): When it ended, RFC 3339; null until it has.
- `expires_at` (string | null): When the result is deleted, RFC 3339; null until the job ends. Its sources' policies set how long it is kept (GET /v1/sources).
- `error` (object | null): Why a failed job failed. null in every other status.
- `code` (string): A code from the error table.
- `message` (string): That code's message, saying what to do next.
By `operation`:
- `operation`: "route":
- `result` (object): the 200 answer of [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "distances":
- `result` (object): the 200 answer of [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "matrix":
- `result` (object): the 200 answer of [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "reach":
- `result` (object): the 200 answer of [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "trace":
- `result` (object): the 200 answer of [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "nearest":
- `result` (object): the 200 answer of [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "optimize":
- `result` (object): the 200 answer of [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "check_plan":
- `result` (object): the 200 answer of [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "address_list":
- `result` (object | null): The operation's answer once the job has succeeded; null in every other status.
- `kind` ("validate" | "geocode" | "reverse"): The kind sent.
- `rows` (array of object): One row per row sent, in the order sent.
- `id` (string | null): The id sent with the row; null where none was.
- `status` ("matched" | "ambiguous" | "unmatched"): 'matched': the row is one address, in address. 'ambiguous': it fits several addresses; send its postnummer or postort. 'unmatched': no address is the row. The recipe Your own address list says when each applies.
- `address` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md) | null): The address the row is, when status is 'matched'; otherwise null, never a guess or another address.
- `issues` (array of "street_not_found" | "number_not_found" | "letter_not_found" | "postcode_mismatch" | "locality_mismatch" | "ambiguous"): For validate, what POST /v1/addresses/validate says is wrong with the row (empty when it is valid); for geocode and reverse, always empty.
- `summary` (object): Your own rows counted by status: how many of them match an address. A count of your list, never of the register.
- `rows` (integer)
- `matched` (integer)
- `ambiguous` (integer)
- `unmatched` (integer)
- `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 ' (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.
- [`idempotency_conflict`](https://api.kartpunkt.se/errors/idempotency_conflict) (409): This idempotency_key was used with a different input. Use a new key for a new input, or resend the original input to get the original job.
- [`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.
## Limits
| Limit | Value |
| --- | --- |
| Request body | 8 MB |
| `route`: `via` | 10 |
| `route`: Ways of the network `avoid` keeps off | 20000 |
| `distances`: `origins` | 50000 |
| `distances`: `destinations` | 5 |
| `distances`: Origins × destinations | 50000 |
| `distances`: Origins × destinations with an `avoid` entry in `mode: "barrier"` | 50000 |
| `distances`: Origins × destinations with `geometry` | 5000 |
| `distances`: Ways of the network `avoid` keeps off | 20000 |
| `matrix`: Cells (origins × destinations) | 10000 |
| `matrix`: Cells with an `avoid` entry in `mode: "barrier"` | 10000 |
| `matrix`: Origins with `vehicle`, `prefer` or `avoid` (a search per origin) | within the cells (a job runs in chunks) |
| `matrix`: Ways of the network `avoid` keeps off | 20000 |
| `reach`: Budget values (`budget.duration_s` or `budget.distance_m`) | 4 |
| `trace`: `points` | 2000 |
| `nearest`: `points` | 50000 |
| `optimize`: `stops` | 500 |
| `optimize`: `shipments` | 250 |
| `optimize`: `vehicles` | 50 |
| `optimize`: Distinct locations (stops, shipments' pickups and deliveries, vehicles' starts and ends) | 600 |
| `optimize`: An `avoid` entry with `mode: "barrier"` | allowed |
| `optimize`: Ways of the network `avoid` keeps off | 20000 |
| `check_plan`: `stops` | 500 |
| `check_plan`: `shipments` | 250 |
| `check_plan`: `vehicles` | 50 |
| `check_plan`: Distinct locations (stops, shipments' pickups and deliveries, vehicles' starts and ends) | 600 |
| `check_plan`: An `avoid` entry with `mode: "barrier"` | allowed |
| `check_plan`: Ways of the network `avoid` keeps off | 20000 |
## Metering
Calculations: what its operation takes for the same input synchronously.
---
# GET /v1/jobs/{id}
> Index: https://kartpunkt.se/llms.txt
operationId: `getJob`. A job, with its result once done.
Tag: `jobs`.
A job's status and progress, and once it has succeeded the operation's answer as `result`. Read it from your backend with `wait` until the job ends, as the recipe "Running a job" says; an account reads its own jobs only.
## Request
`GET https://api.kartpunkt.se/v1/jobs/{id}` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
Parameters:
- `id` (path, string, pattern `^job_[a-z0-9]+$`, required): The job's id, from POST /v1/jobs.
- `wait` (query, integer 0..30, optional): Seconds to wait for the job to end, from 0 to 30: the answer comes as soon as it has succeeded, failed or been cancelled, or when the wait runs out, with the job as it is then. Left out or 0, the job as it is now.
## Response 200
Example:
```json
{
"operation": "route",
"id": "job_01k7exampleexampleexample0",
"status": "succeeded",
"progress": null,
"created_at": "2026-10-13T07:00:00.000Z",
"started_at": "2026-10-13T07:01:00.000Z",
"finished_at": "2026-10-13T07:02:00.000Z",
"expires_at": "2026-10-13T07:03:00.000Z",
"error": null,
"result": {
"status": "ok",
"distance_m": 560,
"duration_s": 403,
"facts": {
"speed_limit": {
"40": 560
}
},
"avoid_m": null,
"snap_m": 10.05,
"street_matched": null,
"destination_snap_m": 14.97,
"destination_street_matched": null,
"blocked_by": null,
"via": null,
"segments": [
{
"length_m": 50,
"ref": "0:17736@0.833269-1.000000",
"facts": {
"speed_limit": "40"
}
},
{
"length_m": 500,
"ref": "0:17742@1.000000-0.000000",
"facts": {
"speed_limit": "40"
}
},
{
"length_m": 10,
"ref": "0:17733@0.000000-0.016646",
"facts": {
"speed_limit": "40"
}
}
],
"avoid": null,
"alternatives": null,
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01k7exampleexampleexample0",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-13T07:04: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"
}
]
}
}
}
```
The shape [Job](https://api.kartpunkt.se/docs/shapes/Job.md).
One of 9 forms, by `operation` ("route", "distances", "matrix", "reach", "trace", "nearest", "optimize", "check_plan", "address_list").
Every form:
- `id` (string): The job's id: read it with GET /v1/jobs/{id}.
- `status` ("queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"): Where the job is: 'queued' or 'running' until it ends 'succeeded', 'failed' or 'cancelled', then 'expired' once its result is deleted at expires_at.
- `progress` (object | null): How far the running job has come. null until it first reports.
- `done` (number): Units of work done.
- `total` (number): Units of work in all.
- `created_at` (string): When the job was submitted, RFC 3339.
- `started_at` (string | null): When it last started running, RFC 3339; null until it has.
- `finished_at` (string | null): When it ended, RFC 3339; null until it has.
- `expires_at` (string | null): When the result is deleted, RFC 3339; null until the job ends. Its sources' policies set how long it is kept (GET /v1/sources).
- `error` (object | null): Why a failed job failed. null in every other status.
- `code` (string): A code from the error table.
- `message` (string): That code's message, saying what to do next.
By `operation`:
- `operation`: "route":
- `result` (object): the 200 answer of [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "distances":
- `result` (object): the 200 answer of [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "matrix":
- `result` (object): the 200 answer of [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "reach":
- `result` (object): the 200 answer of [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "trace":
- `result` (object): the 200 answer of [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "nearest":
- `result` (object): the 200 answer of [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "optimize":
- `result` (object): the 200 answer of [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "check_plan":
- `result` (object): the 200 answer of [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "address_list":
- `result` (object | null): The operation's answer once the job has succeeded; null in every other status.
- `kind` ("validate" | "geocode" | "reverse"): The kind sent.
- `rows` (array of object): One row per row sent, in the order sent.
- `id` (string | null): The id sent with the row; null where none was.
- `status` ("matched" | "ambiguous" | "unmatched"): 'matched': the row is one address, in address. 'ambiguous': it fits several addresses; send its postnummer or postort. 'unmatched': no address is the row. The recipe Your own address list says when each applies.
- `address` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md) | null): The address the row is, when status is 'matched'; otherwise null, never a guess or another address.
- `issues` (array of "street_not_found" | "number_not_found" | "letter_not_found" | "postcode_mismatch" | "locality_mismatch" | "ambiguous"): For validate, what POST /v1/addresses/validate says is wrong with the row (empty when it is valid); for geocode and reverse, always empty.
- `summary` (object): Your own rows counted by status: how many of them match an address. A count of your list, never of the register.
- `rows` (integer)
- `matched` (integer)
- `ambiguous` (integer)
- `unmatched` (integer)
- `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 ' (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.
- [`job_not_found`](https://api.kartpunkt.se/errors/job_not_found) (404): No job with that id for this account, or its result has expired. Results are kept for the time GET /v1/sources gives each source; submit the job again.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# DELETE /v1/jobs/{id}
> Index: https://kartpunkt.se/llms.txt
operationId: `cancelJob`. Cancel a job.
Tag: `jobs`.
Cancels a job and answers it. A queued job is cancelled at once; a running one stops at its next unit of work, so the answer may still say running. A job that has ended is answered as it is.
## Request
`DELETE https://api.kartpunkt.se/v1/jobs/{id}` with `Authorization: Bearer $KARTPUNKT_API_KEY`, from your backend (never browser code: the key would leak, and CORS is not enabled).
Parameters:
- `id` (path, string, pattern `^job_[a-z0-9]+$`, required): The job's id, from POST /v1/jobs.
## Response 200
Example:
```json
{
"operation": "route",
"id": "job_01k7exampleexampleexample0",
"status": "cancelled",
"progress": null,
"created_at": "2026-10-13T07:00:00.000Z",
"started_at": null,
"finished_at": "2026-10-13T07:01:00.000Z",
"expires_at": "2026-10-13T07:02:00.000Z",
"error": null,
"result": null
}
```
The shape [Job](https://api.kartpunkt.se/docs/shapes/Job.md).
One of 9 forms, by `operation` ("route", "distances", "matrix", "reach", "trace", "nearest", "optimize", "check_plan", "address_list").
Every form:
- `id` (string): The job's id: read it with GET /v1/jobs/{id}.
- `status` ("queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"): Where the job is: 'queued' or 'running' until it ends 'succeeded', 'failed' or 'cancelled', then 'expired' once its result is deleted at expires_at.
- `progress` (object | null): How far the running job has come. null until it first reports.
- `done` (number): Units of work done.
- `total` (number): Units of work in all.
- `created_at` (string): When the job was submitted, RFC 3339.
- `started_at` (string | null): When it last started running, RFC 3339; null until it has.
- `finished_at` (string | null): When it ended, RFC 3339; null until it has.
- `expires_at` (string | null): When the result is deleted, RFC 3339; null until the job ends. Its sources' policies set how long it is kept (GET /v1/sources).
- `error` (object | null): Why a failed job failed. null in every other status.
- `code` (string): A code from the error table.
- `message` (string): That code's message, saying what to do next.
By `operation`:
- `operation`: "route":
- `result` (object): the 200 answer of [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "distances":
- `result` (object): the 200 answer of [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "matrix":
- `result` (object): the 200 answer of [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "reach":
- `result` (object): the 200 answer of [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "trace":
- `result` (object): the 200 answer of [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "nearest":
- `result` (object): the 200 answer of [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "optimize":
- `result` (object): the 200 answer of [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "check_plan":
- `result` (object): the 200 answer of [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "address_list":
- `result` (object | null): The operation's answer once the job has succeeded; null in every other status.
- `kind` ("validate" | "geocode" | "reverse"): The kind sent.
- `rows` (array of object): One row per row sent, in the order sent.
- `id` (string | null): The id sent with the row; null where none was.
- `status` ("matched" | "ambiguous" | "unmatched"): 'matched': the row is one address, in address. 'ambiguous': it fits several addresses; send its postnummer or postort. 'unmatched': no address is the row. The recipe Your own address list says when each applies.
- `address` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md) | null): The address the row is, when status is 'matched'; otherwise null, never a guess or another address.
- `issues` (array of "street_not_found" | "number_not_found" | "letter_not_found" | "postcode_mismatch" | "locality_mismatch" | "ambiguous"): For validate, what POST /v1/addresses/validate says is wrong with the row (empty when it is valid); for geocode and reverse, always empty.
- `summary` (object): Your own rows counted by status: how many of them match an address. A count of your list, never of the register.
- `rows` (integer)
- `matched` (integer)
- `ambiguous` (integer)
- `unmatched` (integer)
- `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 ' (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.
- [`job_not_found`](https://api.kartpunkt.se/errors/job_not_found) (404): No job with that id for this account, or its result has expired. Results are kept for the time GET /v1/sources gives each source; submit the job again.
- [`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.
## Limits
none
## Metering
A request only: no calculations, no live call, no address limit.
---
# ErrorBody
> Index: https://kartpunkt.se/llms.txt
Every error response: a stable code, what to do next, the field refused, and the code's page. Every code has a page at https://api.kartpunkt.se/errors/{error}, listed at https://api.kartpunkt.se/errors.
- `error` ("unauthorized" | "quota_exceeded" | "live_access_required" | "live_data_unavailable" | "invalid_request" | "method_not_allowed" | "not_found" | "address_closed" | "address_unknown" | "routing_unavailable" | "network_unavailable" | "network_not_active" | "network_pruned" | "snapshot_engine_unavailable" | "profile_unavailable" | "use_not_permitted" | "too_large" | "job_not_found" | "idempotency_conflict" | "unassigned_unreachable" | "unassigned_skills" | "unassigned_capacity" | "unassigned_time_window" | "unassigned_max_travel" | "unassigned_max_ride" | "unassigned_no_room" | "unassigned_address_id" | "unassigned_not_in_plan" | "plan_capacity" | "plan_time_window" | "plan_vehicle_time_window" | "plan_skills" | "plan_max_travel" | "plan_shipment_order" | "plan_max_ride" | "internal_error"): Stable machine-readable error code.
- `message` (string): What went wrong and what to do next, written for the caller (often an AI agent).
- `param` (string, optional): On invalid_request: the input field refused, by its path in the body (`origins.0.point.lat`, `prefer`). Absent on every other code.
- `doc_url` (string): The code's page: what it means and what to do (https://api.kartpunkt.se/errors/{error}).
Used by: [GET /v1/profiles](https://api.kartpunkt.se/docs/operations/listProfiles.md), [GET /v1/facts](https://api.kartpunkt.se/docs/operations/listFacts.md), [GET /v1/sources](https://api.kartpunkt.se/docs/operations/listSources.md), [GET /v1/tilesets](https://api.kartpunkt.se/docs/operations/listTilesets.md), [GET /v1/networks](https://api.kartpunkt.se/docs/operations/listNetworks.md), [GET /v1/networks/{id}](https://api.kartpunkt.se/docs/operations/getNetwork.md), [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md), [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md), [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md), [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md), [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md), [GET /v1/postcodes/{postcode}](https://api.kartpunkt.se/docs/operations/lookupPostcode.md), [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [GET /v1/roads/{ref}](https://api.kartpunkt.se/docs/operations/getRoad.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Profile
> Index: https://kartpunkt.se/llms.txt
A routing profile: who travels, which roads they may use and how a path is weighed. GET /v1/profiles lists them.
Used by: [GET /v1/profiles](https://api.kartpunkt.se/docs/operations/listProfiles.md), [GET /v1/facts](https://api.kartpunkt.se/docs/operations/listFacts.md), [GET /v1/sources](https://api.kartpunkt.se/docs/operations/listSources.md), [GET /v1/tilesets](https://api.kartpunkt.se/docs/operations/listTilesets.md), [GET /v1/networks](https://api.kartpunkt.se/docs/operations/listNetworks.md), [GET /v1/networks/{id}](https://api.kartpunkt.se/docs/operations/getNetwork.md), [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md), [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md), [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md), [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md), [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md), [GET /v1/postcodes/{postcode}](https://api.kartpunkt.se/docs/operations/lookupPostcode.md), [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [GET /v1/roads/{ref}](https://api.kartpunkt.se/docs/operations/getRoad.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Provenance
> Index: https://kartpunkt.se/llms.txt
What an answer was computed from: the network snapshot, the sources read, and whether its data is synthetic. Store it with the answer. Show every sources[].attribution wherever the data is shown (ADR-0016).
- `synthetic` (boolean): true on every answer to a kp_test_ key: its addresses are synthetic, and its road network is NVDB's real one. false on a kp_live_ key's answers.
- `network` (object, optional): The network snapshot the answer was computed on. Left out where the answer read no network, as on GET /v1/profiles.
- `id` (string): The snapshot's id. Send it as network.id to answer on the same network while it is served.
- `dataset` ("nvdb"): The dataset the network is built from: always 'nvdb'.
- `source_date` (string | null): The NVDB delivery date (YYYY-MM-DD) the snapshot was built from.
- `built_at` (string): When the snapshot was built (RFC 3339).
- `graph` (string | null): The routing graph's snapshot id where the answer used one; null where it did not.
- `profile` ([Profile](https://api.kartpunkt.se/docs/shapes/Profile.md), optional)
- `experimental` (boolean, optional): true when the profile the answer was computed on is not yet verified on the national network, as bike since 2026-10-15. Present with profile, which is left out where the answer used none.
- `sources` (array of [SourceRef](https://api.kartpunkt.se/docs/shapes/SourceRef.md)): Every source the answer read, each with its licence and the attribution to show.
Used by: [GET /v1/profiles](https://api.kartpunkt.se/docs/operations/listProfiles.md), [GET /v1/facts](https://api.kartpunkt.se/docs/operations/listFacts.md), [GET /v1/sources](https://api.kartpunkt.se/docs/operations/listSources.md), [GET /v1/tilesets](https://api.kartpunkt.se/docs/operations/listTilesets.md), [GET /v1/networks](https://api.kartpunkt.se/docs/operations/listNetworks.md), [GET /v1/networks/{id}](https://api.kartpunkt.se/docs/operations/getNetwork.md), [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md), [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md), [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md), [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md), [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md), [GET /v1/postcodes/{postcode}](https://api.kartpunkt.se/docs/operations/lookupPostcode.md), [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [GET /v1/roads/{ref}](https://api.kartpunkt.se/docs/operations/getRoad.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# SourceRef
> Index: https://kartpunkt.se/llms.txt
A data source an answer read, with how to credit it.
- `id` (string): The source: 'nvdb', 'lantmateriet' or 'synthetic', as GET /v1/sources lists them.
- `name` (string): The source's name.
- `licence` (string): The source's licence, as an SPDX id.
- `attribution` (string): The credit to show wherever the data is shown or passed on. The X-Data-Attribution header holds an ASCII copy; show this text.
- `url` (string): The licence's URL.
- `updated` (string | null): The date (YYYY-MM-DD) of the source delivery the answer rests on; null where it is not known.
- `as_of` (string, optional): The instant (RFC 3339) the address ids were read at, where an answer resolved them. On optimize and check, it is when an id without its own as_of was resolved; for a job, when it was submitted. Send it as as_of to answer on the same addresses again.
Used by: [GET /v1/profiles](https://api.kartpunkt.se/docs/operations/listProfiles.md), [GET /v1/facts](https://api.kartpunkt.se/docs/operations/listFacts.md), [GET /v1/sources](https://api.kartpunkt.se/docs/operations/listSources.md), [GET /v1/tilesets](https://api.kartpunkt.se/docs/operations/listTilesets.md), [GET /v1/networks](https://api.kartpunkt.se/docs/operations/listNetworks.md), [GET /v1/networks/{id}](https://api.kartpunkt.se/docs/operations/getNetwork.md), [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md), [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md), [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md), [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md), [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md), [GET /v1/postcodes/{postcode}](https://api.kartpunkt.se/docs/operations/lookupPostcode.md), [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [GET /v1/roads/{ref}](https://api.kartpunkt.se/docs/operations/getRoad.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Address
> Index: https://kartpunkt.se/llms.txt
An address point or a street-level hit.
- `id` (string): Stable id: 'se.lm.' and Lantmäteriet's own object identity of the address (belägenhetsadress), so it is the register's, not Kartpunkt's; 'se.syn.' and a code for a kp_test_ key. Store it with the label; it survives weekly refreshes. Street-level hits have ids starting 'street:'.
- `type` ("address" | "street"): 'address' for an address point; 'street' for a street-level hit, answered while no house number is typed.
- `label` (string): Display label, e.g. 'Östra Ågatan 12B, 753 31 Uppsala'.
- `street` (string | null): Street name or address area (gatunamn / adressområde); null when it has none.
- `number` (integer | null): House number (adressplatsnummer); null on street-level hits.
- `letter` (string | null): Letter after the number (bokstavstillägg), e.g. 'B'; null when there is none.
- `postcode` (string | null): Postnummer, five digits without space; null when it has none.
- `locality` (string | null): Postort; null when it has none.
- `municipality_code` (string | null): Kommunkod, four digits, e.g. '0380' for Uppsala; null when it has none.
- `municipality` (string | null): Kommun name; null when it has none.
- `county_code` (string | null): Länskod, two digits as the register delivers it (e.g. '03' for Uppsala län); null when the address has none.
- `county` (string | null): Län name as SCB writes it, e.g. 'Uppsala län'; null when county_code is null or is not one of Sweden's 21 län codes.
- `district_code` (string | null): Distriktskod, six digits as the register delivers it, e.g. '215134'; null when the address has none. A street-level hit has one only when every address on the street lies in the same distrikt.
- `district` (string | null): Distrikt name as the register delivers it, the folkbokföring district since 2016; null when the address has none. A street-level hit has one only when district_code does.
- `point` (object): Where the address point lies, WGS84. For street-level hits, the street's centre.
- `lat` (number -90..90): Latitude, WGS84 decimal degrees, -90 to 90.
- `lon` (number -180..180): Longitude, WGS84 decimal degrees, -180 to 180.
- `position_type` ("building" | "entrance" | "driveway" | "plot" | "approximate" | "other" | null): What the address point marks: 'building', 'entrance', 'driveway', 'plot' (the property), 'approximate' or 'other'. null when the source does not say, and always on street-level hits. An 'approximate' point was placed only roughly, not to the metre. It describes the point only and never changes ranking or validation.
- `match_type` ("exact" | "corrected" | "partial" | null): How the address matches the words sent. 'exact': everything sent is in the address, ignoring case, spacing, punctuation, diacritics, known abbreviations and parts not sent. 'corrected': a word was read as misspelt. 'partial': something sent, such as a postort, is not the address's. null on reverse. It never changes ranking, score, valid or issues; read an unknown value as 'partial'.
- `score` (number): Relevance score; only meaningful for ordering within one response.
- `distance_m` (number, optional): Straight-line metres (spheroidal) from the point sent. Present on reverse, and on search when near is sent.
Used by: [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md), [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md), [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md), [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md), [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Point
> Index: https://kartpunkt.se/llms.txt
A WGS84 coordinate.
- `lat` (number -90..90): Latitude, WGS84 decimal degrees.
- `lon` (number -180..180): Longitude, WGS84 decimal degrees.
Used by: [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md), [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# AddressChange
> Index: https://kartpunkt.se/llms.txt
What happened to one id sent, comparing what was served at `since` with what is served now.
- `id` (string): The id as sent.
- `status` ("unchanged" | "changed" | "closed" | "unknown"): 'unchanged': served now, every field as it was at `since`. 'changed': served now under the same id, with a field that differs. 'closed': not served now, since a delivery removed or reserved it; the same id can be served again. 'unknown': no version of this id in this key's dataset. See the recipe "Keeping stored addresses current".
- `changed_on` (string, date YYYY-MM-DD, optional): Only on 'changed': the date (YYYY-MM-DD) of the delivery that made the address's current version.
- `changed_fields` (array of "label" | "street" | "number" | "letter" | "postcode" | "locality" | "municipality_code" | "municipality" | "county_code" | "county" | "district_code" | "district" | "point" | "position_type", optional): Only on 'changed': the names of the address fields whose values differ from what was served at `since`, never their old values.
- `address` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md), optional): Only on 'changed': the address as served now, under the same id. Its score is 1 and its match_type null, since no words were sent.
- `closed_on` (string, date YYYY-MM-DD, optional): Only on 'closed': the date (YYYY-MM-DD) of the delivery that stopped serving the address.
Used by: [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md).
---
# StreetPoint
> Index: https://kartpunkt.se/llms.txt
A WGS84 coordinate, with the street it lies on when known.
- `lat` (number -90..90): Latitude, WGS84 decimal degrees.
- `lon` (number -180..180): Longitude, WGS84 decimal degrees.
- `street` (string, max 200 chars, optional): The street the point lies on, e.g. 'Storgatan'. The point then meets the nearest road of that name within 500 m, else the nearest road of the network; street_matched in the answer says which. It is not stored.
Used by: [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# NetworkSelector
> Index: https://kartpunkt.se/llms.txt
The network snapshot to answer on, by id or as_of, one of the two. Leave it out for the snapshot served now. Another snapshot's answers are computed as jobs (POST /v1/jobs), only while its graph runs on the current engine.
- `id` (string, pattern `^snap_[a-z0-9]+$`, optional): A snapshot's id, from GET /v1/networks or an earlier answer's provenance.network.id.
- `as_of` (string, date-time (RFC 3339, with offset), optional): An instant (RFC 3339): the snapshot that was served then.
Used by: [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# RoadRef
> Index: https://kartpunkt.se/llms.txt
A stretch of road: an NVDB link sequence id and the part of it covered, as measures 0 to 1 in the direction travelled. It names the same stretch in every network snapshot that holds the link (ADR-0013).
Used by: [POST /v1/snap](https://api.kartpunkt.se/docs/operations/snapPoints.md), [POST /v1/roads/nearby](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md), [GET /v1/roads/{ref}](https://api.kartpunkt.se/docs/operations/getRoad.md), [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Vehicle
> Index: https://kartpunkt.se/llms.txt
The vehicle's weight, dimensions and kind, on a profile whose `takes_vehicle` is true in GET /v1/profiles (hgv). A road whose recorded limit is below the vehicle's is not used. On any other profile it is refused 400 invalid_request, never ignored.
- `weight_t` (number <= 100, optional): Gross weight, tonnes.
- `axle_load_t` (number <= 30, optional): Heaviest axle load, tonnes.
- `height_m` (number <= 6, optional): Height, metres.
- `width_m` (number <= 4, optional): Width, metres.
- `length_m` (number <= 40, optional): Length, metres.
- `hazmat` (boolean, optional): Carries dangerous goods (farligt gods).
- `kind` ("lorry" | "bus", optional): What the vehicle is, for the bans NVDB records per kind: 'lorry' (lastbil, tung lastbil) or 'bus' (buss). Left out, a lorry.
- `bearing_class` ("BK1" | "BK2" | "BK3" | "BK4", optional): The bearing class (bärighetsklass) the vehicle needs: a road whose class carries less is kept off. Classes order by capacity, BK3 < BK2 < BK1 < BK4, not by number. Left out, bearing class is reported, not routed.
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# Avoid
> Index: https://kartpunkt.se/llms.txt
What the path keeps off, on any profile; left out or empty, nothing. An end on a road a barrier keeps off meets the network on its own side, unless within 1 m of it: the nearest road within 500 m a straight line reaches without crossing the barrier. See "Keeping a path off roads: avoid, barriers and alternatives".
- `stretches` (array of object, 0..100 items, optional): Lines to keep off, at most 200 km together: a closure, roadworks, or a road you classify as unsuitable. Each is matched to the car network's roads along it, piece by piece; the answer's avoid.stretches says which matched. Keep the lists in your own system and send them with each call; nothing is stored.
- `line` (object): The stretch as a GeoJSON LineString in WGS84, drawn along the road it classifies. A street the line only crosses, a line drawn across a road and a separate walk or cycle path beside it are not matched.
- `type` ("LineString"): Always 'LineString' (GeoJSON).
- `coordinates` (array of array of any, 2..2 items, 2..1000 items): The line's positions, 2 to 1000, each [lon, lat] in WGS84 (GeoJSON order: longitude first, -180 to 180; latitude -90 to 90).
- `buffer_m` (number 1..50, optional): How far from the line a road may lie and still be matched, in metres. A piece of road runs between junctions, or to where its speed limit, name or class changes. A piece is matched when 90 % of it lies within buffer_m of the line, or 90 % of the line lies along it, over more than twice buffer_m.
- `mode` ("along" | "barrier", optional): 'along', the default: kept off travelling along the roads matched. 'barrier': also kept from crossing them at grade, at a junction on them or a passage in plan NVDB records across them, such as an övergångsställe or a cykelpassage. A bridge or tunnel stays open. A barrier holds within 2 km of the ends, wider where the path goes further.
- `direction` ("both" | "along" | "against", optional): Which way travel on the roads matched is kept off: 'both', the default; 'along', the way the line is drawn, from its first position to its last; or 'against', the other way. A road matched one way stays open the other. With mode 'along' only: a barrier keeps off both ways.
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
- `facts` (array of [AvoidFact](https://api.kartpunkt.se/docs/shapes/AvoidFact.md), 0..11 items, optional): Facts to keep off, each once, by their key in GET /v1/facts: the flags 'fast_road', 'unlit_outside_built_up', 'ferry', 'motorway', 'built_up', 'lit'; the ranges 'speed_limit', 'road_class', 'traffic_safety_class'; the categories 'surface', 'road_owner'.
- `values` (array of [AvoidValue](https://api.kartpunkt.se/docs/shapes/AvoidValue.md), 0..10 items, optional): Roads to keep off by a fact's value, without drawing a line: every road of the car network with one of those numbers within 2 km of the ends, wider where the path goes further. With between, only that road between two points.
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# AvoidFact
> Index: https://kartpunkt.se/llms.txt
A fact to keep off, by its key among those GET /v1/facts marks avoidable. A flag is { fact: 'fast_road' }, with mode 'barrier' to keep from crossing those roads at grade too. A range is { fact: 'speed_limit', min: 70 }, inclusive, with min, max or both. A category is { fact: 'surface', values: ['gravel'] }.
- Either `{ fact, mode, id }`:
- `fact` ("fast_road" | "unlit_outside_built_up" | "ferry" | "motorway" | "built_up" | "lit")
- `mode` ("along" | "barrier", optional): 'along', the default: kept off travelling along the roads matched. 'barrier': also kept from crossing them at grade, at a junction on them or a passage in plan NVDB records across them, such as an övergångsställe or a cykelpassage. A bridge or tunnel stays open. A barrier holds within 2 km of the ends, wider where the path goes further.
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
- Or `{ fact, min, max, id }`:
- `fact` ("speed_limit")
- `min` (integer 5..150, optional): The lowest value kept off, inclusive: km/h as posted; gångfart and a road with no limit recorded are never in a range.
- `max` (integer 5..150, optional): The highest value kept off, inclusive: km/h as posted; gångfart and a road with no limit recorded are never in a range.
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
- Or `{ fact, min, max, id }`:
- `fact` ("road_class")
- `min` (integer 0..9, optional): The lowest value kept off, inclusive: functional class, 0 (most important) to 9.
- `max` (integer 0..9, optional): The highest value kept off, inclusive: functional class, 0 (most important) to 9.
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
- Or `{ fact, min, max, id }`:
- `fact` ("traffic_safety_class")
- `min` (integer 1..4, optional): The lowest value kept off, inclusive: TS-klass, 1 (mycket god) to 4 (låg).
- `max` (integer 1..4, optional): The highest value kept off, inclusive: TS-klass, 1 (mycket god) to 4 (låg).
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
- Or `{ fact, values, id }`:
- `fact` ("surface")
- `values` (array of "paved" | "gravel" | "unknown", 1..3 items): The values kept off, any of 'paved', 'gravel', 'unknown' ('unknown': where NVDB records none).
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
- Or `{ fact, values, id }`:
- `fact` ("road_owner")
- `values` (array of "state" | "municipal" | "private" | "unknown", 1..4 items): The values kept off, any of 'state', 'municipal', 'private', 'unknown' ('unknown': where NVDB records none).
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# AvoidValue
> Index: https://kartpunkt.se/llms.txt
A road kept off by its value, with no line drawn: { fact: 'road_number', values: ['E20', '47'], mode: 'barrier' }, or only between two points on it: { fact: 'road_number', values: ['113'], between: [{ lat, lon }, { lat, lon }] }.
- `fact` ("road_number"): A category fact from GET /v1/facts whose roads can be kept off by their value.
- `values` (array of string, max 16 chars, 1..20 items): The values to keep off: road numbers as signed, e.g. 'E20' or '47'. Case and spaces are ignored.
- `between` (array of [Point](https://api.kartpunkt.se/docs/shapes/Point.md), 2..2 items, optional): Two points on the road: only the road between them is kept off. Each meets the road within 200 m. Kept off is the shortest connected run between them in whole pieces, with the road's pieces beside it within 50 m. One value only, the points at most 30 km apart. Left out, the whole road near the ends.
- `mode` ("along" | "barrier", optional): 'along', the default: kept off travelling along the roads matched. 'barrier': also kept from crossing them at grade, at a junction on them or a passage in plan NVDB records across them, such as an övergångsställe or a cykelpassage. A bridge or tunnel stays open. A barrier holds within 2 km of the ends, wider where the path goes further.
- `id` (string, max 64 chars, optional): Your own id for it, echoed in blocked_by.
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# Preference
> Index: https://kartpunkt.se/llms.txt
A fact to prefer to keep off: { fact: 'fast_road', cost: 2 }. The path whose cost is least is answered, which can be longer than the shortest.
- `fact` ("fast_road" | "unlit_outside_built_up" | "ferry" | "motorway" | "built_up" | "lit"): A flag fact from GET /v1/facts that a path can be kept off: every road or path where the fact holds.
- `cost` (number <= 10, optional, default 2): What a metre with the fact costs against 1 for every other metre: more than 1, at most 10 (default 2).
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# FactSummary
> Index: https://kartpunkt.se/llms.txt
A fact summed over a path: metres per value for a category, {min, max, unknown_m} for a measure, metres for a flag, or a count.
- `min` (number | null)
- `max` (number | null)
- `unknown_m` (number)
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# SnapResult
> Index: https://kartpunkt.se/llms.txt
Where a point sent met the network.
- `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.
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Segment
> Index: https://kartpunkt.se/llms.txt
A stretch of a path: its metres, its road reference, the facts asked for and its line.
- `length_m` (number): Metres of the path on this stretch.
- `ref` ([RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md) | null): The stretch of road, the same in every snapshot; null on a road with no NVDB link. The synthetic dataset has links of its own.
- `facts` (object): Each requested fact's value on this stretch.
- `geometry` (object | string, optional)
- `type` ("LineString")
- `coordinates` (array of array of number)
- `destination_only` (true, optional): true on a stretch closed to the vehicle's through traffic, driven only because an end or a via lies in that closed zone: a first or last stretch. Closed means genomfart förbjuden, or a ban that exempts the vehicle's own errand, such as deliveries for a lorry or passengers set down for a car or a bus. Left out elsewhere.
Used by: [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md), [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Path
> Index: https://kartpunkt.se/llms.txt
A path between two ends, and why there is none where there is none.
- `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.
Used by: [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# TracePoint
> Index: https://kartpunkt.se/llms.txt
A GPS fix: a WGS84 coordinate, with its time when known.
- `lat` (number -90..90): Latitude, WGS84 decimal degrees.
- `lon` (number -180..180): Longitude, WGS84 decimal degrees.
- `time` (string, date-time (RFC 3339, with offset), optional): When the fix was recorded (RFC 3339 with an offset).
Used by: [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# AddressIdPlace
> Index: https://kartpunkt.se/llms.txt
A place given by its address id instead of coordinates.
- `address_id` (string, max 100 chars, pattern `^se\.(lm|syn)\.[A-Za-z0-9-]{1,80}$`): An address id from the register, as an earlier answer's id gave it (POST /v1/addresses/validate, GET /v1/geocode): 'se.lm.' and Lantmäteriet's object identity for a kp_live_ key, 'se.syn.' and a code for a kp_test_ key. Resolved to the address's point and street to meet the network; the answer never holds them.
- `as_of` (string, date-time (RFC 3339, with offset), optional): Resolve the version of the address served at this instant (RFC 3339 with offset), e.g. the date your plan was made, so the same plan answers the same again; left out, the version served now. Versions are kept 24 months.
Used by: [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# Unreachable
> Index: https://kartpunkt.se/llms.txt
Why a task no vehicle takes is out of reach, and where its place met the network.
- `place` ("stop" | "pickup" | "delivery"): Which of the task's places is out of reach: the stop's, or the shipment's pickup or delivery.
- `met` (object | null): Where the place met the network, as POST /v1/snap answers it, with the road's RoadRef. With engine_not_met, the road the engine would not use; with avoid, the kept-off road the place lies on, if any. null with no_road_near.
- `point` ([Point](https://api.kartpunkt.se/docs/shapes/Point.md)): Where the point meets the network: the nearest point of the road.
- `snap_m` (number): Straight-line metres from the point sent to where it meets the network; never part of a distance_m.
- `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.
- `ref` ([RoadRef](https://api.kartpunkt.se/docs/shapes/RoadRef.md) | null): The road's reference at that point, its two measures equal; null on a road with no NVDB link.
- `road` (object): The road the point meets.
- `name` (string | null): The road's name in NVDB; null where it has none.
- `number` (string | null): The road's number (vägnummer): 'E6' for a europaväg, else digits; null where it has none.
- `cause` ("no_road_near" | "engine_not_met" | "no_path" | "avoid"): 'no_road_near': no road of the profile within 500 m. 'engine_not_met': met on the road in met, which the engine does not use for this vehicle; Kartpunkt's error. 'no_path': met, but no path joins it to a vehicle's start, or in a checked plan to a neighbouring visit. 'avoid': only roads avoid keeps off reach it, or it lies on one.
Used by: [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Unresolved
> Index: https://kartpunkt.se/llms.txt
With unassigned_address_id: which place's address id resolved to no address served, and why. Absent with every other reason.
- `place` ("stop" | "pickup" | "delivery"): Which of the task's places: the stop's, or the shipment's pickup or delivery (the first that failed).
- `cause` ("closed" | "unknown"): 'closed': a delivery of the register closed the address on or before the instant asked (as_of, else now). 'unknown': no version of the id in this key's dataset then (never issued, past the 24 months kept, opened only later, or the other dataset's).
Used by: [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md), [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md), [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# Job
> Index: https://kartpunkt.se/llms.txt
A job: its operation, status and times, and its result once succeeded. One form per operation, by `operation`.
One of 9 forms, by `operation` ("route", "distances", "matrix", "reach", "trace", "nearest", "optimize", "check_plan", "address_list").
Every form:
- `id` (string): The job's id: read it with GET /v1/jobs/{id}.
- `status` ("queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"): Where the job is: 'queued' or 'running' until it ends 'succeeded', 'failed' or 'cancelled', then 'expired' once its result is deleted at expires_at.
- `progress` (object | null): How far the running job has come. null until it first reports.
- `done` (number): Units of work done.
- `total` (number): Units of work in all.
- `created_at` (string): When the job was submitted, RFC 3339.
- `started_at` (string | null): When it last started running, RFC 3339; null until it has.
- `finished_at` (string | null): When it ended, RFC 3339; null until it has.
- `expires_at` (string | null): When the result is deleted, RFC 3339; null until the job ends. Its sources' policies set how long it is kept (GET /v1/sources).
- `error` (object | null): Why a failed job failed. null in every other status.
- `code` (string): A code from the error table.
- `message` (string): That code's message, saying what to do next.
By `operation`:
- `operation`: "route":
- `result` (object): the 200 answer of [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "distances":
- `result` (object): the 200 answer of [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "matrix":
- `result` (object): the 200 answer of [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "reach":
- `result` (object): the 200 answer of [POST /v1/reach](https://api.kartpunkt.se/docs/operations/measureReach.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "trace":
- `result` (object): the 200 answer of [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "nearest":
- `result` (object): the 200 answer of [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "optimize":
- `result` (object): the 200 answer of [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "check_plan":
- `result` (object): the 200 answer of [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md). The operation's answer once the job has succeeded; null in every other status.
- `operation`: "address_list":
- `result` (object | null): The operation's answer once the job has succeeded; null in every other status.
- `kind` ("validate" | "geocode" | "reverse"): The kind sent.
- `rows` (array of object): One row per row sent, in the order sent.
- `id` (string | null): The id sent with the row; null where none was.
- `status` ("matched" | "ambiguous" | "unmatched"): 'matched': the row is one address, in address. 'ambiguous': it fits several addresses; send its postnummer or postort. 'unmatched': no address is the row. The recipe Your own address list says when each applies.
- `address` ([Address](https://api.kartpunkt.se/docs/shapes/Address.md) | null): The address the row is, when status is 'matched'; otherwise null, never a guess or another address.
- `issues` (array of "street_not_found" | "number_not_found" | "letter_not_found" | "postcode_mismatch" | "locality_mismatch" | "ambiguous"): For validate, what POST /v1/addresses/validate says is wrong with the row (empty when it is valid); for geocode and reverse, always empty.
- `summary` (object): Your own rows counted by status: how many of them match an address. A count of your list, never of the register.
- `rows` (integer)
- `matched` (integer)
- `ambiguous` (integer)
- `unmatched` (integer)
- `provenance` ([Provenance](https://api.kartpunkt.se/docs/shapes/Provenance.md))
Used by: [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md), [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md), [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md).
---
# JobCheck
> Index: https://kartpunkt.se/llms.txt
A job checked and not queued (dry_run): a submit of the same body now would be accepted.
- `operation` ("route" | "distances" | "matrix" | "reach" | "trace" | "nearest" | "optimize" | "check_plan" | "address_list"): The job's operation, as sent.
- `dry_run` (true): The job was checked, not queued: no job was made.
- `accepted` (true): A submit of the same body now passes every check made before the queue: the job's limits, its network and profile and, for optimize and check_plan, each vehicle's start and end on a road. Not checked: quotas (the submit may be 429 quota_exceeded) and idempotency_key (a key already used answers its job, or 409 idempotency_conflict).
- `provenance` ([Provenance](https://api.kartpunkt.se/docs/shapes/Provenance.md)): The network snapshot the job would run on if submitted now, or the one network names, and its source. No network, sources empty and X-Data-Attribution empty, for address_list, which reads no register when checked, and when no network is named and none is served yet.
Used by: [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md).
---
# Errors and retries: what to do with each refusal
> Index: https://kartpunkt.se/llms.txt
Every error is `{ error, message, param?, doc_url }`. `error` is a stable
code, `message` says what to do next, and `doc_url` is the code's page.
Act on the status and code:
- 400 `invalid_request`: fix the field `param` names, by its path
(`origins.0.point.lat`), and the rule the message gives. Sending the same
body again gets the same answer.
- 401 `unauthorized`: the key is missing or wrong. 403
`live_access_required` or `use_not_permitted`: the account or the use is
not allowed. Stop and tell the user what the message says.
- 404 `network_unavailable`: no snapshot matches. Pick one that
`GET /v1/networks` lists, or leave `network` out.
- 410 `network_pruned`: the snapshot was deleted, and nothing answers on it
any more, as a job or otherwise. Leave `network` out to answer on the
active one, and store that answer's `provenance.network.id` instead.
- 409 `network_not_active`: the snapshot is no longer served, so send the
same input to `POST /v1/jobs`. A job that fails `snapshot_engine_unavailable`:
the graph was built for an earlier engine, so leave `network` out.
409 `profile_unavailable`: the snapshot was built without
the data the profile needs; use another profile or a newer snapshot.
- 409 `idempotency_conflict`: the key was used for another input. Send a
new `idempotency_key`.
- 413 `too_large`: the input is above the synchronous limits or the body
limit on the operation's page. Send the same input to `POST /v1/jobs` and
read `GET /v1/jobs/{id}?wait=30`; split it only where it is above the job
limits too.
- 429 `quota_exceeded` with `Retry-After` of 60 seconds or less: a
per-minute limit. Wait that many seconds, then send the call again.
- 429 with a longer `Retry-After`: the daily limit, which resets at 00:00
UTC, or a monthly quota, which resets at 00:00 UTC on the 1st. Stop, show
`message`, and resume after that time rather than sleeping in a loop.
- 429 without `Retry-After`: no wait helps, since the account has no plan
with that kind of call. Tell the user what the message says; a plan is
ordered by the account holder, never by you.
- 503 `routing_unavailable` with `Retry-After`: one calculation runs at a
time. Wait that many seconds and send the same call again. Without
`Retry-After` the road graph is not built yet: try again later.
- 503 `live_data_unavailable`: use a `kp_test_` key until live data opens.
500 `internal_error`: retry after a few seconds, and if it keeps failing,
contact hej@kartpunkt.se.
- A 200 is an answer whatever its `status`, so never retry it.
`avoided_road_required` comes with `blocked_by`, each `avoid` entry in the
way as `{ field, index, id }`: show it to whoever decides rather than
guessing.
A wrapper in TypeScript, for your backend:
```ts
type KartpunktError = { error: string; message: string; param?: string; doc_url: string };
export async function kartpunkt(path: string, body: unknown): Promise {
for (let attempt = 0; ; attempt++) {
const res = await fetch(`https://api.kartpunkt.se${path}`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (res.ok) return (await res.json()) as T;
const err = (await res.json()) as KartpunktError;
const wait = Number(res.headers.get("Retry-After") ?? "NaN");
// A short wait helps on 429 and on 503 routing_unavailable; anything else is the caller's to act on.
const retry = (res.status === 429 || err.error === "routing_unavailable") && wait <= 60 && attempt < 3;
if (!retry) throw Object.assign(new Error(err.message), { status: res.status, ...err });
await new Promise((r) => setTimeout(r, wait * 1000));
}
}
```
The caller sends a `too_large` input to `POST /v1/jobs`, fixes `param` on
`invalid_request`, and shows `message` for the rest.
## Operations
- [GET /v1/networks](https://api.kartpunkt.se/docs/operations/listNetworks.md): Network snapshots.
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
- [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md): A job, with its result once done.
---
# Keeping paths off roads: avoid, barriers and closures
> Index: https://kartpunkt.se/llms.txt
`POST /v1/route`, `POST /v1/distances` and `POST /v1/matrix` take `avoid`,
`{ stretches, facts, values }`. Keep the lists in your own system and send
them with each call: nothing is stored here. The answer's path keeps off
everything sent, within the Limits on each operation's page.
- `stretches`: GeoJSON LineStrings drawn along the road, for a closure or
roadworks. A piece of road runs between junctions, or to where the limit
or name changes. A piece is kept off when the line runs along at least
90 % of it, or at least 90 % of the line runs along it. So draw the part
that is closed: a street the line only crosses is not kept off.
- `facts`: any fact `GET /v1/facts` marks `avoidable`, in its form.
- A flag is `{ fact: "fast_road" }`, and so are `lit`, `ferry`,
`motorway`, `built_up` and `unlit_outside_built_up`. `prefer` takes a
flag.
- A range is `{ fact: "speed_limit", min: 70 }`, and so are `road_class`
0-9 and `traffic_safety_class` 1-4: `min`, `max` or both, inclusive.
- A category by value is `{ fact: "surface", values: ["gravel"] }`, and so
is `road_owner`, with `unknown` where NVDB records none.
- `values`: a road by number, `[{ fact: "road_number", values: ["E20"] }]`.
- The path without `avoid` is the alternative `unrestricted`. On
`/v1/route` ask for it with `alternatives: ["unrestricted"]`: a closure's
detour is the one against the other. On `/v1/distances` it is answered
with `avoid` unless you send `alternatives: []`.
- Each result is then the path that keeps off all of it, and
`alternatives.unrestricted` the shortest: facts and paths, never a
verdict. Each path's `avoid_m` is its metres on each stretch.
- `snap_m` and `destination_snap_m` are where the path starts and ends.
Without a path they are where each end met the network, and `null` only
for an end that met none. `street_matched` and
`destination_street_matched` sit beside them: a path states its own ends,
and no `origin` or `destination` block repeats them.
- The answer's `avoid.stretches` says which stretches matched a road;
`matched: false` means none lies along it any more: redraw it.
- `avoided_road_required` means no path keeps off it all, also where the
origin or the destination meets the network on such a road. `blocked_by`
names each entry in the way as `{ field, index, id }`, where `field` is
`stretches`, `facts` or `values`: show it to whoever decides rather than
guessing.
- When the road a destination lies on is kept off, send the destination's
entrance on a path or side road, else every result is
`avoided_road_required`. An end on a road a fact keeps off needs the point
where the trip really starts.
- Barrier: where a path must neither run along nor cross a road at grade,
as with a closed road, send the entry with `mode: "barrier"`, e.g.
`avoid: { values: [{ fact: "road_number", values: ["E20"], mode: "barrier" }] }`.
Junctions and passages in plan across it are then kept off too, and
bridges and tunnels stay open. An origin beside the road is met on its own
side: the path's `snap_m`.
- Closure between two places: where a road is closed only between two
places ("väg 113 mellan Gryby och Gårdstånga"), send one number with the
two places on the road as `between: [{ lat, lon }, { lat, lon }]`. Only
the road's pieces between them are kept off, each piece whole, both
carriageways and a roundabout's arcs included.
- The answer's `avoid.values` shows, per entry, where each point met the
road (`snap_m`), the stretch as `roads` (RoadRefs) and its `length_m`.
Check it against the closure before relying on the path. Where a point
meets no such road within 200 m, or no connected stretch joins them, the
call is refused: draw the stretch as a line in `avoid.stretches` instead.
- `/v1/matrix` with `avoid` answers each cell as the distances call to its
destination answers that result.
## Operations
- [GET /v1/facts](https://api.kartpunkt.se/docs/operations/listFacts.md): The facts catalog.
- [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md): A route, with facts along it.
- [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md): Distances from many origins to their destinations.
- [POST /v1/matrix](https://api.kartpunkt.se/docs/operations/buildMatrix.md): An origins × destinations matrix.
---
# Drawing a route: segments and geometry
> Index: https://kartpunkt.se/llms.txt
- Send `origin`, `destination`, any `via` and a `profile` to
`POST /v1/route`, such as for a trip by car or a walk.
- When `status` is `ok`, `segments` holds the route in order from origin to
destination. Draw them in that order, each with its `geometry`, its
`length_m`, its road reference `ref` and the facts asked for.
- `geometry` is GeoJSON by default; send `geometry: "polyline6"` or
`"none"` for another form. The answer's `geometry` is also the whole route
as one line, and `/v1/trace` answers the matched path's `geometry` the
same way.
- Any other status answers `segments: []` and `geometry: null`: draw
nothing and show the status. Read an unknown status the same way.
- `duration_s` is free-flow at the speed limits, 5 km/h on foot, not a
prediction.
- For a closure's detour, send `avoid` with
`alternatives: ["unrestricted"]` and draw the one against the other: see
the recipe
[Keeping paths off roads](https://api.kartpunkt.se/docs/recipes/keeping-paths-off-roads.md).
## Operations
- [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md): A route, with facts along it.
---
# Address ids: store the id, read the address when you need it
> Index: https://kartpunkt.se/llms.txt
- What the id is: every address result's `id` is `se.lm.` and Lantmäteriet's
own object identity of the address (`belagenhetsadress_objektidentitet` in
the register Belägenhetsadress, a UUID). Kartpunkt adds only the prefix:
the id is the register's, not Kartpunkt's, so a record you key by it does
not tie you to Kartpunkt. A `kp_test_` key's ids are `se.syn.` and a code,
from the synthetic dataset, and are never a `kp_live_` key's. A street
hit's id (`street:…`) is not an address id.
- When it changes or closes: the identity is permanent. A change
Lantmäteriet makes to an address it keeps (a new postnummer, a moved
point, any field) is a new version under the same id, from the weekly
delivery that makes it. An address the register removes, or no longer
holds as current, is closed: no answer returns it, and the register names
no successor. The same id can be served again later, so mark a closed id,
do not delete it. Kartpunkt keeps closed versions for up to 24 months.
- Read one: `POST /v1/addresses/lookup` (`lookupAddress`) with
`{"id": "se.lm.…"}` answers `address`, the address served now, in the
shape every address answer has. Add `"as_of": ""` (at most 24
months back) for the version served at that instant, e.g. when your
decision was made. One id per call: one address is a single answer, which
is what Lantmäteriet's terms allow; it counts as one address toward the
daily address limit. 404 `address_closed` means a delivery closed it (the
message gives the date): find the address again with
`POST /v1/addresses/validate` or `POST /v1/geocode` and store its new id.
404 `address_unknown` means no such id in this key's dataset.
- Keep a register current: weekly at most, send your ids 20 at a time to
`POST /v1/addresses/changes` with the stored `updated` as `since` (the
rule above), replace what you stored on `changed` and mark it on `closed`.
To match a whole register again (at onboarding, or rows whose ids closed),
submit an `address_list` job with `"kind": "validate"` (`POST /v1/jobs`):
each matched row comes back with its address and its id.
- Reproducibility: a lookup's `provenance.sources[].as_of` is the instant the
address was read at, and `updated` the delivery it rests on. Store them
with what you computed from the address; sending that `as_of` again, on a
lookup or on an optimize stop's `address_id`, reads the same version, for
as long as the 24 months of history keep it.
- Only ids an answer gave you: there is no list, range or feed of ids, and
looking ids up one after the other to copy the register is harvesting,
which is not allowed.
## Operations
- [POST /v1/geocode](https://api.kartpunkt.se/docs/operations/searchAddresses.md): Search and autocomplete locations.
- [POST /v1/addresses/validate](https://api.kartpunkt.se/docs/operations/validateAddress.md): Validate an address.
- [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md): What changed for stored addresses.
- [POST /v1/addresses/lookup](https://api.kartpunkt.se/docs/operations/lookupAddress.md): One address by its id.
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
---
# Many origins to a few destinations: distances along the roads
> Index: https://kartpunkt.se/llms.txt
For example homes to a few depots, service addresses to the care units
that serve them, or homes to a school.
- Send coordinates only, never who lives there. Leave out anyone with
skyddade personuppgifter (protected identities).
- Send the destinations as `destinations: [{ id, point }]` (a few, each
its entrance, with `street` when you know it) and the origins as `origins`
to `POST /v1/distances`, on the profile your rule measures: `walk`, or
`car_shortest` for the shortest legal road distance by car. A list above
the synchronous limits goes as a job, never as many synchronous calls.
- Roads your rules keep off go in `avoid.stretches` and the facts they keep
off in `avoid.facts`, sent with each call from your own system. A road
that must neither be travelled along nor crossed goes with
`mode: "barrier"`, and a closure written as "väg 113 mellan Gryby och
Gårdstånga" in `avoid.values` with `between`: see the recipe
[Keeping paths off roads](https://api.kartpunkt.se/docs/recipes/keeping-paths-off-roads.md).
Each result is then the
path that keeps off them, and `alternatives.unrestricted` the shortest
one. When a destination's own road is kept off, send its entrance on a
path or side road as its point, else every result is
`avoided_road_required`. Show `blocked_by` to whoever decides.
- `results` answers every origin in the order sent, one entry per
destination, each origin's together in the order of `destinations`:
`results[origin × destinations + destination]`. Each entry has its own
`status`, `id`, `destination_id` and `destination_index`; match results
to origins by `id` and `destination_id`, not by position alone.
- Each pair is answered exactly as a call to that destination alone,
`avoid` and its alternative included. The answer's `destinations` says
where each destination met the network.
- To store and show each path, send `geometry: "geojson"` or `"polyline6"`
(default `"none"`). Each `ok` path then carries its line from where the
origin meets the network to where the destination does, simplified to
within 1 m of the roads, the alternative's too. It is `null` where there
is no path.
- Each origin's nearest bus stops: send the same origins as `points` to
`POST /v1/nearest` with `kind: "bus_stop"` on `walk`, a second call beside
the distances, with `limit` and `max_distance_m` when the defaults do not
fit. Each result has `origin` (`snap_m`, `street_matched`) and `nearest`,
nearest first, each with `kind`, `id`, `name`, `point`, `distance_m` and
`duration_s`. It is `[]` when none is within reach or the point met no
road.
- The facts are metres and counts NVDB records; whether a route is safe or
a distance qualifies is your assessment, never the API's.
- Store each distance with its profile, its status and the answer's
`provenance`. Its `provenance.network.id` and NVDB date document and
date the distance with the network it was computed on. The same call with
`network: { id }` answers the same while that network is served.
## Operations
- [POST /v1/distances](https://api.kartpunkt.se/docs/operations/measureDistances.md): Distances from many origins to their destinations.
- [POST /v1/nearest](https://api.kartpunkt.se/docs/operations/findNearest.md): What is nearest each point along the network.
---
# Trip logs (körjournal): a trip's start and end address
> Index: https://kartpunkt.se/llms.txt
- At a trip's start and end, call `POST /v1/reverse-geocode` with the
vehicle body (`"radius_m": 5000, "distinct": "street", "limit": 3`) and
store what the recipe
[A vehicle's position](https://api.kartpunkt.se/docs/recipes/a-vehicle-s-position.md)
chooses.
- To draw the trip, call `POST /v1/route` between its ends, or
`POST /v1/trace` with its GPS points for the distance it travelled.
## Operations
- [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md): The nearest addresses to a point.
- [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md): A route, with facts along it.
- [POST /v1/trace](https://api.kartpunkt.se/docs/operations/matchTrace.md): The roads a GPS trace travelled, with facts.
---
# Route optimization (ruttoptimering): vehicles over stops
> Index: https://kartpunkt.se/llms.txt
Kartpunkt knows vehicles, stops and shipments, never pupils, bins or
patients: what a stop stands for, and every rule about people, stays in
your system. The cases below are ways to express a problem in those terms.
- Send coordinates only, never who lives there. Leave out anyone with
skyddade personuppgifter (protected identities).
- Send the vehicles (`start`, `end`, `capacity` in dimensions you name,
`time_window`, `skills`), the stops (`at`, what is picked up or delivered
there, `service_s`, `time_windows`) and shipments picked up at one place
and set down at another to `POST /v1/optimize`, on `car_fastest`,
`car_shortest`, `walk` or `hgv` (each vehicle's own `dimensions`). A
vehicle with no `end` ends at its last stop.
- Sync or job. `POST /v1/optimize` answers at once up to 25 stops, 12
shipments, 5 vehicles and 30 distinct locations (stops, shipments'
pickups and deliveries, vehicles' starts and ends; a depot sent for every
vehicle counts once), with no avoid entry in `mode: "barrier"`. More
locations, or a barrier, is refused 413 `too_large` naming the job; a
longer list of stops, shipments or vehicles is refused 400
`invalid_request` naming the field. Either way, send the same input to
`POST /v1/jobs` as `{"operation": "optimize", "input": {…}}`,
up to 500 stops, 250 shipments, 50 vehicles and 600 locations, and read
`GET /v1/jobs/{id}?wait=30` until `status` is `succeeded`; `result` is the
synchronous answer's shape. Larger still: split the plan by area.
- Address ids instead of coordinates: a stop's or a shipment's `at` may be
`{"address_id": "se.lm.…"}` (an id an earlier address answer gave you;
a `kp_test_` key's are `se.syn.…`), with `as_of` (RFC 3339) to plan on
the version served then. Up to 1 000 ids a call, 50 000 a job. The answer
holds your ids, routes and times, never the address: no point or street,
`street_matched` null and `unreachable.met` null for such a place. Each
distinct id that resolves is an address read, and the call a live call
(`kp_live_`) whether or not any resolves.
`provenance.sources[].as_of` is the instant the ids were resolved at (a
job's: when it was submitted); send it as `as_of` to plan the same again.
An id no address is served under is answered `unassigned_address_id`
(`unresolved.cause` `closed` or `unknown`), not refused. Vehicles' `start`
and `end` stay coordinates.
- `objective`: `"duration"` (the default) makes the vehicles' driving time
together least; `"vehicles_then_duration"` uses the fewest vehicles
first, then the least driving time among those plans. Use it to find the
smallest fleet that serves the stops.
- Each visit's `duration_s` and `distance_m` from the previous visit equal
`POST /v1/route`'s for the same leg on the same profile and network, so a
leg drawn with `/v1/route` (or `geometry` on the plan) matches the plan.
They are free-flow times: no traffic.
- `hgv` (a bus or a lorry, with `dimensions`) is not experimental: its
restriction tests pass on the national NVDB delivery, and answers on it
carry `provenance.network.experimental: false`. Two known gaps: a lorry
ban NVDB records one way only is kept off in both directions, and time
windows on a restriction are reported, not routed around. Read
`GET /v1/profiles` for the profile's current state.
- School transport: name a dimension for seats (`{"seats": 7}`, and
`{"wheelchair": 1}` beside it where needed). Each vehicle starts at its
depot with `capacity` in the same dimensions and its `time_window` ends
when the school starts. `"vehicles_then_duration"` finds the fewest
vehicles. Two ways to send the passengers, never who they are:
- Stops with counts, when passengers board at shared stops
(hållplatser) and ride time is checked per run: each stop's count of
boarders as its `pickup`, each vehicle `end`ing at the school. The
plan knows no passenger, so check ride time in your own system from
the visits' `arrival` times.
- Shipments, when each passenger's own ride time is a rule: one shipment
per passenger, or per group with the same pickup and school. Each is
picked up at home or at the stop and delivered at the school, with
`amount` in seats and `max_ride_s`. A shipment over it is answered
`unassigned_max_ride`. Shipments count toward the lower limits (12
synchronously, 250 as a job).
- Waste collection: capacity in tonnes, counted in whole kilograms since
amounts are whole numbers (`{"kg": 12000}` for 12 t). Each bin or
container is a stop with its `pickup` in kg and its `service_s`, and the
depot is each truck's `start` and `end`. The skill `crane` goes on the
trucks that can empty underground containers and on those stops. A
closed bridge goes in `avoid.stretches`.
- Haulage (åkerier): lorries as vehicles on `hgv`, each with its own
`dimensions` (weight, height, length) and `capacity`
(`{"kg": 18000, "pallets": 33}`), the day's loads as shipments from
pickup to delivery with their `amount` and each dock's `time_windows`,
and the skill `tail_lift` on the lorries that have one and on the loads
that need it. A road whose recorded limit is below a lorry's dimensions
is not used for that lorry.
- Every stop or shipment no vehicle takes is in `unassigned` with
`reason`, a code whose page at `/errors/{code}` says what to change:
- `unassigned_address_id`: its address id is closed or unknown on the
date asked. Check it with `POST /v1/addresses/changes`, or send the
place's coordinates.
- `unassigned_unreachable`: no road of the profile reaches it (or only
roads `avoid` keeps off; `blocked_by` names them). Move the point onto
a road, or lift what `blocked_by` names.
- `unassigned_skills`: no vehicle has every skill it names. Give the
skill to a vehicle, or send the task with fewer skills.
- `unassigned_capacity`: it needs more than any vehicle carries in some
dimension. Send a larger vehicle, or split the task.
- `unassigned_time_window`: no vehicle can be there within its windows
and its own `time_window`. Widen a window, or send a vehicle available
earlier or later.
- `unassigned_max_travel`: no vehicle reaches it within its
`max_travel_s`. Raise it, or send a vehicle that starts nearer.
- `unassigned_max_ride`: a shipment rode longer than its `max_ride_s`
and was taken out. Raise it, plan it on its own, or add vehicles.
- `unassigned_no_room`: a vehicle could take it alone, but not beside its
other tasks. Add a vehicle, widen windows or capacities, or plan the
unassigned tasks in a second call.
- Join the answer to your own records by the ids you sent, and store the
plan with `provenance.network.id`.
## Operations
- [GET /v1/profiles](https://api.kartpunkt.se/docs/operations/listProfiles.md): The routing profiles.
- [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md): What changed for stored addresses.
- [POST /v1/route](https://api.kartpunkt.se/docs/operations/planRoute.md): A route, with facts along it.
- [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md): Plan vehicles' routes over stops and shipments.
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
- [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md): A job, with its result once done.
---
# Check or compare a plan
> Index: https://kartpunkt.se/llms.txt
`POST /v1/optimize/check` takes a plan you already have and says whether it
holds, in the very shapes `POST /v1/optimize` answers. Use it when a person
edits a route, moving a stop to another vehicle or reordering the visits,
and when you compare your current routes with an optimized plan.
- Send the same vehicles, stops and shipments as to `POST /v1/optimize`
(no `objective`), and `routes`: per vehicle,
`{"vehicle_id": "…", "visits": [{"kind": "stop", "id": "…"}, {"kind": "pickup", "id": "…"}, …]}`
in the order it drives them, its start and end left out. An optimize
answer's visits without `start` and `end`, `kind` and `id` only, are
such a plan: edit it and send it back.
- Each route is timed and loaded as optimize times its own plan, on the
same network, so a plan optimize made checks to the same arrivals,
metres, loads and `totals`. To show a saving, send the current routes
here and the same input without `routes` to `POST /v1/optimize`, with the
same `network: {"id": …}`, and compare the two `totals`.
- `feasible` is `true` when nothing is broken and every task is in a
route. Otherwise `violations` says what breaks where, each with
`vehicle_id`, `visit` (the index in that route's `visits`), `id` and
`over` (seconds, or the amount over capacity in `dimension`):
- `plan_capacity`: the load leaving the visit is over the vehicle's
capacity. Move a pickup to another vehicle, or deliver first.
- `plan_time_window`: the vehicle arrives after the task's windows have
closed. Visit it earlier, or give it to another vehicle.
- `plan_vehicle_time_window`: the vehicle finishes after its
`time_window`. Move tasks to another vehicle.
- `plan_skills`: the vehicle lacks a skill the task names.
- `plan_max_travel`: the vehicle drives longer than its `max_travel_s`.
- `plan_shipment_order`: a shipment's pickup and delivery are not both
on one vehicle with the pickup first.
- `plan_max_ride`: a shipment rides longer than its `max_ride_s`.
- `unassigned` lists every stop and shipment in no route, each with a
reason. `unassigned_not_in_plan`: your plan leaves it out.
`unassigned_address_id`: its address id resolves to no address, so its
visits are taken out and the rest of the route is checked as sent.
`unassigned_unreachable`: no road joins it to the visit before, so it is
left out of the route.
- A broken plan is answered 200, never refused. A plan naming a vehicle,
stop or shipment you did not send, a visit whose `kind` is not its
task's, a task visited twice or a vehicle with two routes is refused 400
`invalid_request`, naming the field.
- Sizes are optimize's; a larger plan goes to `POST /v1/jobs` as
`{"operation": "check_plan", "input": {…}}`. One calculation per stop and
per shipment sent.
## Operations
- [POST /v1/optimize](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md): Plan vehicles' routes over stops and shipments.
- [POST /v1/optimize/check](https://api.kartpunkt.se/docs/operations/checkPlan.md): Check a given plan of vehicles' routes.
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
---
# Running a job: submit, wait, read, cancel
> Index: https://kartpunkt.se/llms.txt
A call above its operation's synchronous limits runs as a job: the same
input, answered later. So does a call on a network snapshot no longer
served, while its graph still runs on the current engine.
- Submit `POST /v1/jobs` with `{"operation": "…", "input": , "idempotency_key": ""}`. The operation is
one of `route`, `distances`, `matrix`, `reach`, `trace`, `nearest`,
`optimize`, `check_plan` and `address_list`, which matches your own
address list and has no synchronous call. It answers 202 with the job,
`queued`.
- Send the same `idempotency_key` when you retry after a timeout or a
network error. The same key with the same input answers the job already
made; with another input it is 409 `idempotency_conflict`.
- Read `GET /v1/jobs/{id}?wait=30` from your backend, in a loop. The
answer comes the moment the job ends, or after 30 s with the job still
`queued` or `running`. Then call it again: never poll every few hundred
milliseconds.
- Show `progress` meanwhile. A `wait` above 30 is 400 `invalid_request`.
Kartpunkt calls nothing back: there are no webhooks.
- Without `wait`, poll: first after 2 s, then double the wait after each
`queued` or `running` answer, up to 30 s. A job not yet ended carries
`Retry-After`, the seconds before the next read.
- `succeeded`: `result` is the operation's answer, in the synchronous
call's shape. `failed`: `error` is `{ code, message }` from the error
table; act on the message. `cancelled`: neither.
- Copy what you need before `expires_at`. The input is deleted when the
job ends, and the result at `expires_at`; the job then answers `expired`,
and later 404 `job_not_found`. `GET /v1/sources` gives each source's
`result_ttl_hours`.
- `DELETE /v1/jobs/{id}` cancels. A queued job is `cancelled` at once; a
running one stops at its next unit of work, so read it with `wait` until
it has ended. A job is metered when it is submitted, so cancelling it
gives no calculations back.
## Operations
- [GET /v1/sources](https://api.kartpunkt.se/docs/operations/listSources.md): The data sources and their terms.
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
- [GET /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/getJob.md): A job, with its result once done.
- [DELETE /v1/jobs/{id}](https://api.kartpunkt.se/docs/operations/cancelJob.md): Cancel a job.
---
# A vehicle's position
> Index: https://kartpunkt.se/llms.txt
Which street a vehicle is on, from one GPS point. The API hands you one
candidate per street and the road the point is on; your code chooses.
- Call `POST /v1/reverse-geocode` with
`{"point": {"lat": …, "lon": …}, "radius_m": 5000, "distinct": "street", "limit": 3}`.
The answer holds the nearest address of each of the three nearest
streets, nearest first. 5000 m reaches the nearest address from a rural
road.
- At a junction the nearest door is often on the cross street, so one
result is not enough.
- Choose in this order:
1. The first address in `results` whose `street` equals `road.name`,
when it is within 50 m.
2. Else, when `road.fallback` is `true`, the road: its `name`, else its
`road_number`, with `road.municipality_code`. It has no `id`, so
store it as text.
3. Else `results[0]`.
- What you know can decide instead, such as the street the vehicle was on
at its previous point.
- `road` is the nearest road a car can use with a name or a number. From a
cycle path or an unnamed private road it is the nearest named road, at
its true distance. It never changes `results`.
- `road.municipality_code` is the kommun NVDB registers the road in. Near a
kommun border it can differ from `results[0].municipality_code`; store
the road's with the road.
- With a `kp_test_` key the addresses are synthetic and `road` is NVDB's
real road, so `road.name` rarely equals a `results[].street`. The choice
then usually ends at the road or at `results[0]`.
## Operations
- [POST /v1/reverse-geocode](https://api.kartpunkt.se/docs/operations/reverseGeocode.md): The nearest addresses to a point.
---
# Keeping stored addresses current
> Index: https://kartpunkt.se/llms.txt
- Store each address's `id` and `label` with the address source's
`provenance.sources[].updated` from the same answer.
- At most weekly, since deliveries are weekly, send the stored ids as `ids`
and that date as `since` to `POST /v1/addresses/changes`. Above the
synchronous limit, split the ids into several calls.
- Act on each result's `status`:
- `unchanged`: keep what you stored.
- `changed`: replace what you stored with `address`. `changed_fields`
names what differs; treat a name you do not know as a field you do not
store.
- `closed`: mark it closed, and do not delete it. The same `id` can be
served again, and a later call then answers `changed` or `unchanged`.
- `unknown`: no version of the id in this key's dataset. It was never
issued, is a street hit's id, is past the history kept, or is from the
other dataset.
- Then store the answer's `updated` as the next `since`. With no stored
date, send the earliest date `since` accepts.
- A `kp_test_` key's ids are not a `kp_live_` key's. After going live, look
your addresses up again.
## Operations
- [POST /v1/addresses/changes](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md): What changed for stored addresses.
---
# Your own address list: one job, one row per row
> Index: https://kartpunkt.se/llms.txt
A list of your own addresses (a register at import, a month of trips, a
customer register) is one `address_list` job, never a loop of synchronous
calls.
- Submit `POST /v1/jobs` with `operation: "address_list"` and
`input: { kind, rows }`, up to 50000 rows of one kind. `validate` rows
are `/v1/addresses/validate`'s body, `geocode` rows `{ "id", "q" }` and
`reverse` rows `{ "id", "point" }`.
- The result has one row per row sent, in order, with your `id`, a
`status` and `address`. `summary` counts your rows by status.
- `matched`: the row is one address, and `address.id` is its Lantmäteriet
id (`se.lm.…`; `se.syn.…` for a `kp_test_` key). For `validate` it exists
as sent, or with only its postnummer or its postort differing while the
other one places it. For `geocode` the best hit is an address, not a
street, that holds everything sent, with no other such address beside it.
For `reverse` it is the nearest address within `radius_m`.
- `ambiguous`: the row fits several addresses, so send its postnummer or
postort. `unmatched`: no address is the row. `address` is `null` unless
`matched`: never a guess and never a list of candidates.
- Every row counts as one live call and one address toward the plan,
held when the job is submitted. A list longer than what is left of
today's address limit is 429 `quota_exceeded`, so split it across days.
- The input is deleted when the job ends, and the result within 24 hours.
Store each matched row's `id` and point.
## Operations
- [POST /v1/jobs](https://api.kartpunkt.se/docs/operations/submitJob.md): Run an operation as a job.
---
# When there is no path: a path's status and what to send next
> Index: https://kartpunkt.se/llms.txt
A route, each distances result and each matrix cell carries a `status`.
Every status is a 200 answer, not an error. `distance_m`, `duration_s` and
`geometry` are `null` unless it is `ok`.
- `ok`: a path. Use its `distance_m`, `duration_s` and line.
- `no_road_near`: an end met no road of the profile's network within 500 m.
Send a point on or beside a road, with the `street` it lies on when you
know it.
- `no_path` or `ferry_required`: the network joins no path, or only a ferry
does. Show the status; the same call on the same network answers the same.
- `avoided_road_required`: only what `avoid` keeps off joins the ends.
`blocked_by` names each entry in the way as `{ field, index, id }`. Show
it to whoever decides rather than lifting an entry yourself.
- When an end lies on a road that is kept off, send the end's entrance on a
path or side road as its point. Else every path to it is
`avoided_road_required`.
- Read a status you do not know as no path: draw nothing and show it.
- On `/v1/matrix`, `statuses[origin][destination]` holds each cell's status,
and `distances_m` is `null` where it is not `ok`.
For `avoid` itself, see "Keeping a path off roads: avoid, barriers and
alternatives".
---
# Distances along the road, not as the crow flies
> Distances and routes along Trafikverket's road network, with road facts on request and the road network's version in every answer. The API runs in the EU.
>
> https://kartpunkt.se/en
Kartpunkt is an API for road distances and routes along Trafikverket's road network (NVDB), with the road facts along each path, and for Swedish street addresses from Lantmäteriet's address register. Every answer is documented and dated with the version of the road network it was computed on. While that version is served, the same question gives the same answer.
A free test key routes on the real road network, with no card. The API runs at Hetzner in Helsinki, in the EU, and a coding agent can write the integration from [llms.txt](https://kartpunkt.se/llms.txt).
## Try it on the map
The map on the [Swedish landing page](https://kartpunkt.se/#prova) routes on Trafikverket's road network with no account and no key, and nothing you click is logged.
## What it answers
Every operation is under `/v1`. [For developers](https://kartpunkt.se/en/developers) has the documentation, the keys and three steps to a first answer.
- **Search** (`searchAddresses`): From text to the addresses that match, even when it is misspelled: autocomplete for an address form.
- **Reverse geocoding** (`reverseGeocode`): From a coordinate to the nearest addresses, with the distance in metres and the nearest road.
- **Validation** (`validateAddress`): Checks an address as given and reports what is wrong, without correcting it.
- **Address changes** (`checkAddressChanges`): Whether addresses you stored have changed or closed since you fetched them.
- **Postnummer lookup** (`lookupPostcode`): From a postnummer to its postorter and kommuner.
- **Address lookup** (`lookupAddress`): From a stored address id, Lantmäteriet's own, to the address as it is today or on a date.
- **Route** (`planRoute`): The route for a profile, in segments with a road reference and the facts you ask for. It can keep off stretches you send, such as a closed road.
- **Road distances** (`measureDistances`): From many origins to up to five destinations: distance and time along the roads for a profile, with the facts you ask for and the path that keeps off what you send in avoid.
- **Nearest stops** (`findNearest`): The bus stops nearest each point along the roads for a profile, with the distance and time to each.
- **Matrix** (`buildMatrix`): Distance and time between every origin and every destination; large matrices as a job.
- **Reach** (`measureReach`): The area reached within a number of minutes or metres from a point.
- **Trace** (`matchTrace`): Matches a GPS trace or a drawn line to the roads it followed. Nothing sent is stored.
- **Route optimization** (`optimizeRoutes`): Which vehicle visits which stops, in what order and when, within capacities and time windows; large plans as a job.
- **Plan check** (`checkPlan`): Your own routes, in your order, timed and loaded on the same terms as route optimization, with what each breaks; large plans as a job.
- **Snap** (`snapPoints`): Moves points onto the nearest road a profile may travel, and says how far.
- **Roads** (`listRoadsNearby`): The roads around a point, with the facts you ask for.
- **Road reference** (`getRoad`): A road by its road reference, which stays the same in every network snapshot that holds the road.
- **Profiles** (`listProfiles`): The profiles routes are weighed for: walking, car by the shortest and by the fastest way, and lorries and buses, which follow NVDB's height, weight, axle-load, length and width limits and its bans.
- **Facts** (`listFacts`): Every NVDB fact a route or a road can report, with its kind and unit.
- **Sources** (`listSources`): Each data source with its licence, its attribution and what its data may be used for.
- **Vector tiles** (`listTilesets`): NVDB's roads as vector tiles to draw in your own map.
- **Network snapshots** (`listNetworks`): The road network's snapshots: the one served now and the older ones kept.
- **Network snapshot** (`getNetwork`): One network snapshot: its NVDB date, when it was built and which profiles it can answer.
- **Jobs** (`submitJob`): Sends a large request as a job, such as a large matrix or a route on an older network snapshot.
- **Jobs** (`getJob`): A job's status and, once it is done, its answer.
- **Jobs** (`cancelJob`): Cancels a job.
## Start free. Pay when you go live.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
| Routes & distances (add-on) | SEK 990 a month | 50,000 more calculations a month, on Bas or Pro |
All prices are in Swedish kronor, excl. VAT. [Pricing](https://kartpunkt.se/en/pricing) says what each quota counts.
## Start with a test key today
The test key routes on the real road network; its addresses are synthetic, in the same shapes as live data. When your account is approved for live data, you change the key, not the code. [Create a test key](https://kartpunkt.se/konto) (the account pages are in Swedish), give your coding agent [llms.txt](https://kartpunkt.se/llms.txt) and the [OpenAPI document](https://api.kartpunkt.se/openapi.json), or write to hej@kartpunkt.se for a walkthrough on your own points.
- [For developers](https://kartpunkt.se/en/developers): the documentation, the keys and three steps to a first answer.
- [Pricing](https://kartpunkt.se/en/pricing): four plans, from free test keys to agreements for vendors and fleet operators.
- [Use cases](https://kartpunkt.se/en/use-cases): trip logs, address validation, road context and distances from many points.
- [Compare](https://kartpunkt.se/en/compare): Kartpunkt beside Lantmäteriet's own API, Google and Adresser.online.
- The map demo on the [Swedish landing page](https://kartpunkt.se/#prova), with no key.
The API runs at Hetzner in Helsinki · We never log what you send · Lantmäteriet's terms are enforced in code · The web pages are served by Vercel and sign-in is with Google · A data processing agreement on request.
---
# Get started with Kartpunkt's API
> From sign-in to a first answer in three steps: a test key, documentation for coding agents (llms.txt, OpenAPI) and a ready prompt for your coding agent.
>
> https://kartpunkt.se/en/developers
Kartpunkt gives your system Swedish road distances, routes and addresses from Trafikverket's NVDB and Lantmäteriet's address register. You call the API from your backend with a key, or let your coding agent write the integration. Every answer names its source and network snapshot.
## Documentation
- **For coding agents:** [https://kartpunkt.se/llms.txt](https://kartpunkt.se/llms.txt). What a coding agent needs to write the integration: the operations, the fields, the error codes and the rules.
- **The OpenAPI document:** [https://api.kartpunkt.se/openapi.json](https://api.kartpunkt.se/openapi.json), for tools that generate clients and for reading the schema.
- **API reference:** [https://api.kartpunkt.se/docs](https://api.kartpunkt.se/docs), the same document to read in the browser. There you try each operation with a test key you enter there, in your own browser.
- **The API's base URL:** `https://api.kartpunkt.se`
The documents are generated from the API's definition. The fields and error codes are there, not here.
## Three steps to a first answer
1. [Sign in](https://kartpunkt.se/logga-in) with your Google account (the page is in Swedish). The first sign-in creates your account.
2. Create a test key on your [account page](https://kartpunkt.se/konto). It works at once and is shown once, when you create it.
3. Put the key in the environment variable `KARTPUNKT_API_KEY` in your own environment, for example in the project's `.env`. Your code sends it as `Authorization: Bearer` or `x-api-key`.
Check the key: the call below answers 200 with a test key.
```sh
curl https://api.kartpunkt.se/v1/postcodes/75331 \
-H "Authorization: Bearer $KARTPUNKT_API_KEY"
```
## Prompt for your coding agent
Paste the prompt into your coding agent. It reads llms.txt, takes the key from `KARTPUNKT_API_KEY` and writes the code.
```text
Integrate Kartpunkt (road distances, routes and official Swedish addresses via API) into this project.
1. Read https://kartpunkt.se/llms.txt before you write code.
2. The API key is in the environment variable KARTPUNKT_API_KEY. Never ask me to paste it into the chat. Check it first with GET /v1/postcodes/75331, which must return 200.
3. Start with POST /v1/distances from our origins to our destinations. Store each road distance with provenance.network.id, so it is documented and dated with its network snapshot.
4. Show the attribution from the answer wherever the results are shown.
```
## Test keys and live keys
**A test key** starts with `kp_test_`. You create it yourself on your account page, with no approval. It answers from the synthetic dataset: made-up addresses with the same shapes as live data, and every answer is marked `provenance.synthetic: true`. The mark is about the addresses: the road network is NVDB's real one with either key, and `provenance.sources` says `synthetic` for the synthetic addresses and `nvdb` for the road network.
**A live key** starts with `kp_live_` and answers from Lantmäteriet's address register, once the account is approved for live access and has a paid plan; until then it is refused with a code and a message that says what to do. Live access needs an agreement with us: ask through the [contact page](https://kartpunkt.se/kontakt) (in Swedish) or at hej@kartpunkt.se.
Switching from a test key to a live key changes only the key in `KARTPUNKT_API_KEY`. The code stays the same.
## The key stays on your server
A key is shown once, when you create it. We store it only hashed and cannot show it again. Put it in `KARTPUNKT_API_KEY`, where your code and your coding agent find it.
Never paste the key into a chat with a coding agent, an email or code. If a key ends up somewhere it should not, revoke it on your account page and create a new one.
Call the API from your backend, never from code that runs in the browser. There the key would leak to anyone who opens the page, and the API does not answer calls from browsers.
## Answers and errors
Every answer has `provenance`; a job has it in its answer once the job is done. `provenance` holds each source's attribution in `attribution`, and `synthetic` says a test key answered: the addresses are then synthetic, the road network NVDB's. Each address has a stable `id` and a ready-made address line in `label`. Store the id and the label, and show the attribution where the addresses are shown.
Store `provenance.network.id` with the answer: it documents and dates the answer with its network snapshot. While that snapshot is served, the same call with `network: { id }` gives the same answer. An older snapshot cannot be computed on today.
Every error is `{ error, message, doc_url }`: a stable error code and a message that says what to do next, written so that a coding agent can follow it, with a link to the code's page; an invalid request also names the field in `param`. The fields and error codes are in [llms.txt](https://kartpunkt.se/llms.txt).
## Limits
Calls are limited per minute and per day, so that no one can copy the address register. A call over a limit gets the answer 429 with the header `Retry-After`: wait that many seconds and try again. The plans and what they include are on the [pricing page](https://kartpunkt.se/en/pricing).
A call also has a largest size. A larger call gets an error that says what to change, and the same input runs as a job, from Bas. Per call: route optimization and plan checks take 25 stops, 12 shipments, 5 vehicles and 30 locations directly, and 500 stops, 250 shipments, 50 vehicles and 600 locations as a job; a route takes 10 via points; distances take 1,000 origins and 200 origins × destinations directly (20 with a barrier), 50,000 and 50,000 as a job; a matrix 625 cells directly (8 origins with a vehicle, prefer or avoid) and 10,000 as a job; reach 4 times or distances per call. The other operations' limits are in [llms.txt](https://kartpunkt.se/llms.txt).
## Operations
The operations are under `/v1`, in the API's groups: addresses, roads, routes and distances, planning, registries and network snapshots, and jobs. Each operation has its own page in the API reference.
- **Search** ([`searchAddresses`](https://api.kartpunkt.se/docs/operations/searchAddresses.md)): From text to addresses, even misspelled.
- **Reverse geocoding** ([`reverseGeocode`](https://api.kartpunkt.se/docs/operations/reverseGeocode.md)): From a coordinate to the nearest addresses and the road beside them.
- **Validation** ([`validateAddress`](https://api.kartpunkt.se/docs/operations/validateAddress.md)): Reports what is wrong in an address, without changing it.
- **Address changes** ([`checkAddressChanges`](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md)): Which of your addresses have changed or closed since a date.
- **Postnummer lookup** ([`lookupPostcode`](https://api.kartpunkt.se/docs/operations/lookupPostcode.md)): From a postnummer to its postorter and kommuner.
- **Address lookup** ([`lookupAddress`](https://api.kartpunkt.se/docs/operations/lookupAddress.md)): From a stored address id to the address today or on a date.
- **Snap** ([`snapPoints`](https://api.kartpunkt.se/docs/operations/snapPoints.md)): Moves points onto the nearest road a profile may travel.
- **Roads** ([`listRoadsNearby`](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md)): The roads around a point, with the facts you ask for.
- **Road reference** ([`getRoad`](https://api.kartpunkt.se/docs/operations/getRoad.md)): A road by its road reference, which points to the same road in every delivery where the link remains.
- **Route** ([`planRoute`](https://api.kartpunkt.se/docs/operations/planRoute.md)): The route for a profile, in route segments with a road reference and facts; avoids stretches you send.
- **Road distances** ([`measureDistances`](https://api.kartpunkt.se/docs/operations/measureDistances.md)): From many origins to a few destinations, such as customers to a depot: distance and time along the road network, with the facts you ask for.
- **Nearest bus stops** ([`findNearest`](https://api.kartpunkt.se/docs/operations/findNearest.md)): The bus stops nearest each point along the road network.
- **Matrix** ([`buildMatrix`](https://api.kartpunkt.se/docs/operations/buildMatrix.md)): Distance and time between each origin and each destination.
- **Reach** ([`measureReach`](https://api.kartpunkt.se/docs/operations/measureReach.md)): The area reached within a number of minutes or metres from a point.
- **Trace** ([`matchTrace`](https://api.kartpunkt.se/docs/operations/matchTrace.md)): Matches a GPS trace to the roads it followed; the trace is not stored.
- **Route optimization** ([`optimizeRoutes`](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md)): Which vehicle visits which stops, in what order and when, within capacities and time windows.
- **Plan check** ([`checkPlan`](https://api.kartpunkt.se/docs/operations/checkPlan.md)): Your own routes, in your order, with what breaks capacities, time windows and other conditions.
- **Profiles** ([`listProfiles`](https://api.kartpunkt.se/docs/operations/listProfiles.md)): The profiles routes are computed for: walking, car (shortest and fastest way), lorry and bus.
- **Facts** ([`listFacts`](https://api.kartpunkt.se/docs/operations/listFacts.md)): NVDB's facts a route or a road can report, with their kind and unit.
- **Sources** ([`listSources`](https://api.kartpunkt.se/docs/operations/listSources.md)): Each data source with its licence, its attribution and what its data may be used for.
- **Vector tiles** ([`listTilesets`](https://api.kartpunkt.se/docs/operations/listTilesets.md)): NVDB's roads as vector tiles to draw in your own map.
- **Network snapshots** ([`listNetworks`](https://api.kartpunkt.se/docs/operations/listNetworks.md)): The road network's snapshots: the one served now and the older ones kept.
- **Network snapshot** ([`getNetwork`](https://api.kartpunkt.se/docs/operations/getNetwork.md)): One network snapshot: its NVDB date and which profiles it can answer.
- **Jobs** ([`submitJob`](https://api.kartpunkt.se/docs/operations/submitJob.md)): Submits a job.
- **Jobs** ([`getJob`](https://api.kartpunkt.se/docs/operations/getJob.md)): Status and answer.
- **Jobs** ([`cancelJob`](https://api.kartpunkt.se/docs/operations/cancelJob.md)): Cancels.
## Route optimization: an example
Send the vehicles and the stops to `/v1/optimize` and get back which vehicle drives to which stops, in what order and when. Every stop no vehicle takes is in `unassigned` with an error code that says what to change.
A plan at once:
```sh
curl -X POST https://api.kartpunkt.se/v1/optimize \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vehicles":[{"id":"fordon-1","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}},{"id":"fordon-2","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}}],"stops":[{"id":"s1","at":{"lat":63.720434,"lon":19.348226,"street":"Skolvägen"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s2","at":{"lat":63.724508,"lon":19.339523,"street":"Norrgatan"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s3","at":{"lat":63.724205,"lon":19.337043,"street":"Snedgatan"},"pickup":{"seats":1},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]}],"objective":"vehicles_then_duration","profile":"car_fastest"}'
```
A larger plan as a job: the same input under `"operation": "optimize"`. Read the job until its `status` is `succeeded`; the plan is in `result`.
```sh
JOB_ID=$(curl -X POST https://api.kartpunkt.se/v1/jobs \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"operation":"optimize","input":{"vehicles":[{"id":"fordon-1","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}},{"id":"fordon-2","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}}],"stops":[{"id":"s1","at":{"lat":63.720434,"lon":19.348226,"street":"Skolvägen"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s2","at":{"lat":63.724508,"lon":19.339523,"street":"Norrgatan"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s3","at":{"lat":63.724205,"lon":19.337043,"street":"Snedgatan"},"pickup":{"seats":1},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]}],"objective":"vehicles_then_duration","profile":"car_fastest"}}' | jq -r .id)
curl https://api.kartpunkt.se/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $KARTPUNKT_API_KEY"
```
Jobs are included from Bas. On the free Utvecklare plan, `POST /v1/jobs` answers 403 `live_access_required`, even with a test key; there a plan goes directly to `/v1/optimize`, within the limits above.
The points are in the synthetic dataset's made-up town Ruttby; with a key they are computed on NVDB's real roads, so the answer is a different plan from the one recorded on [/prova](https://kartpunkt.se/prova#ruttoptimering) (in Swedish). The limits for a plan at once and for a job are under [Limits](#limits).
---
# Plans for the Swedish address API
> Kartpunkt's plans: Utvecklare free with test keys, Bas SEK 490 a month, Pro SEK 1,990 a month, Avtal for vendors and fleet operators, by quote. All prices excl. VAT.
>
> https://kartpunkt.se/en/pricing
Test keys are free, with 2,000 calculations a month on the real road network. Bas and Pro are for businesses and developers with a service of their own: live data starts at SEK 490 a month on Bas, with 5,000 calculations a month included, and Pro, at SEK 1,990 a month, includes 50,000. Vendors that sell systems to municipalities and other organisations, and operators that plan their own routes, sign Avtal, priced by quote. All prices are in Swedish kronor (SEK), excl. VAT. Kartpunkt sells to businesses and public bodies, invoices under an agreement, and takes no card payment.
## Plans
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
| Routes & distances (add-on) | SEK 990 a month | 50,000 more calculations a month, on Bas or Pro |
## Ordering a plan
Utvecklare is every account's plan until another is set: create a test key on your [account page](https://kartpunkt.se/konto) (in Swedish) and start. A paid plan is set under an agreement and invoiced: order it through the [contact page](https://kartpunkt.se/kontakt) (in Swedish) or at hej@kartpunkt.se. No page takes a card payment.
## What each plan counts
- **Requests a day** count every call by the account's keys, test keys included.
- **Live calls** are a live key's calls to reverse geocoding, search, validation, postnummer lookup and address changes, one per call.
- **Addresses** are the addresses an account's live keys get back in a day: the limit against copying the register. Postnummer answers, road distances and routes hold no address and do not count.
- **Calculations** (Routes & distances) are routing work: one per route or trace, one per origin per destination of a distance call, one per reach area, one per point of a nearest call and one per stop or shipment of an optimization or a plan check; a matrix counts half its cells, at most ten per point of the longer list. On Utvecklare, test keys' calculations count toward 2,000 a month.
- **Per minute**, every key and every account may make 600 calls, on every plan.
- **A month** is a calendar month in UTC, and a day a UTC day.
## Test data and live data
Test keys answer addresses from the synthetic dataset on every plan, route on NVDB's real road network, and never count toward live calls or addresses. Jobs need a paid plan. Live keys answer from Lantmäteriet's register for an account approved for live access, under an agreement, with a paid plan.
---
# Reverse geocoding for Swedish addresses
> Turn a WGS84 coordinate into the nearest official Swedish address from Lantmäteriet's register, with the road beside it from NVDB. The API runs in the EU.
>
> https://kartpunkt.se/en/reverse-geocoding
Send a WGS84 coordinate and get the nearest official Swedish addresses, with the road the point is on: its speed limit, road owner and surface. `POST /v1/reverse-geocode` answers the nearest current addresses to a WGS84 coordinate from Lantmäteriet's address register, nearest first, each with `distance_m`, the distance in metres.
## The call
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/reverse-geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":59.8599,"lon":17.6443}}'
```
Answer:
```json
{
"results": [
{
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.859903789196885,
"lon": 17.64433171466342
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 1.83
}
],
"road": {
"name": "Järngatan",
"road_number": null,
"road_class": 8,
"speed_limit_kmh": 30,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 1.83,
"fallback": false
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m45nmn6mfey8z9wvaf31nb9n",
"dataset": "nvdb",
"source_date": "2026-10-05",
"built_at": "2026-10-05T09:17:11.764Z",
"graph": "snap_01m45nmnryfey8z9x29xcd08tq"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
},
{
"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-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
Each result is an address: a stable `id`, the `label` to show and store, the street, number and letter, postnummer, postort, kommun, län and distrikt, the point in WGS84 and what it marks (`position_type`). `road` is the nearest drivable road with a name or a number within 100 m, or null.
## For a vehicle
For a vehicle's position, such as a trip's start or end, send `radius_m` 5000, `distinct` `street` and `limit` 3. You get the nearest address of each of the three nearest streets, and choose the street the vehicle drove on: at a junction the nearest door is often on the cross street. More in [trip logs](https://kartpunkt.se/en/use-cases/trip-logs).
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/reverse-geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":59.8599,"lon":17.6443},"radius_m":5000,"distinct":"street","limit":3}'
```
Answer:
```json
{
"results": [
{
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.859903789196885,
"lon": 17.64433171466342
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 1.83
},
{
"id": "se.syn.syn-0000taj",
"type": "address",
"label": "Bredstigen 11, 756 46 Uppsala",
"street": "Bredstigen",
"number": 11,
"letter": null,
"postcode": "75646",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903805",
"district": "Uppsala landsbygd",
"point": {
"lat": 59.85826774209593,
"lon": 17.64290149051346
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 198.02
},
{
"id": "se.syn.syn-0000t9s",
"type": "address",
"label": "Örnvägen 2, 753 45 Uppsala",
"street": "Örnvägen",
"number": 2,
"letter": null,
"postcode": "75345",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903804",
"district": "Uppsala västra",
"point": {
"lat": 59.86256320677179,
"lon": 17.645462724134184
},
"position_type": "building",
"match_type": null,
"score": 1,
"distance_m": 303.78
}
],
"road": {
"name": "Järngatan",
"road_number": null,
"road_class": 8,
"speed_limit_kmh": 30,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 1.83,
"fallback": false
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m45nmn6mfey8z9wvaf31nb9n",
"dataset": "nvdb",
"source_date": "2026-10-05",
"built_at": "2026-10-05T09:17:11.764Z",
"graph": "snap_01m45nmnryfey8z9x29xcd08tq"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
},
{
"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-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
## When nothing is near
No address within `radius_m` (2000 m unless you send another, at most 50000) is a 200 with an empty `results` list, never an error. `limit` sets how many addresses you get, 1 to 10.
## Price
Test keys are free on every plan and answer from the synthetic dataset. Live calls need a paid plan and an account approved for live access. All prices are in Swedish kronor, excl. VAT.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
[Create a test key](https://kartpunkt.se/konto) on your account page (the account pages are in Swedish), read [how to start](https://kartpunkt.se/en/developers), or see [all plans](https://kartpunkt.se/en/pricing).
---
# Validate Swedish addresses
> Check an address a customer typed, at checkout or in a CRM, against Lantmäteriet's register: what is wrong is reported, never silently corrected.
>
> https://kartpunkt.se/en/address-validation
`POST /v1/addresses/validate` checks that an address exists exactly as given in Lantmäteriet's address register, and reports what is wrong in `issues` instead of silently correcting it. Use it at checkout or sign-up, before you ship to an address or store it, and in a CRM to check the addresses you hold.
## The call
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/addresses/validate \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"street":"Järngatan","number":2,"letter":"C","postcode":"75541"}'
```
Answer:
```json
{
"valid": false,
"match": {
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.859903789196885,
"lon": 17.64433171466342
},
"position_type": "driveway",
"match_type": "partial",
"score": 1
},
"issues": [
"letter_not_found"
],
"suggestions": [],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
The customer typed 2C, and the register has no letter C at Järngatan 2 in 755 41. `valid` is false, `issues` says `letter_not_found`, and `match` is the closest address, for you to compare with what was typed. It is never a correction made for you.
## What issues can say
- `street_not_found`: no such street; `suggestions` holds search hits for what was sent.
- `number_not_found`: the street has no such number; `suggestions` holds the nearest numbers on the street.
- `letter_not_found`: the number has no such letter.
- `postcode_mismatch`: the postnummer sent is not the address's.
- `locality_mismatch`: the postort or kommun sent is not the address's.
- `ambiguous`: what was sent fits several addresses.
`valid` is true only when `issues` is empty.
## In an address form
For the form itself, `POST /v1/geocode` is autocomplete that tolerates typos: misspellings, å, ä and ö typed without diacritics, and abbreviations such as sthlm. It answers street-level hits until a house number is typed. Each hit says in `match_type` whether it is `exact`, `corrected` (a word was read as a misspelling: ask the user) or `partial`.
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"q":"jarngatan 2 upsala","limit":1}'
```
Answer:
```json
{
"results": [
{
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.859903789196885,
"lon": 17.64433171466342
},
"position_type": "driveway",
"match_type": "corrected",
"score": 6.057753791356872
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
## Know when an address you hold changes
Store each address's `id` with its `label`. `POST /v1/addresses/changes` then says, for up to 20 stored ids at a time, whether each is unchanged, changed or closed since a date you give.
## Price
Test keys are free on every plan and answer from the synthetic dataset. Live calls need a paid plan and an account approved for live access. All prices are in Swedish kronor, excl. VAT.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
[Create a test key](https://kartpunkt.se/konto) on your account page (the account pages are in Swedish), read [how to start](https://kartpunkt.se/en/developers), or see [all plans](https://kartpunkt.se/en/pricing).
---
# Swedish postal code (postnummer) lookup
> Look up a Swedish postal code (postnummer) and get the postorter and kommuner it is used in, with län and the number of addresses, from Lantmäteriet's address register.
>
> https://kartpunkt.se/en/postcode-lookup
A postnummer is the Swedish postal code: five digits, often written with a space after the third (755 41). `GET /v1/postcodes/{postcode}` answers the postorter (post towns) and kommuner (municipalities) it is used in, most addresses first, derived from the current addresses in Lantmäteriet's address register.
## The call
Request:
```sh
curl https://api.kartpunkt.se/v1/postcodes/75541 \
-H "Authorization: Bearer $KARTPUNKT_API_KEY"
```
Answer:
```json
{
"postcode": "75541",
"localities": [
{
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"addresses": 8
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
Usually there is one entry. A postnummer used in more than one postort or kommun answers one entry for each, with its count of addresses.
## PO boxes
A postnummer used only for PO boxes (box addresses) is not in the address register, so it answers 404 `not_found`, with a message that says so. A postnummer no current address has answers the same.
## Format
Send five digits, optionally with a space after the third (URL-encode the space). Anything else answers 400 `invalid_request`, with a message that names what is wrong.
## Price
Test keys are free on every plan and answer from the synthetic dataset. Live calls need a paid plan and an account approved for live access. All prices are in Swedish kronor, excl. VAT.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
[Create a test key](https://kartpunkt.se/konto) on your account page (the account pages are in Swedish), read [how to start](https://kartpunkt.se/en/developers), or see [all plans](https://kartpunkt.se/en/pricing).
---
# What you can build with Kartpunkt
> What developers build with Kartpunkt: trip log addresses, road distances for transport platforms, address validation at checkout and in a CRM, and road context.
>
> https://kartpunkt.se/en/use-cases
Kartpunkt's answers, by what you are building. Each page has an example call and its answer.
## Use cases
- [Trip logs](https://kartpunkt.se/en/use-cases/trip-logs): an official address for each trip's start and end in a körjournal, from the vehicle's GPS position.
- [School transport platforms](https://kartpunkt.se/en/use-cases/school-transport): for platforms that build school-transport products, road distances on foot or by car from many homes to a few destinations, with NVDB's facts along each route.
- [Address validation at checkout and in a CRM](https://kartpunkt.se/en/address-validation): check an address as typed, and say what is wrong instead of correcting it.
- [Road context](https://kartpunkt.se/en/use-cases/road-context): the road at a coordinate, with its speed limit, road owner and surface.
---
# Addresses for trip logs
> An official Swedish address for each trip's start and end, from a vehicle's GPS position: the nearest streets' addresses and the road with its speed limit.
>
> https://kartpunkt.se/en/use-cases/trip-logs
A körjournal is the Swedish trip log for a company car, and Skatteverket asks for an address for each trip. Kartpunkt turns the vehicle's GPS position at a trip's start and end into the nearest official address from Lantmäteriet's register, and names the road the vehicle is on, with its speed limit.
## The call
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/reverse-geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":59.8599,"lon":17.6443},"radius_m":5000,"distinct":"street","limit":3}'
```
Answer:
```json
{
"results": [
{
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.859903789196885,
"lon": 17.64433171466342
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 1.83
},
{
"id": "se.syn.syn-0000taj",
"type": "address",
"label": "Bredstigen 11, 756 46 Uppsala",
"street": "Bredstigen",
"number": 11,
"letter": null,
"postcode": "75646",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903805",
"district": "Uppsala landsbygd",
"point": {
"lat": 59.85826774209593,
"lon": 17.64290149051346
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 198.02
},
{
"id": "se.syn.syn-0000t9s",
"type": "address",
"label": "Örnvägen 2, 753 45 Uppsala",
"street": "Örnvägen",
"number": 2,
"letter": null,
"postcode": "75345",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903804",
"district": "Uppsala västra",
"point": {
"lat": 59.86256320677179,
"lon": 17.645462724134184
},
"position_type": "building",
"match_type": null,
"score": 1,
"distance_m": 303.78
}
],
"road": {
"name": "Järngatan",
"road_number": null,
"road_class": 8,
"speed_limit_kmh": 30,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 1.83,
"fallback": false
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m45nmn6mfey8z9wvaf31nb9n",
"dataset": "nvdb",
"source_date": "2026-10-05",
"built_at": "2026-10-05T09:17:11.764Z",
"graph": "snap_01m45nmnryfey8z9x29xcd08tq"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
},
{
"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-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
With `distinct` `street` and `limit` 3, the answer is the nearest address of each of the three nearest streets, and `road` is the road the coordinate is on. Store the first address whose street equals `road.name` when it is within 50 m. When `road.fallback` is true, no address is within 50 m: store the road (its name, else its number) with the kommun. Otherwise store the first address. [llms.txt](https://kartpunkt.se/llms.txt) has the rule in full.
## What to save with the trip
Store the address's `id` and `label` with the trip. Kartpunkt's answers may be stored with the trip they were looked up for, shared between your users and systems, and shown on any map, with the attribution.
## The road and its speed limit
`road` names the road with its speed limit (`speed_limit_kmh`), its number, its road class, its road owner and its surface, from Trafikverket's NVDB. More in [road context](https://kartpunkt.se/en/use-cases/road-context).
## Drivers' positions
A vehicle's position is personal data about its driver. Kartpunkt never logs or stores the coordinates you send: about a call it keeps only the key, the operation, the status, the response time and counts.
## Price
Test keys are free on every plan and answer from the synthetic dataset. Live calls need a paid plan and an account approved for live access. All prices are in Swedish kronor, excl. VAT.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
[Create a test key](https://kartpunkt.se/konto) on your account page (the account pages are in Swedish), read [how to start](https://kartpunkt.se/en/developers), or see [all plans](https://kartpunkt.se/en/pricing).
---
# Road distances for school-transport platforms
> For platforms that build school-transport (skolskjuts) products: road distances on foot or by car from many homes to 1 to 5 destinations, up to 200 pairs per call and 50,000 as a job, along Trafikverket's NVDB, with NVDB's facts along each route.
>
> https://kartpunkt.se/en/use-cases/school-transport
This page is for platforms that build school-transport (skolskjuts) products on Kartpunkt. Kartpunkt answers road distances and NVDB's facts along the roads; the rules your product applies stay in your product. `POST /v1/distances` answers road distances by a profile, on foot or by car, from homes to 1 to 5 destinations, up to 200 home–destination pairs in one call and 50,000 as a job, along Trafikverket's road network (NVDB).
## The call
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/distances \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"destinations":[{"point":{"lat":59.859904,"lon":17.644332,"street":"Järngatan"}}],"origins":[{"id":"home-1","point":{"lat":59.858268,"lon":17.642901,"street":"Bredstigen"}},{"id":"home-2","point":{"lat":59.862563,"lon":17.645463,"street":"Örnvägen"}}],"profile":"walk","facts":["fast_road","unlit_outside_built_up","crossings_without_refuge"]}'
```
Answer:
```json
{
"avoid": null,
"destinations": [
{
"id": null,
"snap_m": 0.01,
"street_matched": true
}
],
"results": [
{
"id": "home-1",
"destination_id": null,
"destination_index": 0,
"status": "ok",
"distance_m": 199.1,
"duration_s": 143,
"facts": {
"fast_road": 0,
"unlit_outside_built_up": 0,
"crossings_without_refuge": 0
},
"avoid_m": null,
"snap_m": 0.04,
"street_matched": true,
"destination_snap_m": 0.01,
"destination_street_matched": true,
"blocked_by": null,
"alternatives": null
},
{
"id": "home-2",
"destination_id": null,
"destination_index": 0,
"status": "ok",
"distance_m": 713.7,
"duration_s": 514,
"facts": {
"fast_road": 0,
"unlit_outside_built_up": 0,
"crossings_without_refuge": 0
},
"avoid_m": null,
"snap_m": 21.76,
"street_matched": true,
"destination_snap_m": 0.01,
"destination_street_matched": true,
"blocked_by": null,
"alternatives": null
}
],
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m45nmn6mfey8z9wvaf31nb9n",
"dataset": "nvdb",
"source_date": "2026-10-05",
"built_at": "2026-10-05T09:17:11.764Z",
"graph": "snap_01m45nmnryfey8z9x29xcd08tq",
"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-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
Each origin is answered in the order sent, with a status of its own, so one bad address never costs the others their answers. Each result is one home to one destination, with its `destination_id`. `walk` is the shortest way on foot, `car_shortest` the shortest legal road distance by car, and `car_fastest` the quickest way by car; each gives `distance_m` along the roads and `duration_s`, free-flow seconds (5 km/h on foot, the posted limits by car, no traffic).
## Facts along the route
Send the facts you need as `facts`, and each result's `facts` states NVDB's facts about its route: `fast_road`, the metres along roads of 70 km/h or more; `unlit_outside_built_up`, the metres outside built-up areas that are not on a lit path; `crossings_without_refuge`, crossings of roads of 60 km/h or more without a refuge; and `traffic_volume`, the traffic (ÅDT). It is not a verdict on whether the route is safe.
## Documented and dated with the network snapshot
Store each distance with its profile, its status and the answer's `provenance.network`: the network snapshot's id and its NVDB date. That documents and dates the distance with the road network it was computed on.
## Bus stops and routes
Send the same homes to `POST /v1/nearest` with `kind: "bus_stop"` to get each one's nearest bus stops on foot, and send `avoid` to `/v1/distances` to keep off stretches you name. `POST /v1/route` answers the route itself for one pair of points, as segments to draw on a map, each with its road reference and the facts you ask for.
## Protected identities
Never send people with skyddade personuppgifter (protected identities). Kartpunkt never logs or stores what you send: about a call it keeps only the key, the operation, the status, the response time and counts.
## Price
Road distances and routes count in calculations under Routes & distances: one per route or trace, one per origin per destination of a distance call, one per reach area, one per point of a nearest call and one per stop or shipment of an optimization or a plan check; a matrix counts half its cells, at most ten per point of the longer list. Utvecklare has 2,000 calculations a month on test keys, Bas includes 5,000 and Pro 50,000; the add-on gives 50,000 more on Bas or Pro. 200 origins, each to three depots, are 600 calculations as distances with road facts and 300 as a matrix. All prices are in Swedish kronor, excl. VAT.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
| Routes & distances (add-on) | SEK 990 a month | 50,000 more calculations a month, on Bas or Pro |
[Create a test key](https://kartpunkt.se/konto) on your account page (the account pages are in Swedish), read [how to start](https://kartpunkt.se/en/developers), or see [all plans](https://kartpunkt.se/en/pricing).
---
# Speed limit, road owner and surface at a coordinate
> For a coordinate, the nearest named road from Trafikverket's NVDB: its speed limit, road number, road class, road owner (state, kommun or private) and surface.
>
> https://kartpunkt.se/en/use-cases/road-context
Every reverse geocoding answer names `road`: the nearest drivable road with a name or a number within 100 m of the coordinate, from Trafikverket's national road database NVDB (CC0). The roads are the same for every key: a test key's addresses are synthetic, its roads NVDB's.
## The call
Request:
```sh
curl -X POST https://api.kartpunkt.se/v1/reverse-geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":59.8599,"lon":17.6443},"limit":2}'
```
Answer:
```json
{
"results": [
{
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.859903789196885,
"lon": 17.64433171466342
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 1.83
},
{
"id": "se.syn.syn-0000uvs",
"type": "address",
"label": "Järngatan 1, 755 41 Uppsala",
"street": "Järngatan",
"number": 1,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.860088490002525,
"lon": 17.644471061598395
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 23.08
}
],
"road": {
"name": "Järngatan",
"road_number": null,
"road_class": 8,
"speed_limit_kmh": 30,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 1.83,
"fallback": false
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m45nmn6mfey8z9wvaf31nb9n",
"dataset": "nvdb",
"source_date": "2026-10-05",
"built_at": "2026-10-05T09:17:11.764Z",
"graph": "snap_01m45nmnryfey8z9x29xcd08tq"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
},
{
"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-05"
}
]
}
}
```
A test key's answer: the addresses are synthetic (`provenance.synthetic: true`), and `road` is NVDB's.
`road` carries `speed_limit_kmh` (the base limit; `speed_limit_varies` is true when a limit bound to certain times or vehicles also applies), `owner` (`state`, `municipal` or `private`: who keeps the road, never who by name), `surface` (`paved` or `gravel`), `municipality_code` (the kommunkod NVDB registers the road in, which near a kommun border can differ from the address's), `road_number`, `road_class` (0 to 9) and `distance_m`.
## The road is not an address
The road has no id and adds nothing to the result count. `fallback` is true when no address in `results` is within 50 m of the coordinate: then the road, not an address, is what to store. In the answer above it is false, since Järngatan 2 is 2 m away, and the road's name, Roslagsgatan, is not the address's street.
## What it is used for
- Who keeps the road to an address: the state, the kommun or a private road association, for snow clearing and maintenance questions.
- The speed limit where a vehicle was, for trip logs and telematics.
- Gravel or paved, for deliveries and services that care.
`POST /v1/route` gives the facts you ask for on each segment of a route, such as `speed_limit`, `road_owner`, `surface` and `road_class`, with the segment's road reference.
## Price
Test keys are free on every plan and answer from the synthetic dataset. Live calls need a paid plan and an account approved for live access. All prices are in Swedish kronor, excl. VAT.
| Plan | Price, excl. VAT | Includes |
| --- | --- | --- |
| Utvecklare | SEK 0 | 2,000 calculations a month; 1,000 test requests a day; Test addresses in the same format as live data; Jobs are included from Bas |
| Bas | SEK 490 a month | 5,000 calculations/month included; 25,000 live calls a month; 10,000 addresses a day; +50,000 calculations/month as an add-on |
| Pro | SEK 1,990 a month | 50,000 calculations/month included; 200,000 live calls a month; 50,000 addresses a day |
| Avtal | by quote | Vendors: priced per municipality; Operators: priced per vehicle; Network version and source in every answer; The API runs in the EU; Data processing agreement: draft on request |
[Create a test key](https://kartpunkt.se/konto) on your account page (the account pages are in Swedish), read [how to start](https://kartpunkt.se/en/developers), or see [all plans](https://kartpunkt.se/en/pricing).
---
# Kartpunkt compared
> Kartpunkt beside Belägenhetsadress Direkt, Google's Geocoding API and Adresser.online: price, data, terms, and what each does that the others don't.
>
> https://kartpunkt.se/en/compare
How Kartpunkt compares with the services developers ask about. Each page says when we checked the other service, links our sources, marks what we could not check, and says what the other service does that Kartpunkt does not. Prices and terms change: read the linked pages before you decide.
## Comparisons
- [Kartpunkt or Lantmäteriet's Belägenhetsadress Direkt](https://kartpunkt.se/en/compare/lantmateriet-direkt): the same register, from the source or through Kartpunkt.
- [Kartpunkt or Google's Geocoding API](https://kartpunkt.se/en/compare/google-geocoding): the world, or Sweden's official register.
- [Kartpunkt or Adresser.online](https://kartpunkt.se/en/compare/adresser-online): four Nordic countries, or Sweden with roads.
---
# Kartpunkt or Belägenhetsadress Direkt
> Belägenhetsadress Direkt is Lantmäteriet's own API for the address register, at 0 kr. What Kartpunkt adds on the same register, and when Direkt fits better.
>
> https://kartpunkt.se/en/compare/lantmateriet-direkt
Belägenhetsadress Direkt is Lantmäteriet's own API for the same register Kartpunkt serves. It costs 0 kr for those whose use Lantmäteriet approves. Kartpunkt reads the same register from Lantmäteriet's deliveries and charges for what it adds on top.
## Side by side
| | Belägenhetsadress Direkt | Kartpunkt |
| --- | --- | --- |
| Data | Lantmäteriet's address register, from the source | Lantmäteriet's address register, from Lantmäteriet's deliveries |
| Freshness | The register as Lantmäteriet holds it | Lantmäteriet's weekly deliveries, checked for daily; every answer states the date of its data |
| Price | 0 kr | Utvecklare free with test keys; live data from SEK 490 a month, excl. VAT |
| Access | Your own application to Lantmäteriet; use bound by the purpose it approves | Test keys at sign-in; live data under an agreement with Kartpunkt, whose access Lantmäteriet granted on 2026-09-23 |
| Search | Step-wise autocomplete and free-text search | Search that tolerates typos, with `match_type` on each hit |
| Reverse geocoding | The nearest address to a point | The nearest addresses, one per street on request, with the road |
| Validation | Lookup by id | `issues` for what is wrong, never silently corrected |
| Property link | Yes | No |
| Roads, road distances, routes | Not in its technical description | From Trafikverket's NVDB |
| Documentation | Lantmäteriet's technical description | llms.txt and an OpenAPI 3.1 document for coding agents |
## When Direkt fits better
- You want the register at no cost and can go through Lantmäteriet's review of your purpose yourself.
- You need the link from an address to its property, which Kartpunkt does not serve.
- You want no service between you and the source.
## When Kartpunkt fits
- You want to build today on test keys, without an application of your own.
- You need search that tolerates typos, validation that names what is wrong, or change checks for addresses you stored.
- You need roads, road distances or routes from NVDB in the same API.
- You want documentation a coding agent can follow.
## Sources
- Lantmäteriet, [Avgifter och leveransinformation för geodata](https://www.lantmateriet.se/globalassets/geodata/geodataprodukter/avgifter_och_leveransinformation_for_geodata.pdf) (fee document v2.31, 2026-05-29, pages 17 and 27): Belägenhetsadress Direkt at 0 kr. Checked 2026-09-24.
- Lantmäteriet, [Belägenhetsadress Direkt, technical description 4.2](https://geotorget.lantmateriet.se/dokument/projects/belaegenhetsadress-direkt/released/4.2/Teknisk-beskrivning.html): its operations. Checked 2026-09-16.
Unverified: we have not called Direkt, so we make no claim about how its search handles misspellings, and we read its validation and the absence of roads from the technical description only.
---
# Kartpunkt or Google's Geocoding API
> Google's Geocoding API covers the world; Kartpunkt covers Sweden from Lantmäteriet's register. Price, and the terms for storing and mapping results, as checked.
>
> https://kartpunkt.se/en/compare/google-geocoding
Google's Geocoding API covers the world. Kartpunkt covers only Sweden, from Lantmäteriet's official address register, with roads from Trafikverket's NVDB. This page compares price and the terms for storing and showing results, as we read them. It is not legal advice.
## Side by side
| | Google Geocoding API | Kartpunkt |
| --- | --- | --- |
| Coverage | The world | Sweden |
| Source | Google's own data | Lantmäteriet's address register; roads from Trafikverket's NVDB |
| Price | USD 5.00 per 1,000 requests, the first 10,000 a month free, lower at volume | Bas SEK 490 a month for 25,000 live calls, Pro SEK 1,990 a month for 200,000, excl. VAT |
| Storing results | The address may be kept for the end user's own use, per end user, not shared across users; coordinates at most 30 days | May be stored for the matter the lookup concerns and shared between your users and systems, with the attribution |
| Showing results on a map | In the EEA, no Geocoding content other than coordinates and place IDs on any map | On any map, with the attribution |
| Places and businesses | Yes | No: street addresses only |
| Roads | Not in the Geocoding API | Speed limit, road owner and surface; road distances and routes |
| Hosting | Google, a US company | Hetzner in Helsinki, in the EU |
## When Google fits better
- You need addresses outside Sweden, or the names of places and businesses.
- You already build on Google Maps Platform and show results on a Google map.
## When Kartpunkt fits
- You need the official Swedish address from Lantmäteriet's register, with a stable id and a way to check whether it changed.
- You need roads, road distances or routes from NVDB.
- You want the API that receives drivers' positions or your customers' addresses to run only in the EU.
## Sources
- Google, [Google Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing): Geocoding and its free monthly calls. Checked 2026-09-24.
- Google, [Google Maps Platform Service Specific Terms](https://cloud.google.com/maps-platform/terms/maps-service-terms) (version of 2026-06-10, section 6.3 on caching, and the EEA terms' section 6.1 on maps). Read 2026-09-23.
Unverified: this is our reading of Google's terms, which no lawyer has reviewed. We have not measured Google's accuracy for Swedish addresses and make no claim about it.
---
# Kartpunkt or Adresser.online
> Adresser.online sells address autocomplete and validation for Sweden, Norway, Denmark and Finland. Kartpunkt covers Sweden, with reverse geocoding and roads.
>
> https://kartpunkt.se/en/compare/adresser-online
Adresser.online sells address autocomplete and validation for Sweden, Norway, Denmark and Finland from official registers. Kartpunkt covers only Sweden, and adds reverse geocoding and roads from NVDB.
## Side by side
| | Adresser.online | Kartpunkt |
| --- | --- | --- |
| Coverage | Sweden, Norway, Denmark and Finland | Sweden |
| Autocomplete | Yes (Suggest) | Yes (`/v1/geocode`) |
| Validation | Yes (Complete) | Yes (`/v1/addresses/validate`) |
| Reverse geocoding | Not mentioned on its site | Yes (`/v1/reverse-geocode`) |
| Other data | Grid area and grid owner (nätområde, nätägare), geodata | Roads, road distances and routes from NVDB |
| Source | Official registers, updated at least weekly (its words) | Lantmäteriet's address register |
| Price | Small SEK 499 a month (10,000 Suggest and 3,000 Complete), Medium SEK 1,499 (50,000 and 15,000), Large SEK 3,499 (200,000 and 60,000) | Bas SEK 490 a month (25,000 live calls), Pro SEK 1,990 a month (200,000), excl. VAT |
| Trying it | A 5-day trial | Free test keys on the synthetic dataset |
| Hosting | Not stated on its site | Hetzner in Helsinki, in the EU; nothing you send is logged |
## When Adresser.online fits better
- You need addresses in Norway, Denmark or Finland too.
- You need grid area or grid owner for an address.
- You want to try it on real addresses rather than a synthetic dataset.
## When Kartpunkt fits
- You need reverse geocoding: from a coordinate to an address.
- You need roads, road distances or routes from NVDB.
- You need to know where the API runs, and that nothing you send is logged.
- You want documentation a coding agent can follow.
## Sources
- Adresser.online, [adresser.online](https://adresser.online/): its products, plans and prices. Checked 2026-09-16.
Unverified: we did not ask Adresser.online whether it offers reverse geocoding, where it is hosted, or whether its prices include VAT.
---
Utvecklare
# Kom igång med Kartpunkts API
Kartpunkt ger ert system svenska vägavstånd, färdvägar och adresser ur Trafikverkets NVDB och Lantmäteriets adressregister. Ni anropar API:et från er backend med en nyckel, eller låter er kodagent skriva integrationen. Varje svar anger källa och nätversion.
## Dokumentation
- **För kodagenter:** [https://kartpunkt.se/llms.txt](https://kartpunkt.se/llms.txt). Det en kodagent behöver för att skriva integrationen: operationerna, fälten, felkoderna och reglerna.
- **OpenAPI-dokumentet:** [https://api.kartpunkt.se/openapi.json](https://api.kartpunkt.se/openapi.json), för verktyg som genererar klienter och för den som vill läsa schemat.
- **API-referens:** [https://api.kartpunkt.se/docs](https://api.kartpunkt.se/docs), samma dokument att läsa i webbläsaren. Där provar du varje operation med en testnyckel som du skriver in där, i din egen webbläsare.
- **API:ets basadress:** `https://api.kartpunkt.se`
Dokumenten genereras ur API:ets definition. Fälten och felkoderna står där, inte här.
## Tre steg till första svaret
1. [Logga in](https://kartpunkt.se/logga-in) med Google. Första gången skapas ditt konto.
2. Skapa en testnyckel på [kontosidan](https://kartpunkt.se/konto). Den fungerar direkt och visas bara en gång.
3. Lägg nyckeln i miljövariabeln `KARTPUNKT_API_KEY`, till exempel i projektets `.env`. Koden skickar den som `Authorization: Bearer` eller `x-api-key`.
Kontrollera nyckeln: anropet nedan svarar 200 med en testnyckel.
```
curl -H "Authorization: Bearer $KARTPUNKT_API_KEY" https://api.kartpunkt.se/v1/postcodes/75331
```
## Prompt till er kodagent
Klistra in prompten i er kodagent. Agenten läser llms.txt, hämtar nyckeln ur `KARTPUNKT_API_KEY` och skriver koden.
Prompt
Integrera Kartpunkt (vägavstånd, färdvägar och officiella svenska adresser via API) i det här projektet. 1. Läs https://kartpunkt.se/llms.txt innan du skriver kod. 2. API-nyckeln finns i miljövariabeln KARTPUNKT\_API\_KEY. Be mig aldrig klistra in den i chatten. Kontrollera den först med GET /v1/postcodes/75331, som ska svara 200. 3. Börja med POST /v1/distances från våra startpunkter till våra mål. Spara varje vägavstånd med provenance.network.id, så att det är dokumenterat och daterat med nätversion. 4. Visa källhänvisningen från svaret där resultaten visas.
## Testnyckel och livenyckel
**En testnyckel** börjar med `kp_test_`. Du skapar den själv på kontosidan, utan godkännande. Den svarar från testdata: påhittade adresser med samma form som livedata, och varje svar är märkt `provenance.synthetic: true`. Märkningen gäller adresserna: vägnätet är NVDB:s riktiga med båda nycklarna, och `provenance.sources` säger `"synthetic"` för testdatas adresser och `"nvdb"` för vägnätet.
**En livenyckel** börjar med `kp_live_` och svarar från Lantmäteriets livedata på konton som är godkända för livedata, vilket kräver ett avtal med oss. Du ber om det via [kontaktsidan](https://kartpunkt.se/kontakt). På ett konto som inte är godkänt för livedata svarar en livenyckel 403 `live_access_required`.
Att gå från testnyckel till livenyckel ändrar bara nyckeln i `KARTPUNKT_API_KEY`. Koden är densamma.
## Nyckeln stannar på er server
En nyckel visas en gång, när du skapar den. Vi sparar den bara hashad och kan inte visa den igen. Lägg den i `KARTPUNKT_API_KEY`, där koden och kodagenten hittar den.
Klistra aldrig in nyckeln i en chatt med en kodagent, i ett mejl eller i koden. Har en nyckel ändå hamnat fel, återkalla den på kontosidan och skapa en ny.
Anropa API:et från er backend, aldrig från kod som körs i webbläsaren. Där skulle nyckeln läcka till var och en som öppnar sidan, och API:et svarar inte på anrop från webbläsare.
## Svaren och felen
Varje svar har `provenance`, ett jobb i sitt svar när jobbet är klart. Där står varje datakällas källhänvisning i `attribution`, och `synthetic` säger att en testnyckel svarade: adresserna är då testdata, vägnätet NVDB:s. Varje adress har ett stabilt `id` och en färdig adressrad i `label`. Spara id och label, och visa källhänvisningen där adresserna visas.
Spara `provenance.network.id` med svaret, så är det dokumenterat och daterat med nätversion. Medan nätversionen används ger samma anrop med `network: { id }` samma svar. På en äldre nätversion går det inte att räkna i dag.
Varje fel är `{ error, message, doc_url }`: en stabil felkod och ett meddelande som säger vad som ska göras härnäst, skrivet så att en kodagent kan följa det, med en länk till felkodens sida; ett ogiltigt anrop namnger också fältet i `param`. Fälten och felkoderna står i [llms.txt](https://kartpunkt.se/llms.txt).
## Gränser
Anropen är begränsade per minut och per dygn, så att ingen kan kopiera adressregistret. Ett anrop över en gräns får svaret 429 med headern `Retry-After`: vänta så många sekunder och försök igen.
Ett anrop har också en största storlek. Ett större anrop får ett fel som säger vad som ska ändras, och samma indata går som jobb, från Bas:
Operation
Direkt
Som jobb
Ruttoptimering och ruttkontroll
25 stopp, 12 sändningar, 5 fordon, 30 platser
500 stopp, 250 sändningar, 50 fordon, 600 platser
Färdväg
10 delmål (via)
10 delmål (via)
Vägavstånd
1 000 startpunkter, 200 startpunkter × mål (20 med barriär)
50 000 startpunkter, 50 000 startpunkter × mål
Avståndsmatris
625 celler; 8 startpunkter med fordon, prefer eller avoid
10 000 celler
Räckvidd
4 tider eller avstånd per anrop
4 tider eller avstånd per anrop
Övriga operationers gränser står i [llms.txt](https://kartpunkt.se/llms.txt).
## Operationer
Operationerna finns under `/v1`, i API:ets grupper: adresser, vägar, färdvägar och avstånd, planering, kataloger och nätversioner, och jobb. Varje operation har en egen sida i API-referensen.
- **Sök**[`searchAddresses`](https://api.kartpunkt.se/docs/operations/searchAddresses.md)
Från en text till adresser, även felstavad.
- **Omvänd geokodning**[`reverseGeocode`](https://api.kartpunkt.se/docs/operations/reverseGeocode.md)
Från en koordinat till närmaste adresser och vägen intill.
- **Validera**[`validateAddress`](https://api.kartpunkt.se/docs/operations/validateAddress.md)
Pekar ut vad som är fel i en adress, utan att ändra den.
- **Ändring**[`checkAddressChanges`](https://api.kartpunkt.se/docs/operations/checkAddressChanges.md)
Vilka av era adresser som har ändrats eller avslutats sedan ett datum.
- **Postnummer**[`lookupPostcode`](https://api.kartpunkt.se/docs/operations/lookupPostcode.md)
Från ett postnummer till postorter och kommuner.
- **Adressuppslag**[`lookupAddress`](https://api.kartpunkt.se/docs/operations/lookupAddress.md)
Från ett sparat adress-id till adressen i dag eller vid ett datum.
- **Närmaste vägpunkt**[`snapPoints`](https://api.kartpunkt.se/docs/operations/snapPoints.md)
Flyttar punkter till närmaste väg en profil kan färdas på.
- **Väg**[`listRoadsNearby`](https://api.kartpunkt.se/docs/operations/listRoadsNearby.md)
Vägarna kring en punkt, med de vägfakta ni ber om.
- **Vägreferens**[`getRoad`](https://api.kartpunkt.se/docs/operations/getRoad.md)
En väg utifrån dess vägreferens, som pekar på samma väg i varje leverans där länken finns kvar.
- **Färdväg**[`planRoute`](https://api.kartpunkt.se/docs/operations/planRoute.md)
Färdvägen för en profil, i delsträckor med vägreferens och vägfakta; undviker sträckor ni anger.
- **Vägavstånd**[`measureDistances`](https://api.kartpunkt.se/docs/operations/measureDistances.md)
Från många startpunkter till ett mål, som kunder till en depå: avstånd och tid längs vägnätet, med de vägfakta ni ber om.
- **Närmaste hållplats**[`findNearest`](https://api.kartpunkt.se/docs/operations/findNearest.md)
De hållplatser som ligger närmast varje punkt längs vägnätet.
- **Avståndsmatris**[`buildMatrix`](https://api.kartpunkt.se/docs/operations/buildMatrix.md)
Avstånd och tid mellan varje startpunkt och varje mål.
- **Räckvidd**[`measureReach`](https://api.kartpunkt.se/docs/operations/measureReach.md)
Området som nås inom ett antal minuter eller meter från en punkt.
- **Spårmatchning**[`matchTrace`](https://api.kartpunkt.se/docs/operations/matchTrace.md)
Matchar ett GPS-spår mot vägarna det följer; spåret sparas inte.
- **Ruttoptimering**[`optimizeRoutes`](https://api.kartpunkt.se/docs/operations/optimizeRoutes.md)
Vilket fordon som besöker vilka stopp, i vilken ordning och när, inom kapacitet och tidsfönster.
- **Ruttkontroll**[`checkPlan`](https://api.kartpunkt.se/docs/operations/checkPlan.md)
Era egna rutter, i er ordning, med det som bryter mot kapacitet, tidsfönster och övriga villkor.
- **Profil**[`listProfiles`](https://api.kartpunkt.se/docs/operations/listProfiles.md)
Profilerna vägarna beräknas för: gång, bil (kortaste och snabbaste väg), lastbil och buss.
- **Vägfakta**[`listFacts`](https://api.kartpunkt.se/docs/operations/listFacts.md)
Vägfakta ur NVDB som en färdväg eller väg kan redovisa, med slag och enhet.
- **Datakälla**[`listSources`](https://api.kartpunkt.se/docs/operations/listSources.md)
Varje datakälla med licens, källhänvisning och vad dess data får användas till.
- **Vektorrutor**[`listTilesets`](https://api.kartpunkt.se/docs/operations/listTilesets.md)
NVDB:s vägar som vektorrutor att visa i er egen karta.
- **Nätversion**[`listNetworks`](https://api.kartpunkt.se/docs/operations/listNetworks.md)
Vägnätets versioner: den som används nu och de äldre som finns kvar.
- **Nätversion**[`getNetwork`](https://api.kartpunkt.se/docs/operations/getNetwork.md)
En nätversion: NVDB-datum och vilka profiler den kan svara på.
- **Jobb**[`submitJob`](https://api.kartpunkt.se/docs/operations/submitJob.md)
Lämnar ett jobb.
- **Jobb**[`getJob`](https://api.kartpunkt.se/docs/operations/getJob.md)
Status och svar.
- **Jobb**[`cancelJob`](https://api.kartpunkt.se/docs/operations/cancelJob.md)
Avbryter.
## Ruttoptimering: ett exempel
Skicka fordonen och stoppen till `/v1/optimize` och få tillbaka vilket fordon som kör till vilka stopp, i vilken ordning och när. Varje stopp som inget fordon tar står i `unassigned` med en felkod som säger vad som ska ändras.
En plan direkt:
```
curl -X POST https://api.kartpunkt.se/v1/optimize \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vehicles":[{"id":"fordon-1","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}},{"id":"fordon-2","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}}],"stops":[{"id":"s1","at":{"lat":63.720434,"lon":19.348226,"street":"Skolvägen"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s2","at":{"lat":63.724508,"lon":19.339523,"street":"Norrgatan"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s3","at":{"lat":63.724205,"lon":19.337043,"street":"Snedgatan"},"pickup":{"seats":1},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]}],"objective":"vehicles_then_duration","profile":"car_fastest"}'
```
En större plan som jobb: samma indata under `"operation": "optimize"`. Läs jobbet tills `status` är `succeeded`; planen står i `result`.
```
JOB_ID=$(curl -X POST https://api.kartpunkt.se/v1/jobs \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"operation":"optimize","input":{"vehicles":[{"id":"fordon-1","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}},{"id":"fordon-2","start":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"end":{"lat":63.72059,"lon":19.342166,"street":"Skolvägen"},"capacity":{"seats":8},"time_window":{"start":"2026-10-12T06:45:00+02:00","end":"2026-10-12T08:15:00+02:00"}}],"stops":[{"id":"s1","at":{"lat":63.720434,"lon":19.348226,"street":"Skolvägen"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s2","at":{"lat":63.724508,"lon":19.339523,"street":"Norrgatan"},"pickup":{"seats":2},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]},{"id":"s3","at":{"lat":63.724205,"lon":19.337043,"street":"Snedgatan"},"pickup":{"seats":1},"service_s":60,"time_windows":[{"start":"2026-10-12T07:00:00+02:00","end":"2026-10-12T07:45:00+02:00"}]}],"objective":"vehicles_then_duration","profile":"car_fastest"}}' | jq -r .id)
curl https://api.kartpunkt.se/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $KARTPUNKT_API_KEY"
```
Jobb ingår från Bas. På den kostnadsfria planen Utvecklare svarar `POST /v1/jobs` 403 `live_access_required`, även med en testnyckel; där går en plan direkt till `/v1/optimize`, inom gränserna ovan.
Punkterna ligger i testdatas påhittade ort Ruttby; med en nyckel räknas de på NVDB:s riktiga vägar, så svaret blir en annan plan än den som är inspelad på [/prova](https://kartpunkt.se/prova#ruttoptimering). Gränserna för en plan direkt och för ett jobb står under [Gränser](#granser).
> Från inloggning till första svaret i tre steg: testnyckel, dokumentation för kodagenter (llms.txt, OpenAPI) och en färdig prompt till er kodagent.
>
> https://kartpunkt.se/utvecklare
---
Utforska
# Utforska API:et
Tio delar av API:et, var och en med ett förslag att köra och anropet för er egen kod. Kartorna räknar live på Trafikverkets vägnät (NVDB) genom demon, utan konto och nyckel, och det du klickar loggas inte. Adresserna visas med inspelade svar från testdata.
1. [1. Gångväg som undviker snabba vägar](#gangvag)
2. [2. Avstånd från 20 punkter till ett mål](#punkter)
3. [3. Stäng av en väg](#avstangning)
4. [4. Räckvidd](#rackvidd)
5. [5. Spårmatchning](#spar)
6. [6. Gatans eget läge](#gata)
7. [7. Avståndsmatris](#matris)
8. [8. Vektorrutor](#rutor)
9. [9. Adresser i detalj](#adresser)
10. [10. Dokumenterat och daterat med nätversion](#samma)
1 · POST /v1/route med prefer
## Gångväg som undviker snabba vägar
Den kortaste gångvägen och gångvägen som undviker vägar med 70+ km/h, mellan samma två punkter. Se vad omvägen kostar i meter och minuter.
Eller klicka ut en start och ett mål på kartan.
Två gångvägar
- kortaste
- undviker snabba vägar
- på väg med 70+ km/h
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/route \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"origin":{"lat":57.469,"lon":12.096},"destination":{"lat":57.4874,"lon":12.0873},"profile":"walk","facts":["speed_limit","fast_road","unlit_outside_built_up","lit","crossings_without_refuge","crossings_unknown_refuge","level_crossings","traffic_volume"],"geometry":"geojson","prefer":[{"fact":"fast_road"}]}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/route", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"origin": {
"lat": 57.469,
"lon": 12.096
},
"destination": {
"lat": 57.4874,
"lon": 12.0873
},
"profile": "walk",
"facts": [
"speed_limit",
"fast_road",
"unlit_outside_built_up",
"lit",
"crossings_without_refuge",
"crossings_unknown_refuge",
"level_crossings",
"traffic_volume"
],
"geometry": "geojson",
"prefer": [
{
"fact": "fast_road"
}
]
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
2 · POST /v1/distances och POST /v1/nearest
## Avstånd från 20 punkter till ett mål
Ett mål, som ett kontor, en depå eller ett utlämningsställe, och 20 punkter runt det, i två anrop: varje punkts gångavstånd, det NVDB registrerar längs vägen och de närmaste busshållplatserna. Punkterna räknas fram av sidan och är inga adresser.
Eller klicka på kartan där målet ligger: 20 punkter läggs runt det.
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/distances \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"destinations":[{"point":{"lat":57.4874,"lon":12.0873}}],"origins":[{"id":"1","point":{"lat":57.490479,"lon":12.0873}},{"id":"2","point":{"lat":57.484244,"lon":12.09268}},{"id":"3","point":{"lat":57.487856,"lon":12.077639}},{"id":"4","point":{"lat":57.491051,"lon":12.096161}},{"id":"5","point":{"lat":57.480804,"lon":12.085129}},{"id":"6","point":{"lat":57.493584,"lon":12.079982}},{"id":"7","point":{"lat":57.485347,"lon":12.101511}},{"id":"8","point":{"lat":57.483505,"lon":12.073348}},{"id":"9","point":{"lat":57.495814,"lon":12.093017}},{"id":"10","point":{"lat":57.478675,"lon":12.094001}},{"id":"11","point":{"lat":57.491595,"lon":12.070623}},{"id":"12","point":{"lat":57.490493,"lon":12.105645}},{"id":"13","point":{"lat":57.478096,"lon":12.077269}},{"id":"14","point":{"lat":57.498297,"lon":12.082843}},{"id":"15","point":{"lat":57.480759,"lon":12.104874}},{"id":"16","point":{"lat":57.485868,"lon":12.0653}},{"id":"17","point":{"lat":57.496797,"lon":12.102034}},{"id":"18","point":{"lat":57.474767,"lon":12.088272}},{"id":"19","point":{"lat":57.496607,"lon":12.070254}},{"id":"20","point":{"lat":57.486785,"lon":12.112065}}],"profile":"walk","facts":["speed_limit","fast_road","unlit_outside_built_up","lit","crossings_without_refuge","crossings_unknown_refuge","level_crossings","traffic_volume"]}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/distances", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"destinations": [
{
"point": {
"lat": 57.4874,
"lon": 12.0873
}
}
],
"origins": [
{
"id": "1",
"point": {
"lat": 57.490479,
"lon": 12.0873
}
},
{
"id": "2",
"point": {
"lat": 57.484244,
"lon": 12.09268
}
},
{
"id": "3",
"point": {
"lat": 57.487856,
"lon": 12.077639
}
},
{
"id": "4",
"point": {
"lat": 57.491051,
"lon": 12.096161
}
},
{
"id": "5",
"point": {
"lat": 57.480804,
"lon": 12.085129
}
},
{
"id": "6",
"point": {
"lat": 57.493584,
"lon": 12.079982
}
},
{
"id": "7",
"point": {
"lat": 57.485347,
"lon": 12.101511
}
},
{
"id": "8",
"point": {
"lat": 57.483505,
"lon": 12.073348
}
},
{
"id": "9",
"point": {
"lat": 57.495814,
"lon": 12.093017
}
},
{
"id": "10",
"point": {
"lat": 57.478675,
"lon": 12.094001
}
},
{
"id": "11",
"point": {
"lat": 57.491595,
"lon": 12.070623
}
},
{
"id": "12",
"point": {
"lat": 57.490493,
"lon": 12.105645
}
},
{
"id": "13",
"point": {
"lat": 57.478096,
"lon": 12.077269
}
},
{
"id": "14",
"point": {
"lat": 57.498297,
"lon": 12.082843
}
},
{
"id": "15",
"point": {
"lat": 57.480759,
"lon": 12.104874
}
},
{
"id": "16",
"point": {
"lat": 57.485868,
"lon": 12.0653
}
},
{
"id": "17",
"point": {
"lat": 57.496797,
"lon": 12.102034
}
},
{
"id": "18",
"point": {
"lat": 57.474767,
"lon": 12.088272
}
},
{
"id": "19",
"point": {
"lat": 57.496607,
"lon": 12.070254
}
},
{
"id": "20",
"point": {
"lat": 57.486785,
"lon": 12.112065
}
}
],
"profile": "walk",
"facts": [
"speed_limit",
"fast_road",
"unlit_outside_built_up",
"lit",
"crossings_without_refuge",
"crossings_unknown_refuge",
"level_crossings",
"traffic_volume"
]
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
3 · POST /v1/route med avoid
## Stäng av en väg
Räkna en färdväg, rita en sträcka att undvika över en väg och se omvägen.
Klicka ut en start och ett mål på kartan.
Färdvägar
- färdvägen
- väg som undviker sträckan
- sträcka att undvika
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/route \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"origin":{"lat":57.469,"lon":12.096},"destination":{"lat":57.4874,"lon":12.0873},"profile":"walk","geometry":"geojson"}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/route", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"origin": {
"lat": 57.469,
"lon": 12.096
},
"destination": {
"lat": 57.4874,
"lon": 12.0873
},
"profile": "walk",
"geometry": "geojson"
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
4 · POST /v1/reach
## Räckvidd
Hur långt du når på 5, 10 och 15 minuter från en punkt, till fots eller med bil.
Räckvidd
- 5 min
- 10 min
- 15 min
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/reach \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"origin":{"lat":59.7905,"lon":17.65},"profile":"walk","budget":{"duration_s":[300,600,900]}}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/reach", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"origin": {
"lat": 59.7905,
"lon": 17.65
},
"profile": "walk",
"budget": {
"duration_s": [
300,
600,
900
]
}
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
5 · POST /v1/trace
## Spårmatchning
Rita en linje på kartan och se vilka vägar den följde, med vägreferens och fakta för varje väg.
Klicka längs en väg för att rita, högst 200 punkter och 10 km. 0 punkter ritade.
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/trace \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"points":[{"lat":57.469,"lon":12.096},{"lat":57.4874,"lon":12.0873}],"profile":"car_shortest","gps_accuracy_m":25,"facts":["speed_limit","road_owner","surface","road_name","road_number"],"geometry":"geojson"}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/trace", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"points": [
{
"lat": 57.469,
"lon": 12.096
},
{
"lat": 57.4874,
"lon": 12.0873
}
],
"profile": "car_shortest",
"gps_accuracy_m": 25,
"facts": [
"speed_limit",
"road_owner",
"surface",
"road_name",
"road_number"
],
"geometry": "geojson"
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
6 · POST /v1/snap med street
## Gatans eget läge
En punkt mellan två vägar möter den närmaste, ofta den större. Med gatans namn möter den sin egen gata. Samma punkt skickas med och utan namnet.
Gatans namn
Klicka på kartan där punkten ligger. Gatunamnet jämförs med NVDB:s vägnamn inom 500 m från punkten. Det slås aldrig upp som en adress, och det loggas inte.
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/snap \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"points":[{"lat":57.4874,"lon":12.0873},{"lat":57.4874,"lon":12.0873,"street":"Sommarlustvägen"}],"profile":"car_shortest"}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/snap", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"points": [
{
"lat": 57.4874,
"lon": 12.0873
},
{
"lat": 57.4874,
"lon": 12.0873,
"street": "Sommarlustvägen"
}
],
"profile": "car_shortest"
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
7 · POST /v1/matrix
## Avståndsmatris
Upp till fem starter och fem mål, och avstånd och tid mellan varje start och varje mål.
0 starter och 0 mål, högst 5 av varje.
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/matrix \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"origins":[{"lat":57.469,"lon":12.096},{"lat":57.4995,"lon":12.0745},{"lat":57.4812,"lon":12.0428}],"destinations":[{"lat":57.4874,"lon":12.0873},{"lat":57.4876,"lon":12.0681},{"lat":57.4606,"lon":12.0716}],"profile":"car_fastest"}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/matrix", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"origins": [
{
"lat": 57.469,
"lon": 12.096
},
{
"lat": 57.4995,
"lon": 12.0745
},
{
"lat": 57.4812,
"lon": 12.0428
}
],
"destinations": [
{
"lat": 57.4874,
"lon": 12.0873
},
{
"lat": 57.4876,
"lon": 12.0681
},
{
"lat": 57.4606,
"lon": 12.0716
}
],
"profile": "car_fastest"
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
8 · GET /v1/tilesets
## Vektorrutor
Sveriges vägar ur NVDB som vektorrutor, färgade efter hastighet, väghållare, slitlager eller bärighetsklass, och koden för att visa dem i er egen karta.
Färga vägarna efter
Hastighet
- ≤ 30 km/h
- 40 km/h
- 50 km/h
- 60 km/h
- 70–80 km/h
- 90–120 km/h
- ingen gräns
Svaret räknas live på NVDB:s vägnät när du väljer förslaget eller klickar på kartan, och visas här. Det kräver JavaScript; anropet nedan fungerar utan.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/tilesets \
-H "Authorization: Bearer $KARTPUNKT_API_KEY"
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/tilesets", {
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
},
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när du har valt förslaget eller klickat på kartan.
9 · adressanropen
## Adresser i detalj
Adressanropen med inspelade svar från testdata: demon läser inga adresser, så här skickas inget. Med en testnyckel får du samma sorts svar från ert eget system.
Inspelat svar från testdata
### Validering med förslag
En felstavad gata: svaret säger vad som är fel och föreslår adresser som finns, utan att rätta tyst.
- Ogiltig: gatan finns inte som den skrevs.
- Förslag: Östra Ågatan 12B, 757 97 Uppsala; Östra Ågatan 12, 757 97 Uppsala; Östra Ågatan 12A, 757 97 Uppsala.
Syntetiska testadresser från Kartpunkt, inte verkliga adresser.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/addresses/validate \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"street":"Östra Ågatn","number":12,"letter":"B","locality":"Uppsala"}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/addresses/validate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"street": "Östra Ågatn",
"number": 12,
"letter": "B",
"locality": "Uppsala"
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret
- provenance: källorna, deras villkor och nätversionen svaret räknades på
- ref: vägreferensen, samma sträcka i varje nätversion
- facts: det NVDB registrerar längs vägen
```
{
"valid": false,
"match": null,
"issues": [
"street_not_found"
],
"suggestions": [
{
"id": "se.syn.syn-0000t9b",
"type": "address",
"label": "Östra Ågatan 12B, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"position_type": "entrance",
"match_type": "corrected",
"score": 6.096714830317912
},
{
"id": "se.syn.syn-0000t99",
"type": "address",
"label": "Östra Ågatan 12, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": null,
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92239328453523,
"lon": 17.582887649142425
},
"position_type": "building",
"match_type": "partial",
"score": 5.596714830317912
},
{
"id": "se.syn.syn-0000t9a",
"type": "address",
"label": "Östra Ågatan 12A, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "A",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238919581831,
"lon": 17.58281664402885
},
"position_type": "entrance",
"match_type": "partial",
"score": 5.596714830317912
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-06"
}
]
}
}
```
Inspelat svar från testdata
### Koordinat till adress, med vägen
De närmaste adresserna till en punkt, och vägen intill från NVDB.
- Östra Ågatan 12B, 757 97 Uppsala (0 m)
- Östra Ågatan 12A, 757 97 Uppsala (4 m)
- Östra Ågatan 12, 757 97 Uppsala (8 m)
- Vägen intill: Östra Ågatan, 50 km/h, 22 m bort.
Syntetiska testadresser från Kartpunkt, inte verkliga adresser. · Vägdata från NVDB, Trafikverket, CC0 1.0
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/reverse-geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":59.92238511056452,"lon":17.582745460249157},"limit":3}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/reverse-geocode", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"limit": 3
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret
- provenance: källorna, deras villkor och nätversionen svaret räknades på
- ref: vägreferensen, samma sträcka i varje nätversion
- facts: det NVDB registrerar längs vägen
```
{
"results": [
{
"id": "se.syn.syn-0000t9b",
"type": "address",
"label": "Östra Ågatan 12B, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 0
},
{
"id": "se.syn.syn-0000t9a",
"type": "address",
"label": "Östra Ågatan 12A, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "A",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238919581831,
"lon": 17.58281664402885
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 4.01
},
{
"id": "se.syn.syn-0000t99",
"type": "address",
"label": "Östra Ågatan 12, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": null,
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92239328453523,
"lon": 17.582887649142425
},
"position_type": "building",
"match_type": null,
"score": 1,
"distance_m": 8
}
],
"road": {
"name": "Östra Ågatan",
"road_number": null,
"road_class": 6,
"speed_limit_kmh": 50,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 22.01,
"fallback": false
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m481nr59fezs7pcmbff9jjwq",
"dataset": "nvdb",
"source_date": "2026-10-06",
"built_at": "2026-10-06T07:25:59.337Z",
"graph": "snap_01m481nrr6fezs7pcz0p337t4y"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-06"
},
{
"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-06"
}
]
}
}
```
Inspelat svar från testdata
### En adress per gata
Med distinct: "street" den närmaste adressen på varje gata, för en körjournal som ska välja gatan den körde på.
- Östra Ågatan 12B, 757 97 Uppsala (0 m)
- Baronhagsallén 9, 752 91 Marksjö (1,4 km)
- Lönnholmsvägen 1A, 757 12 Uppsala (1,5 km)
- Lammhagsvägen 28, 752 91 Marksjö (1,6 km)
- Vägen intill: Östra Ågatan, 50 km/h, 22 m bort.
Syntetiska testadresser från Kartpunkt, inte verkliga adresser. · Vägdata från NVDB, Trafikverket, CC0 1.0
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/reverse-geocode \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":59.92238511056452,"lon":17.582745460249157},"distinct":"street","limit":4,"radius_m":2000}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/reverse-geocode", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"distinct": "street",
"limit": 4,
"radius_m": 2000
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret
- provenance: källorna, deras villkor och nätversionen svaret räknades på
- ref: vägreferensen, samma sträcka i varje nätversion
- facts: det NVDB registrerar längs vägen
```
{
"results": [
{
"id": "se.syn.syn-0000t9b",
"type": "address",
"label": "Östra Ågatan 12B, 757 97 Uppsala",
"street": "Östra Ågatan",
"number": 12,
"letter": "B",
"postcode": "75797",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903801",
"district": "Uppsala norra",
"point": {
"lat": 59.92238511056452,
"lon": 17.582745460249157
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 0
},
{
"id": "se.syn.syn-0000wn0",
"type": "address",
"label": "Baronhagsallén 9, 752 91 Marksjö",
"street": "Baronhagsallén",
"number": 9,
"letter": null,
"postcode": "75291",
"locality": "Marksjö",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903800",
"district": "Uppsala centrum",
"point": {
"lat": 59.92495168896839,
"lon": 17.558161035972773
},
"position_type": "driveway",
"match_type": null,
"score": 1,
"distance_m": 1404.39
},
{
"id": "se.syn.syn-0000tas",
"type": "address",
"label": "Lönnholmsvägen 1A, 757 12 Uppsala",
"street": "Lönnholmsvägen",
"number": 1,
"letter": "A",
"postcode": "75712",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {
"lat": 59.909810524115365,
"lon": 17.591908582137883
},
"position_type": "entrance",
"match_type": null,
"score": 1,
"distance_m": 1491.78
},
{
"id": "se.syn.syn-0000wow",
"type": "address",
"label": "Lammhagsvägen 28, 752 91 Marksjö",
"street": "Lammhagsvägen",
"number": 28,
"letter": null,
"postcode": "75291",
"locality": "Marksjö",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903804",
"district": "Uppsala västra",
"point": {
"lat": 59.92674662080804,
"lon": 17.555509047055587
},
"position_type": "building",
"match_type": null,
"score": 1,
"distance_m": 1598.88
}
],
"road": {
"name": "Östra Ågatan",
"road_number": null,
"road_class": 6,
"speed_limit_kmh": 50,
"speed_limit_varies": false,
"owner": "municipal",
"surface": "paved",
"municipality_code": "0380",
"distance_m": 22.01,
"fallback": false
},
"provenance": {
"synthetic": true,
"network": {
"id": "snap_01m481nr59fezs7pcmbff9jjwq",
"dataset": "nvdb",
"source_date": "2026-10-06",
"built_at": "2026-10-06T07:25:59.337Z",
"graph": "snap_01m481nrr6fezs7pcz0p337t4y"
},
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-06"
},
{
"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-06"
}
]
}
}
```
Inspelat svar från testdata
### Vad som ändrats sedan ett datum
Sparade adress-id:n jämförda med vad som serveras nu: oförändrad, ändrad eller avslutad.
- se.syn.synh-0380-0001: ändrad 2026-10-06 (etikett, postnummer), nu Gränsbrovägen 1, 759 90 Gränsbro.
- se.syn.syn-0000t9b: oförändrad.
- se.syn.synh-riv-0002: avslutad adress sedan 2026-10-06.
Syntetiska testadresser från Kartpunkt, inte verkliga adresser.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/addresses/changes \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ids":["se.syn.synh-0380-0001","se.syn.syn-0000t9b","se.syn.synh-riv-0002"],"since":"2026-10-05"}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/addresses/changes", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"ids": [
"se.syn.synh-0380-0001",
"se.syn.syn-0000t9b",
"se.syn.synh-riv-0002"
],
"since": "2026-10-05"
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret
- provenance: källorna, deras villkor och nätversionen svaret räknades på
- ref: vägreferensen, samma sträcka i varje nätversion
- facts: det NVDB registrerar längs vägen
```
{
"results": [
{
"id": "se.syn.synh-0380-0001",
"status": "changed",
"changed_on": "2026-10-06",
"changed_fields": [
"label",
"postcode"
],
"address": {
"id": "se.syn.synh-0380-0001",
"type": "address",
"label": "Gränsbrovägen 1, 759 90 Gränsbro",
"street": "Gränsbrovägen",
"number": 1,
"letter": null,
"postcode": "75990",
"locality": "Gränsbro",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903804",
"district": "Uppsala västra",
"point": {
"lat": 59.94563949225021,
"lon": 17.770290601111526
},
"position_type": "building",
"match_type": null,
"score": 1
}
},
{
"id": "se.syn.syn-0000t9b",
"status": "unchanged"
},
{
"id": "se.syn.synh-riv-0002",
"status": "closed",
"closed_on": "2026-10-06"
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-06"
}
]
}
}
```
Inspelat svar från testdata
### Postnummer
Vilka postorter och kommuner ett postnummer används i.
- 75990 används i Gränsbro (Uppsala kommun, 12 adresser) och Gränsbro (Upplands Väsby kommun, 5 adresser).
Syntetiska testadresser från Kartpunkt, inte verkliga adresser.
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/postcodes/75990 \
-H "Authorization: Bearer $KARTPUNKT_API_KEY"
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/postcodes/75990", {
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
},
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret
- provenance: källorna, deras villkor och nätversionen svaret räknades på
- ref: vägreferensen, samma sträcka i varje nätversion
- facts: det NVDB registrerar längs vägen
```
{
"postcode": "75990",
"localities": [
{
"locality": "Gränsbro",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"addresses": 12
},
{
"locality": "Gränsbro",
"municipality_code": "0114",
"municipality": "Upplands Väsby",
"county_code": "01",
"county": "Stockholms län",
"addresses": 5
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-06"
}
]
}
}
```
Inspelat 2026-10-06 med en testnyckel (kp\_test\_). Testdata är syntetiska adresser som ser verkliga ut men inte finns.
10 · network: { id } och { as\_of }
## Dokumenterat och daterat med nätversion
Varje svar säger vilken nätversion det räknades på. Spara id:t med svaret: medan nätversionen används ger samma fråga samma svar.
Varje svar på vägnätet har `provenance.network.id`, nätversionen det räknades på, och `provenance.network.source_date`, datumet för NVDB-leveransen. Välj förslaget i Gångväg som undviker snabba vägar så visas id:t för ditt svar här. Samma id står i svarets huvud `X-Kartpunkt-Network`.
Spara id:t med svaret, så är det dokumenterat och daterat med nätversion. Medan nätversionen används ger `network: { id }` samma svar, och `network: { as_of }` med en tidpunkt väljer det nät som användes då. Är nätversionen inte längre den som används svarar `/v1/route` med `network_not_active`, och frågan går bara som ett jobb med `POST /v1/jobs` om nätversionens graf fungerar med dagens ruttmotor; annars slutar jobbet med `snapshot_engine_unavailable`. En nätversion som har tagits bort svarar `network_pruned`: fråga då utan `network` för den nätversion som används nu.
På en äldre nätversion går det inte att räkna i dag. Den enda som finns kvar är byggd för en tidigare ruttmotor, och de andra är borttagna. Sidan anropar därför ingen äldre version: den visar hur du sparar svaret med dess nätversion.
Samma nät, med id
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/route \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"origin":{"lat":57.469,"lon":12.096},"destination":{"lat":57.4874,"lon":12.0873},"profile":"walk","facts":["speed_limit","fast_road","unlit_outside_built_up","lit","crossings_without_refuge","crossings_unknown_refuge","level_crossings","traffic_volume"],"geometry":"geojson","network":{"id":"snap_…"}}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/route", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"origin": {
"lat": 57.469,
"lon": 12.096
},
"destination": {
"lat": 57.4874,
"lon": 12.0873
},
"profile": "walk",
"facts": [
"speed_limit",
"fast_road",
"unlit_outside_built_up",
"lit",
"crossings_without_refuge",
"crossings_unknown_refuge",
"level_crossings",
"traffic_volume"
],
"geometry": "geojson",
"network": {
"id": "snap_…"
}
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Svaret visas här när Gångväg som undviker snabba vägar har räknat en gångväg.
Det nät som användes vid en tidpunkt
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/route \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"origin":{"lat":57.469,"lon":12.096},"destination":{"lat":57.4874,"lon":12.0873},"profile":"walk","facts":["speed_limit","fast_road","unlit_outside_built_up","lit","crossings_without_refuge","crossings_unknown_refuge","level_crossings","traffic_volume"],"geometry":"geojson","network":{"as_of":"2026-10-01T12:00:00Z"}}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/route", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"origin": {
"lat": 57.469,
"lon": 12.096
},
"destination": {
"lat": 57.4874,
"lon": 12.0873
},
"profile": "walk",
"facts": [
"speed_limit",
"fast_road",
"unlit_outside_built_up",
"lit",
"crossings_without_refuge",
"crossings_unknown_refuge",
"level_crossings",
"traffic_volume"
],
"geometry": "geojson",
"network": {
"as_of": "2026-10-01T12:00:00Z"
}
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Sidan anropar ingen äldre nätversion.
En äldre nätversion, som jobb, när den går att räkna på
Se anropet och svaret
curl
```
curl https://api.kartpunkt.se/v1/jobs \
-H "Authorization: Bearer $KARTPUNKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"operation":"route","input":{"origin":{"lat":57.469,"lon":12.096},"destination":{"lat":57.4874,"lon":12.0873},"profile":"walk","facts":["speed_limit","fast_road","unlit_outside_built_up","lit","crossings_without_refuge","crossings_unknown_refuge","level_crossings","traffic_volume"],"geometry":"geojson","network":{"id":"snap_…"}}}'
```
TypeScript
```
const res = await fetch("https://api.kartpunkt.se/v1/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KARTPUNKT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"operation": "route",
"input": {
"origin": {
"lat": 57.469,
"lon": 12.096
},
"destination": {
"lat": 57.4874,
"lon": 12.0873
},
"profile": "walk",
"facts": [
"speed_limit",
"fast_road",
"unlit_outside_built_up",
"lit",
"crossings_without_refuge",
"crossings_unknown_refuge",
"level_crossings",
"traffic_volume"
],
"geometry": "geojson",
"network": {
"id": "snap_…"
}
}
}),
})
const answer = await res.json()
```
Skapa en testnyckel på kartpunkt.se/konto och lägg den i miljövariabeln KARTPUNKT\_API\_KEY i er egen miljö. Sidan här anropar demon utan nyckel.
Sidan anropar ingen äldre nätversion.
[Skapa testnyckel](https://kartpunkt.se/konto)
> Prova varje del av Kartpunkts API på NVDB:s vägnät: gångväg som undviker snabba vägar, avstånd från många punkter till ett mål, avstängda vägar, räckvidd, spårmatchning, matris och vektorrutor, med anropet för er egen kod.
>
> https://kartpunkt.se/utforska
---
Om Kartpunkt
# Sveriges vägnät och adresser, i ett API
Kartpunkt är ett API för vägar och adresser i Sverige, byggt på Trafikverkets vägnät NVDB och Lantmäteriets adressregister, och drivs av Zephyr Valley AB.
## Vem som driver Kartpunkt
Kartpunkt drivs av **Zephyr Valley AB**, org.nr 559220-6873, momsregistreringsnummer SE559220687301.
Bolaget erbjuder Kartpunkt till företag och offentliga verksamheter under avtal.
## Data och licenser
Vägnätet kommer ur Trafikverkets NVDB, under CC0. Adresserna kommer ur Lantmäteriets register Belägenhetsadress, som ges ut under licensen CC BY 4.0. Lantmäteriet beviljade Zephyr Valley AB åtkomst till registret 2026-09-23 (LM2026/143671). Varje svar med Lantmäteriets adresser har källhänvisning till Lantmäteriet, som licensen kräver.
Lantmäteriet har bekräftat att en leverantör som bygger på Kartpunkt inte behöver ett eget vidareförädlaravtal. Varje svar anger källa och är dokumenterat och daterat med nätversion.
En testnyckel räknar på Trafikverkets riktiga vägnät, med påhittade adresser i samma format som livedata. Livedata, Lantmäteriets adresser, är för kunder med avtal.
## Var tjänsten körs
API:et och dess databas körs på servrar som vi hyr av Hetzner, ett tyskt EU-ägt bolag, i Helsingfors. Adresser och API-anrop stannar i EU. Webbplatsen kartpunkt.se levereras av Vercel från Stockholm; API-anropen går direkt till Hetzner, inte via Vercel.
## Vad vi inte sparar
Söktexter, koordinater och adresser som skickas till API:et loggas inte. Vi sparar dem inte heller, utom indata till ett jobb: den sparas tills jobbet är klart och raderas då. Jobbets resultat raderas efter högst en vecka. Om ett anrop sparar vi bara dess id, nyckelns id, operationen, statuskoden och svarstiden, och räknar antalet anrop och träffar.
Personuppgiftsbiträdesavtal finns som utkast på begäran. Vilka personuppgifter vi behandlar och hur länge står i [integritetspolicyn](https://kartpunkt.se/integritet).
## Vad API:et gör
Ert system får färdvägar och avstånd för bil, lastbil och buss efter NVDB:s gränser för höjd, vikt, längd och förbud, ruttoptimering och ruttkontroll, vägfakta och spårmatchning, och adresser, en adress i taget.
Det är byggt för utvecklare och deras kodagenter, i system för körjournal, avfallshämtning, åkerier, kollektivtrafik och andra persontransporter, och för plattformar som bygger egna tjänster på det. Hur du kommer igång står på [sidan för utvecklare](https://kartpunkt.se/utvecklare), och dokumentationen för kodagenter i [llms.txt](https://kartpunkt.se/llms.txt).
## Kontakt
Mejla [hej@kartpunkt.se](mailto:hej@kartpunkt.se). Vad du ska skriva, och vad du inte ska skicka, står på [kontaktsidan](https://kartpunkt.se/kontakt).
> Vem som driver Kartpunkt, var adresserna kommer ifrån, var tjänsten körs och vad vi inte sparar om era anrop.
>
> https://kartpunkt.se/om
---
Kontakt
# Kontakta Kartpunkt
Mejla [hej@kartpunkt.se](mailto:hej@kartpunkt.se). Skriv vad det gäller och vilket konto, så kan vi svara utan att fråga tillbaka.
Kartpunkt drivs av Zephyr Valley AB, org.nr 559220-6873. Mer om bolaget och tjänsten står på [sidan om Kartpunkt](https://kartpunkt.se/om).
## Beställa en plan eller Avtal
Ni beställer Bas, Pro, Avtal eller tillägget Rutter och avstånd, och livedata på kontot, genom att mejla [hej@kartpunkt.se](mailto:hej@kartpunkt.se). Skriv:
- vilken e-postadress kontot har,
- vilken organisation ni är,
- vilken plan ni vill ha, och för Avtal om ni är leverantör eller operatör.
Vi fakturerar enligt avtal. Det går inte att betala med kort.
Ett utkast till personuppgiftsbiträdesavtal och listan över underbiträden skickar vi på begäran, i samma mejl eller ett eget.
## Hjälp med en integration
De flesta svar finns redan på [sidan för utvecklare](https://kartpunkt.se/utvecklare) och i dokumentationen för kodagenter, [llms.txt](https://kartpunkt.se/llms.txt). Hittar du inte svaret där, mejla [hej@kartpunkt.se](mailto:hej@kartpunkt.se) och skriv:
- vilken operation du anropade,
- vilken felkod och vilket meddelande API:et svarade,
- vilken nyckel det gäller, med namnet den har på kontosidan eller dess första tecken, som `kp_test_AbCd`. Aldrig hela nyckeln.
## Skicka aldrig en API-nyckel
Skicka aldrig en API-nyckel till oss, varken i ett mejl, i en chatt eller någon annanstans. Ingen på Kartpunkt ber dig någonsin om en nyckel.
Har du skickat en nyckel av misstag, återkalla den på [kontosidan](https://kartpunkt.se/konto) och skapa en ny. En nyckel visas bara en gång, när den skapas, så vi kan varken se den eller skapa samma nyckel igen.
## Personuppgifter och att radera ett konto
Frågor om dina personuppgifter och begäran om att radera ett konto mejlar du till [hej@kartpunkt.se](mailto:hej@kartpunkt.se). Vilka uppgifter vi sparar och hur länge står i [integritetspolicyn](https://kartpunkt.se/integritet).
## Säkerhet
Har du hittat en säkerhetsbrist, mejla den till [hej@kartpunkt.se](mailto:hej@kartpunkt.se). Adressen står också i [security.txt](https://kartpunkt.se/.well-known/security.txt).
> Så når du Kartpunkt: mejla hej@kartpunkt.se för att beställa en plan eller Avtal, få utkastet till personuppgiftsbiträdesavtal, hjälp med en integration eller svar om dina personuppgifter.
>
> https://kartpunkt.se/kontakt
---
Integritet
# Integritetspolicy
Senast uppdaterad 2026-10-05
Kort sagt: det ni skickar till API:et, som söktexter och koordinater, loggas inte. Om ert konto vet vi vem som har det, vilka nycklar det har och hur många anrop de gör. Vi säljer inga uppgifter. Sidvisningar räknas utan cookies.
## Personuppgiftsansvarig
**Zephyr Valley AB**, org.nr 559220-6873, som driver Kartpunkt. Frågor om dina personuppgifter mejlar du till [hej@kartpunkt.se](mailto:hej@kartpunkt.se).
## När ni anropar API:et
Det ni skickar i ett anrop till API:et, till exempel en söktext, en koordinat eller en adress att validera, kan vara personuppgifter om era användare, förare eller kunder. För dem är **ni** personuppgiftsansvariga och vi ert personuppgiftsbiträde. Ett utkast till personuppgiftsbiträdesavtal får ni på begäran.
Vi använder uppgifterna bara för att besvara anropet. De **loggas inte och sparas inte**: varken söktext, koordinater, adresser eller anropets adress hamnar i någon logg, databas eller cache. Det enda undantaget är indata till ett jobb: den sparas i databasen tills jobbet är klart och raderas då, och jobbets resultat raderas efter högst 7 dagar. Om anropet sparar vi bara en rad i driftloggen och antalet anrop och träffar (se Uppgifter nedan).
Adresserna i svaren kommer ur Lantmäteriets register Belägenhetsadress. För dem är Zephyr Valley AB personuppgiftsansvarig enligt Lantmäteriets beslut, och ni blir själva ansvariga för de svar ni sparar. Vi lagrar bara adressen och dess koordinat, inga uppgifter om ägare eller boende. En testnyckel svarar från testdata: påhittade adresser som inte finns.
## Demon på startsidan
På startsidan kan du prova API:et utan konto och utan nyckel: du klickar ut en start och ett mål på kartan, eller väljer ett förslag, och demon ritar vägen dit. De två punkterna skickas som koordinater från din webbläsare direkt till vårt API på api.kartpunkt.se, på vår server hos Hetzner i Helsingfors. Vägen räknas fram på Trafikverkets vägdata (NVDB, fri data under CC0). Demon visar inga adresser. Punkterna du klickar på loggas inte och sparas inte: de finns inte i någon logg, databas eller cache. Driftloggen får en rad per anrop, med "demo" i stället för en nyckel, operationen, statuskoden och svarstiden, och vi räknar antalet anrop för demon som helhet, inte per besökare. I demons andra lägen skickas på samma sätt punkten du klickar på, de inställningar du väljer och den sträcka du ritar.
För att demon inte ska kunna missbrukas begränsar vi hur många anrop en besökare kan göra. För det håller API:et en envägshash av din IP-adress i minnet. Hashen görs med ett slumpat värde som bara finns i minnet och byts varje natt klockan 00.00 (UTC), så efter det går den inte att koppla till din IP-adress. Den skrivs aldrig till någon logg eller databas. Innan demon svarar räknar din webbläsare fram en liten kontrolluppgift i stället för en captcha; ingen annan tjänst är inblandad och ingen cookie sätts.
Rättslig grund: berättigat intresse (art. 6.1 f), att visa hur tjänsten fungerar och skydda den mot missbruk.
## Uppgifter om dig som har konto: varför och med vilken rättslig grund
Uppgifterna gäller dig som loggar in på kartpunkt.se, oftast som utvecklare hos en av våra kunder. Du loggar in med ditt Google-konto; första gången skapas ditt Kartpunkt-konto. Uppgifterna från Google behövs för att du ska kunna ha ett konto. Utan dem kan du inte skapa API-nycklar.
- Namn, e-postadress, om Google har bekräftat adressen, adressen till din profilbild
Varifrån
Google, när du loggar in
Varför
Veta vem som har kontot och kunna kontakta dig om kontot, nycklarna, livedata och tjänsten.
Rättslig grund
Avtal (art. 6.1 b)
- Ditt Google-kontos id och vilka behörigheter du gav vid inloggningen
Varifrån
Google, när du loggar in
Varför
Koppla din Google-inloggning till ditt konto. Vi anropar inte Google efter inloggningen.
Rättslig grund
Avtal (art. 6.1 b)
- Inloggningssession: en slumpad sessionsnyckel, när den går ut, din IP-adress och webbläsarens user agent
Varifrån
Din webbläsare
Varför
Låta dig vara inloggad, se varifrån en session kom och begränsa antalet inloggningsförsök per IP-adress.
Rättslig grund
Berättigat intresse: att skydda kontona (art. 6.1 f)
- Tillfälliga uppgifter under inloggningen hos Google
Varifrån
Inloggningen
Varför
Skydda omdirigeringen till och från Google mot förfalskning.
Rättslig grund
Berättigat intresse: säkerhet (art. 6.1 f)
- API-nycklar: namnet du gav nyckeln, dess första tecken (t.ex. kp\_test\_AbCd), en hash av nyckeln, om det är en testnyckel eller livenyckel, när den skapades och senast användes
Varifrån
Dig, på kontosidan
Varför
Känna igen anrop till API:et och låta dig skilja nycklarna åt och återkalla dem. Själva nyckeln sparar vi inte. Den visas en gång, när du skapar den.
Rättslig grund
Avtal (art. 6.1 b)
- Användning: per nyckel, konto, dygn och operation, antalet anrop och antalet träffar
Varifrån
API:et
Varför
Visa användningen på kontosidan, fakturering, dagliga gränser mot massuttag ur adressregistret och uppföljning som Lantmäteriets villkor kräver.
Rättslig grund
Avtal (art. 6.1 b); berättigat intresse för gränserna och uppföljningen (art. 6.1 f)
- Godkännande för livedata: kontot, en hänvisning till ert undertecknade avtal (inte själva avtalet), datum för godkännande och återkallelse
Varifrån
Oss, när avtalet är klart
Varför
Bara kunder med avtal får Lantmäteriets livedata, och vi ska kunna visa Lantmäteriet vilka kunder som hade det och när.
Rättslig grund
Avtal (art. 6.1 b); berättigat intresse för uppföljningen (art. 6.1 f)
- Driftlogg: för varje API-anrop dess id (ert eget X-Request-Id om ni skickar ett), nyckelns id, operationen, statuskoden och svarstiden
Varifrån
API:et
Varför
Drift, felsökning och att upptäcka missbruk.
Rättslig grund
Berättigat intresse (art. 6.1 f)
Vi fattar inga automatiserade beslut om dig och gör ingen profilering.
## Hur länge vi sparar uppgifterna
Här står vad som gäller i dag. Det som är markerat _Planerat_ är beslutat men ännu inte byggt; tills det är byggt gäller det som står först.
- Inloggningssessioner
En session gäller i 7 dagar och förlängs när du använder den. Loggar du ut raderas den direkt. Utgångna sessioner raderas automatiskt varje dygn, så din IP-adress och user agent finns kvar som mest omkring 7 dagar efter att du senast var inloggad.
- Tillfälliga uppgifter under inloggningen
Går ut inom några minuter och raderas varje dygn tillsammans med utgångna sessioner.
- Googles inloggningsnycklar (tokens)
Sparas inte alls.
- Kontot (namn, e-post, Google-id)
Tills kontot raderas. Du kan i dag inte radera kontot själv på kontosidan; mejla oss så raderar vi det.
PlaneratEtt konto som varken loggats in på eller vars nycklar använts på 24 månader raderas, efter en varning per mejl 30 dagar innan.
- API-nycklar
Tills du återkallar nyckeln. Då raderas den.
- Användning (antal anrop och träffar)
Raderas i dag inte, och finns kvar med kontots id även om kontot raderas.
PlaneratSparas i 24 månader och raderas med kontot, utom de uppgifter som en faktura bygger på; de sparas som räkenskapsinformation enligt bokföringslagen (7 år).
- Godkännanden för livedata
Sparas när godkännandet återkallas, men raderas om kontot raderas.
PlaneratSparas i 3 år efter återkallelsen, även om kontot raderas, för Lantmäteriets uppföljning.
- Hashen av din IP-adress i demon
Finns bara i minnet och går inte att koppla till din IP-adress efter nästa midnatt (UTC). Skrivs aldrig till logg eller databas.
- Driftloggen
Skrivs över när den når en viss storlek. Hur många dagar den räcker beror på hur mycket tjänsten används.
PlaneratSparas som mest i 30 dagar.
- Säkerhetskopior
Tas inte i dag.
PlaneratKrypterade säkerhetskopior av kontouppgifterna, hos Hetzner i Helsingfors, raderas efter 30 dagar. När de är påslagna finns raderade uppgifter alltså kvar i säkerhetskopiorna i upp till 30 dagar.
## Var uppgifterna finns och vem som får del av dem
Kontouppgifterna lagras i vår egen databas på en server som vi hyr av **Hetzner Online GmbH** (Tyskland), i ett datacenter i Helsingfors, Finland. Hetzner är vårt personuppgiftsbiträde, och vi har ett personuppgiftsbiträdesavtal med dem. Där tas också API-anropen emot och besvaras.
Kartan i demon hämtas från tiles.kartpunkt.se, vår egen server hos Hetzner i Helsingfors. Din webbläsare skickar då din IP-adress och vilka kartrutor som visas; vi loggar inte de förfrågningarna. Kartdata © OpenStreetMaps bidragsgivare, under licensen ODbL.
**Google** sköter inloggningen och är självständigt personuppgiftsansvarig för den (Google Ireland Limited för användare i EU/EES). Eftersom din webbläsare pratar direkt med Google vet Google att du loggar in på Kartpunkt, när och från vilken IP-adress och webbläsare. Vi får ditt namn, din e-postadress, om den är bekräftad, adressen till din profilbild och ditt Google-id, och skickar inget om dig till Google utöver själva inloggningsförfrågan. Hur Google behandlar uppgifterna står i [Googles integritetspolicy](https://policies.google.com/privacy).
**Vercel Inc.** (USA) levererar webbplatsen kartpunkt.se från sitt datacenter i Stockholm och ser varje sidvisning, även inloggnings- och kontosidan: din IP-adress, webbläsarens user agent och vilken sida du öppnade, i sina egna loggar. Kontouppgifterna går inte via Vercel. Inloggningen och kontosidan hämtar dem direkt från vårt API på api.kartpunkt.se. Vercel vet alltså att en IP-adress öppnade kontosidan, men inte vems kontot är. Vercel ser inte heller vilka punkter du klickar på i demon på startsidan: de går direkt från din webbläsare till api.kartpunkt.se. Hur länge Vercel sparar sina loggar har vi ännu inte bekräftat för vårt abonnemang.
Det du mejlar till [hej@kartpunkt.se](mailto:hej@kartpunkt.se) tas emot av **Cloudflare** (e-postvidarebefordran) och vidarebefordras till en e-postlåda hos **Google** (Gmail), där vi läser det för att svara dig. Övriga leverantörer, för namnuppslag (DNS), certifikat och driftövervakning, får inga uppgifter om dig från oss. Vi säljer inga personuppgifter och lämnar inte ut dem till någon annan, om inte lagen kräver det.
## Överföring till länder utanför EU/EES
Kontouppgifterna och API-anropen stannar hos Hetzner i Finland. Google och Vercel är amerikanska bolag och kan behandla uppgifter i USA, liksom Cloudflare för e-post till oss. Google LLC, Vercel Inc. och Cloudflare, Inc. är certifierade enligt **EU-U.S. Data Privacy Framework**, som EU-kommissionen har beslutat ger en adekvat skyddsnivå. Du kan kontrollera det på [dataprivacyframework.gov](https://www.dataprivacyframework.gov/list).
## Cookies
Webbplatsen kartpunkt.se sätter inga cookies och spårar dig inte mellan webbplatser. Vi räknar sidvisningar med Vercel Web Analytics, som inte använder cookies och inte sparar något som identifierar dig. Bara sidans adress skickas, utan frågesträng och utan det du skriver eller klickar i demon, och inloggnings- och kontosidorna räknas inte alls. Typsnitten levereras från vår egen webbplats.
Demon och kartan på startsidan sätter inga cookies.
När du loggar in sätter vårt API, på api.kartpunkt.se, två cookies som behövs för inloggningen och därför inte kräver samtycke:
- **kartpunkt.session\_token**: gör att du förblir inloggad. Gäller i 7 dagar och tas bort när du loggar ut.
- **kartpunkt.state**: skyddar inloggningen hos Google. Gäller i 5 minuter.
I webbläsaren kan namnen ha prefixet `__Secure-`. Google sätter egna cookies på sina sidor när du loggar in där.
## Dina rättigheter
Du har rätt att:
- **få veta** vilka uppgifter vi har om dig och få en kopia. Kontosidan visar din e-postadress, dina nycklar och användningen de senaste 30 dagarna; resten, till exempel sessioner med IP-adresser, får du om du mejlar oss.
- **få fel rättade**. Namn och e-post kommer från Google och kan inte ändras på kontosidan; mejla oss så rättar vi.
- **få uppgifterna raderade**. Du kan själv återkalla dina nycklar och logga ut, men inte radera kontot på kontosidan. Mejla oss så raderar vi kontot. Det som finns kvar efter en radering står under lagringstiderna ovan.
- **invända** mot behandling som bygger på berättigat intresse, **begära begränsning** och **få ut dina uppgifter** i ett maskinläsbart format (dataportabilitet).
Mejla [hej@kartpunkt.se](mailto:hej@kartpunkt.se) från den adress kontot gäller, så svarar vi inom en månad. Är du inte nöjd med hur vi behandlar dina uppgifter kan du klaga hos [Integritetsskyddsmyndigheten (IMY)](https://www.imy.se).
Gäller det uppgifter som en kund skickat till API:et är kunden ansvarig; vänd dig först till den.
## Ändringar
Ändras behandlingen uppdaterar vi den här sidan och datumet överst.
> Hur Kartpunkt (Zephyr Valley AB) behandlar personuppgifter: kontot du loggar in med, API-nycklar, användning och vad som händer med det ni skickar till API:et.
>
> https://kartpunkt.se/integritet
---
Ruttoptimering
# Ruttoptimering via API, på Trafikverkets vägnät
Ni skickar fordon och stopp, och API:et svarar vilket fordon som tar vilka stopp, i vilken ordning och när, med varje sträcka tidsatt på Trafikverkets vägnät NVDB.
## Operationerna
- **Ruttoptimering**[`POST /v1/optimize`](https://api.kartpunkt.se/docs#tag/planning/POST/v1/optimize)
Fördelar stopp och sändningar på fordonen inom kapacitet och tidsfönster. Ett stopp som inget fordon kan ta står i unassigned, med skälet.
- **Ruttkontroll**[`POST /v1/optimize/check`](https://api.kartpunkt.se/docs#tag/planning/POST/v1/optimize/check)
Tidsätter era egna rutter, i er ordning, och svarar med det som bryter mot kapacitet, tidsfönster och övriga villkor.
- **Jobb**[`POST /v1/jobs`](https://api.kartpunkt.se/docs#tag/jobs/POST/v1/jobs)
Samma indata som jobb, för en plan större än ett anrop tar. Jobb ingår från Bas.
## Ett anrop och dess svar
Två bilar med plats för 30 kärl var, och tio stopp som hämtar mellan 4 och 8 kärl. Målet är kortast total tid.
```
POST https://api.kartpunkt.se/v1/optimize
Authorization: Bearer $KARTPUNKT_API_KEY
Content-Type: application/json
{
"vehicles": [
{
"id": "bil-1",
"start": {"lat": 55.929161, "lon": 13.560304},
"end": {"lat": 55.929161, "lon": 13.560304},
"capacity": {"karl": 30}
},
{
"id": "bil-2",
"start": {"lat": 55.929161, "lon": 13.560304},
"end": {"lat": 55.929161, "lon": 13.560304},
"capacity": {"karl": 30}
}
],
"stops": [
{
"id": "h01",
"at": {"lat": 55.936634, "lon": 13.551388},
"pickup": {"karl": 8},
"service_s": 240
},
{
"id": "h02",
"at": {"lat": 55.941993, "lon": 13.546969},
"pickup": {"karl": 6},
"service_s": 180
}
// … 8 stopp till
],
"objective": "duration",
"profile": "car_fastest",
"geometry": "geojson"
}
```
Svaret från api.kartpunkt.se, inspelat 2026-10-25. Rader som börjar med // … är utelämnade.
```
{
"routes": [
{
"vehicle_id": "bil-1",
"visits": [
{
"kind": "start",
"id": null,
"snap_m": 0.03,
"street_matched": null,
"arrival": null,
"departure": null,
"arrival_s": 0,
"waiting_s": 0,
"service_s": 0,
"load": {"karl": 0},
"distance_m": 0,
"duration_s": 0
},
{
"kind": "stop",
"id": "h04",
"snap_m": 0.06,
"street_matched": null,
"arrival": null,
"departure": null,
"arrival_s": 269,
"waiting_s": 0,
"service_s": 150,
"load": {"karl": 5},
"distance_m": 2991.3,
"duration_s": 268.7
}
// … 5 besök till
],
"distance_m": 9958.8,
"duration_s": 1729,
"waiting_s": 0,
"service_s": 870
// … legs, varje sträcka med sin linje
}
// … 1 rutt till
],
"unassigned": [],
"totals": {
"vehicles_used": 2,
"distance_m": 17532.8,
"duration_s": 3178,
"waiting_s": 0,
"service_s": 1680
},
"provenance": {
"synthetic": false,
"network": {
"id": "snap_01m5jtxeacfk49vqky0fy1myje",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-22T22:15:11.949Z",
"graph": "snap_01m5k1bmzgfk49vt8z6wche2jr",
"profile": "car_fastest",
"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"
}
]
}
}
```
## Vad svaret innehåller
Varje rutt har sina besök med ankomst, väntan, last och sträcka från förra besöket, och `totals` summerar planen. Ett stopp som inget fordon kan ta står i `unassigned` med ett skäl och vad ni kan ändra; en plan som inte går ihop besvaras alltså, den avvisas inte.
`provenance` anger nätversionen planen räknades på, NVDB:s leverans och källhänvisningen. Svaret är dokumenterat och daterat med nätversion.
## Pröva en plan ni redan har
Samma tio stopp i listans ordning, fem per bil, gick till `/v1/optimize/check`. Svaret blev `"feasible": false`: bil-1 lastar 3 kärl mer än det får plats vid stopp h05 (`plan_capacity`). Ruttkontrollen räknar på samma vägnät och i samma fält som ruttoptimeringen, så de två planerna går att jämföra fält för fält.
## Hur stor en plan kan vara
En plan direkt till `/v1/optimize` svarar i samma anrop. En större plan går som jobb, upp till ett par hundra hämtpunkter i ett jobb. Gränserna står under [Gränser](https://kartpunkt.se/utvecklare#granser) på sidan för utvecklare.
## Prova och kom igång
- [Prova ruttoptimering och ruttkontroll på kartan, utan nyckel](https://kartpunkt.se/#prova-ruttoptimering)
- [Skapa en gratis testnyckel](https://kartpunkt.se/konto). Den räknar på NVDB:s riktiga vägnät, med testadresser i samma format som livedata.
- Fälten och felkoderna står i [API-referensen](https://api.kartpunkt.se/docs#tag/planning/POST/v1/optimize), och integrationen för en kodagent på [sidan för utvecklare](https://kartpunkt.se/utvecklare). Vad planerna kostar står under [Priser](https://kartpunkt.se/priser).
> Skicka fordon och stopp, få tillbaka vilket fordon som tar vilka stopp, i vilken ordning och när, tidsatt på NVDB:s vägnät. Och pröva en plan ni redan har.
>
> https://kartpunkt.se/ruttoptimering
---
NVDB API
# NVDB som API: vägfakta och vägreferenser
Ni frågar vad Trafikverkets vägdatabas NVDB anger om en väg, som hastighetsgräns, väghållare, bärighetsklass och höjdhinder, och får svaret med vägreferens och nätversion.
## Operationerna
- **Väg**[`POST /v1/roads/nearby`](https://api.kartpunkt.se/docs#tag/roads/POST/v1/roads/nearby)
Vägarna kring en punkt, närmast först, med de vägfakta ni ber om.
- **Vägreferens**[`GET /v1/roads/{ref}`](https://api.kartpunkt.se/docs#tag/roads/GET/v1/roads/{ref})
En väg utifrån en vägreferens ni sparat ur ett tidigare svar.
- **Vägfakta**[`GET /v1/facts`](https://api.kartpunkt.se/docs#tag/registries/GET/v1/facts)
De 34 vägfakta ur NVDB som en väg, en färdväg eller ett spår kan redovisa, med slag och enhet.
- **Vektorrutor**[`GET /v1/tilesets`](https://api.kartpunkt.se/docs#tag/registries/GET/v1/tilesets)
NVDB:s vägar som vektorrutor (PMTiles) att visa i er egen karta.
## Ett anrop och dess svar
Vägen vid en punkt, inom 40 meter, med elva vägfakta.
```
POST https://api.kartpunkt.se/v1/roads/nearby
Authorization: Bearer $KARTPUNKT_API_KEY
Content-Type: application/json
{
"point": {"lat": 59.70658, "lon": 17.7998},
"radius_m": 40,
"limit": 1,
"facts": [
"speed_limit",
"road_owner",
"surface",
"bearing_class",
"road_class",
"traffic_safety_class",
"built_up",
"fast_road",
"traffic_volume",
"max_weight",
"max_height"
]
}
```
Svaret från api.kartpunkt.se, inspelat 2026-10-24. Rader som börjar med // … är utelämnade.
```
{
"roads": [
{
"ref": "1000:89985@0.873831-0.877665",
"distance_m": 0.52,
"name": null,
"number": "1043",
"length_m": 10.2,
"facts": {
"speed_limit": "70",
"road_owner": "state",
"surface": "gravel",
"bearing_class": "BK1",
"road_class": "5",
"traffic_safety_class": "unknown",
"built_up": false,
"fast_road": true,
"traffic_volume": 185,
"max_weight": null,
"max_height": 3.7
}
// … geometry, vägens linje som GeoJSON
}
],
"provenance": {
"synthetic": false,
"network": {
"id": "snap_01m5jtxeacfk49vqky0fy1myje",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-22T22:15:11.949Z",
"graph": "snap_01m5k1bmzgfk49vt8z6wche2jr"
},
"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"
}
]
}
}
```
## Vägreferensen
Varje sträcka i ett svar har en vägreferens, `ref`: NVDB:s länk och en del av den, som `1000:89985@0.873831-0.877665`. Referensen pekar på samma väg i varje leverans där länken finns kvar. Spara den med ert eget ärende, och läs vägen igen med `GET /v1/roads/{ref}`. Färdvägar och spårmatchningar har samma referens på varje delsträcka.
## Nätversion och källa
Vägnätet byggs ur NVDB, som Trafikverket ger ut under CC0. Vi läser in nya NVDB-leveranser, och `provenance.network` i varje svar anger nätversionen och vilken leverans den byggdes ur (`source_date`). Svaret är dokumenterat och daterat med nätversion, och har NVDB:s källhänvisning.
## Prova och kom igång
- [Klicka på en väg på kartan och se dess vägfakta och vägreferens, utan nyckel](https://kartpunkt.se/#prova-vagfakta)
- [Skapa en gratis testnyckel](https://kartpunkt.se/konto). Den räknar på NVDB:s riktiga vägnät, med testadresser i samma format som livedata.
- Fälten och felkoderna står i [API-referensen](https://api.kartpunkt.se/docs#tag/roads/POST/v1/roads/nearby), och integrationen för en kodagent på [sidan för utvecklare](https://kartpunkt.se/utvecklare). Vad planerna kostar står under [Priser](https://kartpunkt.se/priser).
> Vad Trafikverkets NVDB anger om en väg, som hastighetsgräns, väghållare och bärighetsklass, i ett anrop med vägreferens och nätversion i svaret.
>
> https://kartpunkt.se/nvdb-api
---
Lastbil och buss
# Färdvägar för lastbil och buss, efter NVDB:s gränser
Ni skickar start, mål och fordonets mått, och API:et svarar med en färdväg för lastbil eller buss efter NVDB:s gränser för höjd, vikt, längd och förbud.
## Operationerna
- **Färdväg**[`POST /v1/route`](https://api.kartpunkt.se/docs#tag/routing/POST/v1/route)
Färdvägen med profilen hgv och fordonets mått, i delsträckor med vägreferens och vägfakta.
- **Vägavstånd**[`POST /v1/distances`](https://api.kartpunkt.se/docs#tag/routing/POST/v1/distances)
Vägavstånd för samma fordon från många startpunkter till några mål.
- **Profil**[`GET /v1/profiles`](https://api.kartpunkt.se/docs#tag/registries/GET/v1/profiles)
Profilerna och vad var och en tar hänsyn till, så att ni inte behöver skriva in dem i koden.
## Ett anrop och dess svar
En lastbil på 26 ton, 4 meter hög och 12 meter lång, med väghållare och hastighetsgräns summerade längs färdvägen.
```
POST https://api.kartpunkt.se/v1/route
Authorization: Bearer $KARTPUNKT_API_KEY
Content-Type: application/json
{
"origin": {"lat": 59.2965, "lon": 18.0445},
"destination": {"lat": 59.2705, "lon": 17.9195},
"profile": "hgv",
"vehicle": {"kind": "lorry", "weight_t": 26, "height_m": 4, "length_m": 12},
"facts": ["road_owner", "speed_limit"],
"geometry": "none"
}
```
Svaret från api.kartpunkt.se, inspelat 2026-10-22. Rader som börjar med // … är utelämnade.
```
{
"status": "ok",
"distance_m": 9840.4,
"duration_s": 507,
"facts": {
"road_owner": {"state": 8823.2, "private": 272.9, "municipal": 744.4},
"speed_limit": {"30": 46.4, "40": 275, "50": 695.8, "60": 483.4, "70": 3431.2, "80": 4908.6}
},
"avoid_m": null,
"snap_m": 19.44,
"street_matched": null,
"destination_snap_m": 10.79,
"destination_street_matched": null,
"blocked_by": null,
"via": null,
"segments": [
{
"length_m": 157.2,
"ref": "13636:3867@0.613570-1.000000",
"facts": {"road_owner": "state", "speed_limit": "70"}
},
{
"length_m": 0,
"ref": "13636:3873@0.000000-0.000002",
"facts": {"road_owner": "state", "speed_limit": "70"}
}
// … 108 delsträckor till
],
"avoid": null,
"alternatives": null,
"provenance": {
"synthetic": false,
"network": {
"id": "snap_01m51kym0qe499571qqyfr5q6g",
"dataset": "nvdb",
"source_date": "2026-10-13",
"built_at": "2026-10-16T05:46:25.175Z",
"graph": "snap_01m52ek3n7e4995j4m1xrkapwh",
"profile": "hgv",
"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"
}
]
}
}
```
## Fordonet i anropet
Profilen `hgv` tar fordonets mått i `vehicle`: vikt, höjd och längd. Med `"kind": "bus"` räknas färdvägen för en buss, efter de förbud NVDB anger för buss. Färdvägen håller sig borta från vägar och svängar som NVDB stänger för fordonet i den riktning det kör, och från gränser för höjd, vikt och längd som är lägre än fordonets. Tiden räknas vid fritt flöde, med högst 80 km/h.
## En känd begränsning
Ett lastbilsförbud som NVDB anger i en riktning behandlas som förbud i båda riktningarna. Färdvägen kan då bli längre än den behöver vara.
## Vad svaret innehåller
Varje delsträcka har en vägreferens, `ref`, till NVDB:s länk, som pekar på samma väg i varje leverans där länken finns kvar, och de vägfakta ni bad om. `provenance` anger nätversionen, profilen och NVDB:s leverans och källhänvisning: svaret är dokumenterat och daterat med nätversion.
## Prova och kom igång
- [Välj Fordon och en höjd eller vikt på kartan och se fordonets färdväg bredvid bilens, utan nyckel](https://kartpunkt.se/#prova-fardvag)
- [Skapa en gratis testnyckel](https://kartpunkt.se/konto). Den räknar på NVDB:s riktiga vägnät, med testadresser i samma format som livedata.
- Fälten och felkoderna står i [API-referensen](https://api.kartpunkt.se/docs#tag/routing/POST/v1/route), och integrationen för en kodagent på [sidan för utvecklare](https://kartpunkt.se/utvecklare). Vad planerna kostar står under [Priser](https://kartpunkt.se/priser).
> Färdvägar och avstånd för lastbil och buss efter NVDB:s gränser för höjd, vikt, längd och förbud, med fordonets mått i anropet.
>
> https://kartpunkt.se/lastbil
---
Vägavstånd
# Vägavstånd och avståndsmatris via API
Ni skickar startpunkter och mål, och API:et svarar med vägavstånd i meter och restid i sekunder längs Trafikverkets vägnät NVDB, för gång, bil, lastbil eller buss.
## Operationerna
- **Avståndsmatris**[`POST /v1/matrix`](https://api.kartpunkt.se/docs#tag/routing/POST/v1/matrix)
Meter och sekunder från varje startpunkt till varje mål, med en status per cell.
- **Vägavstånd**[`POST /v1/distances`](https://api.kartpunkt.se/docs#tag/routing/POST/v1/distances)
Från många startpunkter till några mål, med de vägfakta ni ber om summerade längs varje väg och, om ni vill, dess linje.
- **Färdväg**[`POST /v1/route`](https://api.kartpunkt.se/docs#tag/routing/POST/v1/route)
En färdväg mellan två punkter, i delsträckor med vägreferens och vägfakta.
## Ett anrop och dess svar
Fem punkter mot samma fem punkter med bil, den snabbaste vägen: en avståndsmatris på 5 × 5.
```
POST https://api.kartpunkt.se/v1/matrix
Authorization: Bearer $KARTPUNKT_API_KEY
Content-Type: application/json
{
"origins": [
{"lat": 57.77071, "lon": 11.99134},
{"lat": 57.79255, "lon": 11.96891},
{"lat": 57.73889, "lon": 11.97117},
{"lat": 57.79789, "lon": 12.04819},
{"lat": 57.75602, "lon": 11.92846}
],
"destinations": [
{"lat": 57.77071, "lon": 11.99134},
{"lat": 57.79255, "lon": 11.96891},
{"lat": 57.73889, "lon": 11.97117},
{"lat": 57.79789, "lon": 12.04819},
{"lat": 57.75602, "lon": 11.92846}
],
"profile": "car_fastest"
}
```
Svaret från api.kartpunkt.se, inspelat 2026-10-13. Rader som börjar med // … är utelämnade.
```
{
"distances_m": [
[0, 4011.9, 5023.7, 7766.2, 6878],
[3967.5, 0, 9078.7, 7259.9, 8812],
[5121.8, 9317.3, 0, 12751.6, 6334.5],
[7713.2, 7543.9, 12108, 0, 14719.2],
[7128.5, 9032.3, 6431.2, 15067.3, 0]
],
"durations_s": [
[0, 274, 348, 494, 481],
[270, 0, 519, 442, 581],
[355, 570, 0, 714, 455],
[454, 426, 653, 0, 794],
[498, 596, 462, 816, 0]
],
"statuses": [
["ok", "ok", "ok", "ok", "ok"],
["ok", "ok", "ok", "ok", "ok"],
["ok", "ok", "ok", "ok", "ok"],
["ok", "ok", "ok", "ok", "ok"],
["ok", "ok", "ok", "ok", "ok"]
],
"provenance": {
"synthetic": false,
"network": {
"id": "snap_01m4twhhp1fvmv2fbzba4mk01y",
"dataset": "nvdb",
"source_date": "2026-10-06",
"built_at": "2026-10-13T15:01:52.961Z",
"graph": "snap_01m4v498g5fvmv2jph75zf2g5w",
"profile": "car_fastest",
"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-06"
}
]
}
}
```
## Vad svaret innehåller
Raderna är startpunkterna och kolumnerna målen, i den ordning ni skickade dem. En punkt som inte når vägnätet får sin status i `statuses` i stället för ett tal, så en dålig punkt fäller inte anropet. Restiden räknas vid fritt flöde, efter NVDB:s hastighetsgränser, utan trafik och tid på dygnet.
`provenance` anger nätversionen, profilen och NVDB:s leverans: varje vägavstånd är dokumenterat och daterat med nätversion. Hur ett vägavstånd ser ut som utskrivbart dokument visar [mätprotokollet](https://kartpunkt.se/exempel/matprotokoll).
## Matris, avstånd eller färdväg
Avståndsmatrisen ger bara tal, för en tabell mellan många platser. `/v1/distances` ger varje väg med sina vägfakta och linje, och `/v1/route` en färdväg med en vägreferens per delsträcka. Ett avstånd är en beräkning per startpunkt och mål, och en avståndsmatris räknas som hälften av sina celler, högst tio per punkt i den längsta listan.
## Prova och kom igång
- [Klicka start och mål på kartan och se färdvägen och dess vägavstånd, utan nyckel](https://kartpunkt.se/#prova-fardvag)
- [Skapa en gratis testnyckel](https://kartpunkt.se/konto). Den räknar på NVDB:s riktiga vägnät, med testadresser i samma format som livedata.
- Fälten och felkoderna står i [API-referensen](https://api.kartpunkt.se/docs#tag/routing/POST/v1/matrix), och integrationen för en kodagent på [sidan för utvecklare](https://kartpunkt.se/utvecklare). Vad planerna kostar står under [Priser](https://kartpunkt.se/priser).
> Meter och sekunder längs Trafikverkets vägnät, från en startpunkt eller många, som avstånd med vägfakta eller som avståndsmatris, daterade med nätversion.
>
> https://kartpunkt.se/vagavstand
---
Adresser
# Geokodning av svenska adresser via API
Ni skickar en text som en person skrev eller en koordinat, och API:et svarar med adresser ur Lantmäteriets adressregister, en adress i taget.
## Operationerna
- **Sök**[`POST /v1/geocode`](https://api.kartpunkt.se/docs#tag/addresses/POST/v1/geocode)
Från en text till adresser, även felstavad, medan personen skriver.
- **Omvänd geokodning**[`POST /v1/reverse-geocode`](https://api.kartpunkt.se/docs#tag/addresses/POST/v1/reverse-geocode)
Från en koordinat till närmaste adresser och vägen intill.
- **Validera**[`POST /v1/addresses/validate`](https://api.kartpunkt.se/docs#tag/addresses/POST/v1/addresses/validate)
Pekar ut vad som är fel i en adress, utan att ändra den.
- **Postnummer**[`GET /v1/postcodes/{postcode}`](https://api.kartpunkt.se/docs#tag/addresses/GET/v1/postcodes/{postcode})
Från ett postnummer till postorter och kommuner.
## Ett anrop och dess svar
En felstavad sökning, med en träff.
```
POST https://api.kartpunkt.se/v1/geocode
Authorization: Bearer $KARTPUNKT_API_KEY
Content-Type: application/json
{"q": "jarngatan 2 upsala", "limit": 1}
```
Svaret en testnyckel fick, ur testdata (2026-10-05). Rader som börjar med // … är utelämnade.
```
{
"results": [
{
"id": "se.syn.syn-0000uvt",
"type": "address",
"label": "Järngatan 2, 755 41 Uppsala",
"street": "Järngatan",
"number": 2,
"letter": null,
"postcode": "75541",
"locality": "Uppsala",
"municipality_code": "0380",
"municipality": "Uppsala",
"county_code": "03",
"county": "Uppsala län",
"district_code": "903802",
"district": "Uppsala södra",
"point": {"lat": 59.859903789196885, "lon": 17.64433171466342},
"position_type": "driveway",
"match_type": "corrected",
"score": 6.057753791356872
}
],
"provenance": {
"synthetic": true,
"sources": [
{
"id": "synthetic",
"name": "Kartpunkt synthetic test addresses",
"licence": "CC0-1.0",
"attribution": "Syntetiska testadresser från Kartpunkt, inte verkliga adresser.",
"url": "https://creativecommons.org/publicdomain/zero/1.0/",
"updated": "2026-10-05"
}
]
}
}
```
## Vad svaret innehåller
Varje träff har ett adress-id, `id`, adressens delar, kommun och län, en punkt och dess lägestyp, och i `match_type` hur träffen förhåller sig till det som skrevs. Spara adress-id:t: med `POST /v1/addresses/changes` ser ni senare om adressen har ändrats eller avslutats.
`provenance` anger källan och dess licens. Ett svar med Lantmäteriets adresser har Lantmäteriets källhänvisning, som licensen CC BY 4.0 kräver.
## Testnyckel och livenyckel
En testnyckel svarar med testdata: påhittade adresser i samma format som livedata, märkta `"synthetic": true`, som i svaret ovan. En livenyckel svarar med Lantmäteriets adresser och kräver en betald plan och godkänd tillgång till livedata. Integrationen är densamma; ni byter bara nyckel.
## En adress i taget
Lantmäteriets adresser lämnas som enskilda svar: varje anrop gäller en sökning, en koordinat eller en adress och svarar med några träffar. En egen adresslista, upp till 50 000 rader, geokodas som ett jobb med ett svar per rad.
## Prova och kom igång
- [Se sökning, omvänd geokodning och validering med inspelade svar ur testdata](https://kartpunkt.se/utforska#adresser)
- [Skapa en gratis testnyckel](https://kartpunkt.se/konto). Den räknar på NVDB:s riktiga vägnät, med testadresser i samma format som livedata.
- Fälten och felkoderna står i [API-referensen](https://api.kartpunkt.se/docs#tag/addresses/POST/v1/geocode), och integrationen för en kodagent på [sidan för utvecklare](https://kartpunkt.se/utvecklare). Vad planerna kostar står under [Priser](https://kartpunkt.se/priser).
> Sök adresser ur Lantmäteriets register, omvänd geokodning från en koordinat, validering och postnummer, en adress i taget. Testnyckel med testdata.
>
> https://kartpunkt.se/geokodning
---
Priser
# Priser för Kartpunkts API
Börja gratis med en testnyckel och betala när ni går i drift. Bas och Pro är för företag och utvecklare med en egen tjänst. Leverantörer som säljer till kommuner och andra organisationer, och operatörer som planerar egna rutter, tecknar Avtal. Priserna är exkl. moms.
- Utvecklare
Bygg och testa mot testdata, utan kostnad. Vägnätet är NVDB:s riktiga.
0 kr
- 2 000 beräkningar per månad
- 1 000 testanrop per dygn
- Testadresser i samma format som livedata
- Jobb ingår från Bas
[Skapa testnyckel](https://kartpunkt.se/konto)
- Bas
För en egen tjänst: en första integration med livedata.
490 kr/mån
- 5 000 beräkningar/mån ingår
- 25 000 liveanrop per månad
- 10 000 adresser per dygn
- +50 000 beräkningar/mån som tillägg
[Beställ](https://kartpunkt.se/kontakt)
- Pro
För en egen tjänst i drift.
1 990 kr/mån
- 50 000 beräkningar/mån ingår
- 200 000 liveanrop per månad
- 50 000 adresser per dygn
[Beställ](https://kartpunkt.se/kontakt)
- Avtal
För leverantörer som säljer system till kommuner och andra organisationer, och för operatörer som planerar egna rutter.
enligt offert
- Leverantörer: pris per kommun
- Operatörer: pris per fordon
- Nätversion och källa i varje svar
- API:et körs i EU
- Personuppgiftsbiträdesavtal: utkast på begäran
[Kontakta oss](https://kartpunkt.se/kontakt)
Tillägg
### Rutter och avstånd
Finns på Bas och Pro
Avstånd, färdvägar och ruttoptimering längs Trafikverkets vägnät, med vägfakta längs vägen.
En beräkning är till exempel en färdväg, ett avstånd från en startpunkt till ett mål eller ett stopp i en ruttoptimering.
990 kr/mån
+50 000 beräkningar/mån
[Beställ tillägg](https://kartpunkt.se/kontakt)
Så räknas beräkningar
En färdväg
1 beräkning
Avstånd från en start till tre mål
3 beräkningar
Ruttoptimering med 40 stopp
40 beräkningar
Avståndsmatris 200 × 3, hälften av cellerna
300 beräkningar
En beräkning är en färdväg, en spårmatchning, ett avstånd från en startpunkt till ett mål, en räckviddsyta, en punkt till närmaste hållplats eller ett stopp eller en sändning i ruttoptimering och ruttkontroll. En avståndsmatris räknas som hälften av sina celler, högst tio per punkt i den längsta listan. 200 punkter, var och en mot tre depåer, är 600 beräkningar som avstånd med vägfakta och 300 som avståndsmatris.
[Skapa en gratis testnyckel](https://kartpunkt.se/konto) på kontosidan. En plan beställer ni genom att [kontakta oss](https://kartpunkt.se/kontakt), och vi fakturerar enligt avtal.
> Planerna Utvecklare, Bas, Pro och Avtal, vad varje plan ingår med och tillägget Rutter och avstånd. Börja med en gratis testnyckel. Priserna är exkl. moms.
>
> https://kartpunkt.se/priser