{
  "openapi": "3.1.1",
  "info": {
    "title": "TollCalculation.Api | v1",
    "version": "1.0.0",
    "description": "API for toll-station lookup and toll-cost calculations for Norwegian routes, including both synchronous and asynchronous batch workflows. Ferry fares are shown with the attribution required by the Creative Commons BY 4.0 licence of their source: Data from the AutoPASS ferjedatabanken operated by Skyttel (CC BY 4.0). A calculation response carries this wording in dataSources.ferryPricesAttribution when at least one passage is priced from that source; a client that shows ferry fares must show it too.",
    "contact": {
      "name": "Toll Calculation Team (contact details will be published)"
    }
  },
  "servers": [
    {
      "url": "http://localhost:5299"
    }
  ],
  "paths": {
    "/api/toll-calculation/toll-stations": {
      "get": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "List toll stations",
        "description": "Returns a paged list of toll stations from the NVDB-synced reference catalogue. Each station says whether it charges a tariff now (hasTariff), carries the standard tariff of a passenger car with and without AutoPASS (standardTariff), says whether it is on a public or a private road (roadCategory) and, for a station without a tariff, why it has none (noTariffReason: an informational point without a price, not charged). The optional hasTariff parameter returns only the stations with (true) or without (false) a tariff; pagination.total and nextOffset then describe that filtered set.",
        "operationId": "ListTollStations",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Defaults to 50; values outside 1 to 200 are clamped into that range.",
            "example": 10,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Zero-based index of the first station to return. Defaults to 0; negative values count as 0.",
            "example": 0,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          {
            "name": "hasTariff",
            "in": "query",
            "description": "Optional filter on items[].hasTariff: true returns only the stations that charge a tariff now, false only those that do not. Without the parameter every station is returned. Any other value than true or false is a 400 problem. Paging applies to the filtered set: pagination.total is the number of matching stations.",
            "example": true,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TollStationPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/toll-stations/{nvdbId}": {
      "get": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Get a toll station",
        "description": "Returns a single toll station by its NVDB id, with hasTariff, standardTariff, roadCategory and noTariffReason as in the list.",
        "operationId": "GetTollStation",
        "parameters": [
          {
            "name": "nvdbId",
            "in": "path",
            "required": true,
            "description": "NVDB id of the toll station, as returned in items[].nvdbId by the list operation.",
            "example": 82443541,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TollStationItem"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/place-lookups": {
      "post": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Search for a place by name",
        "description": "Searches for places (addresses, towns, points of interest) matching a free-text query, for use as calculation origin/destination/waypoints.",
        "operationId": "LookupPlace",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "query": "Karl Johans gate 1, Oslo",
                "limit": 5,
                "country": "NO"
              },
              "schema": {
                "$ref": "#/components/schemas/PlaceLookupsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceLookupsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/place-lookups/reverse": {
      "post": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Find the place nearest to a coordinate",
        "description": "Reverse geocoding for pick-on-map and device position: returns the places nearest to a coordinate in Norway, Denmark, Sweden or Finland, in the place-lookups candidate shape. A point outside those countries or without a result answers 200 with an empty list. Available only when the HERE place provider is active; otherwise 404. 503 when HERE cannot answer.",
        "operationId": "ReverseLookupPlace",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "lat": 59.9139,
                "lon": 10.7522,
                "limit": 1,
                "country": "NO"
              },
              "schema": {
                "$ref": "#/components/schemas/ReversePlaceLookupsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceLookupsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/calculations/{calculationId}": {
      "get": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Get a completed toll calculation",
        "description": "Retrieves a previously calculated toll result by its calculation id.",
        "operationId": "GetCalculation",
        "parameters": [
          {
            "name": "calculationId",
            "in": "path",
            "required": true,
            "description": "Id of a stored calculation: the calculationId of a calculation response (also the last segment of the Location header of its 201 response). The example is illustrative and answers 404.",
            "example": "01J9ZK3Q8X5M2T7V4N6B0D1E2F",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCalculationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/ferry-quays": {
      "get": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "List ferry quays",
        "description": "Returns a paged list of the ferry quays of the loaded ferry geography, ordered by nsrQuayId, so one call with the default limit returns the whole catalogue. Each quay lists the sambands (ferry connections) that serve it and, per samband, the other quays of that samband as connections, in the order NVDB lists the samband's quays. Each connection carries the standard fare of a small petrol passenger car with and without AutoPASS, or null when no priced, valid fare exists; the response names the fare source, its load time and the attribution that must be shown with a fare. The data is read from memory (the geography is refreshed from NVDB about once a day, the fares from the AutoPASS ferry price database every few hours), never from a provider per request. An empty or not yet loaded catalogue answers 200 with an empty list and dataSources.ferryGeography null.",
        "operationId": "ListFerryQuays",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            },
            "description": "Page size. Defaults to 500; values outside 1 to 500 are clamped into that range.",
            "example": 100
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            },
            "description": "Zero-based index of the first quay to return. Defaults to 0; negative values count as 0.",
            "example": 0
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FerryQuaysPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/calculation-batches": {
      "post": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Submit a batch of toll calculations",
        "description": "Accepts up to 10,000 toll-calculation requests for asynchronous processing and returns a batch id and status URL to poll for results.",
        "operationId": "CreateCalculationBatch",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "requests": [
                  {
                    "origin": {
                      "lat": 59.9139,
                      "lon": 10.7522,
                      "label": "Oslo"
                    },
                    "destination": {
                      "lat": 59.7441,
                      "lon": 10.2045,
                      "label": "Drammen"
                    },
                    "waypoints": [],
                    "vehicle": {
                      "class": "small",
                      "fuelType": "petrol",
                      "autopassAgreement": true
                    },
                    "departureTime": "2026-10-07T08:00:00Z",
                    "includeGeometry": false
                  }
                ]
              },
              "schema": {
                "$ref": "#/components/schemas/CalculationBatchesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Accepted"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/calculation-batches/{batchId}": {
      "get": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Get a calculation batch's status and results",
        "description": "Returns the current status, per-request results, and errors for a previously submitted calculation batch.",
        "operationId": "GetCalculationBatch",
        "parameters": [
          {
            "name": "batchId",
            "in": "path",
            "required": true,
            "description": "Id of a submitted batch: the batchId of the 202 response (also the last segment of its statusUrl). The example is illustrative and answers 404.",
            "example": "01J9ZK4R9Y6N3V8W5P7C1E2F3G",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Result"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    },
    "/api/toll-calculation/calculations": {
      "post": {
        "tags": [
          "TollCalculation.Api"
        ],
        "summary": "Calculate toll for a route",
        "description": "Calculates the toll cost for a route between an origin and destination (with optional waypoints), given a vehicle profile and departure time. A new calculation is answered with 201 Created. Supports an optional Idempotency-Key header: replaying the same key with an identical request body returns the stored result of the original calculation with 200 OK and does not calculate again; reusing the key with a different request body returns 409 Conflict.",
        "operationId": "CalculateToll",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional client-chosen key, at most 128 characters. Replaying the same key with an identical request body returns the original response; replaying it with a different body returns 409.",
            "example": "8f4d2c1e-5b7a-4e0f-9c3d-2a6b1d7e9f40",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "origin": {
                  "lat": 59.9139,
                  "lon": 10.7522,
                  "label": "Oslo"
                },
                "destination": {
                  "lat": 59.7441,
                  "lon": 10.2045,
                  "label": "Drammen"
                },
                "waypoints": [],
                "vehicle": {
                  "class": "small",
                  "fuelType": "petrol",
                  "autopassAgreement": true,
                  "lengthMeters": 4.8
                },
                "departureTime": "2026-10-07T08:00:00Z",
                "includeGeometry": false
              },
              "schema": {
                "$ref": "#/components/schemas/CalculateTollRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created. A new calculation was computed and stored; this is the answer to every request without an Idempotency-Key and to the first request with a key. The Location header points at the stored calculation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalculateTollResponse"
                }
              }
            }
          },
          "200": {
            "description": "OK. The same Idempotency-Key was replayed with an identical request body, so the stored result of the original calculation is returned and nothing is calculated again. A new calculation is never answered with 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalculateTollResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict. The same Idempotency-Key was already used with a different request body. Send a new key for a different request, or repeat the original request body to get the stored result.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "Bearer": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "Bearer": {
        "type": "http",
        "description": "Paste an Entra access token for TollCalculation API.",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    },
    "schemas": {
      "Accepted": {
        "required": [
          "batchId",
          "acceptedAt",
          "statusUrl",
          "estimatedCompletionSeconds"
        ],
        "type": "object",
        "properties": {
          "batchId": {
            "type": "string"
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time"
          },
          "statusUrl": {
            "type": "string"
          },
          "estimatedCompletionSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "AutopassComparisonDetail": {
        "required": [
          "totalWithAutopass",
          "totalWithoutAutopass",
          "savings",
          "requested"
        ],
        "type": "object",
        "description": "The totals of a route with and without an AutoPASS agreement. Both scenarios are priced from the same matched passages; they differ in the AutoPASS discount on Norwegian road stations (20 percent for small vehicles), the agreement price of Danish bridges and the EasyGo fare of ferries, and the hourly rule is applied to each scenario on its own. Each total is the sum of the rounded per-passage amounts (amountWithAutopass and amountWithoutAutopass) of its scenario; a passage priced in neither scenario adds nothing to either total. The object is left out (null) when a passage is priced in only one of the two scenarios, or when an unpriced ferry crossing (FERRY_CROSSING_UNPRICED) has a fare in the other scenario, because the missing price would make up a saving or a cost.",
        "properties": {
          "totalWithAutopass": {
            "description": "The total in NOK with an AutoPASS agreement: the sum of amountWithAutopass over the passages.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "totalWithoutAutopass": {
            "description": "The total in NOK without an AutoPASS agreement: the sum of amountWithoutAutopass over the passages.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "savings": {
            "description": "What an AutoPASS agreement saves in NOK: totalWithoutAutopass minus totalWithAutopass, never below 0.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "requested": {
            "description": "The scenario the request asked for (autopassAgreement). Its total equals totalAmount.",
            "enum": [
              "WITH_AUTOPASS",
              "WITHOUT_AUTOPASS"
            ],
            "type": "string"
          }
        }
      },
      "CalculateTollRequest": {
        "required": [
          "origin",
          "destination",
          "waypoints",
          "vehicle",
          "departureTime"
        ],
        "type": "object",
        "properties": {
          "origin": {
            "$ref": "#/components/schemas/GeoPointRequest"
          },
          "destination": {
            "$ref": "#/components/schemas/GeoPointRequest"
          },
          "waypoints": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/GeoPointRequest"
            }
          },
          "vehicle": {
            "$ref": "#/components/schemas/VehicleRequest"
          },
          "departureTime": {
            "description": "RFC 3339 timestamp. The API checks it against the time it receives the request: it must not be earlier than one minute before that time and must not be later than 30 days after it, otherwise the answer is 400 (\"departureTime must not be in the past.\" or \"departureTime must be within 30 days.\"). The field is required and has no default, so send a current time. The date in the example is a placeholder, not a current time: it is fixed in this contract and a request that sends it unchanged is rejected once that date has passed, so replace it by a time inside that window. Any offset is accepted; rush windows are evaluated in Europe/Oslo local time.",
            "type": "string",
            "format": "date-time",
            "examples": [
              "2026-10-07T08:00:00Z"
            ]
          },
          "routingPreference": {
            "type": [
              "null",
              "string"
            ]
          },
          "hourlyRuleEnabled": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Optional toggle for per-project hourly suppression. null/omitted defaults to true."
          },
          "rushHourEnabled": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Optional toggle for rush-hour multiplier pricing. null/omitted defaults to true."
          },
          "includeGeometry": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Optional toggle for public route geometry. null/omitted defaults to false."
          },
          "geometryDetail": {
            "type": [
              "null",
              "string"
            ],
            "description": "Detail of route.polyline and every alternative polyline when includeGeometry is true. 'full' (default when null/omitted) returns every routing point. 'overview' returns a Douglas-Peucker simplification with a 25 m tolerance over coordinates rounded to 5 decimals, for drawing the whole route on a map; totals, passages, distances and durations are unchanged. Unknown values are rejected with 400. Ignored when includeGeometry is false or omitted. Part of the idempotency request hash when includeGeometry is true.",
            "enum": [
              "full",
              "overview",
              null
            ]
          }
        }
      },
      "CalculateTollResponse": {
        "required": [
          "calculationId",
          "totalAmount",
          "currency",
          "crossesBorder",
          "passageCount",
          "routeSource",
          "warnings",
          "createdAt",
          "route",
          "alternatives",
          "passages",
          "dataSources",
          "countriesTraversed"
        ],
        "type": "object",
        "properties": {
          "calculationId": {
            "type": "string"
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "currency": {
            "type": "string"
          },
          "crossesBorder": {
            "type": "boolean"
          },
          "countriesTraversed": {
            "description": "Ordered, distinct ISO 3166-1 alpha-2 country codes traversed by the route, derived from route endpoints, route geometry, matched toll stations, and authoritative provider span countries when available.",
            "type": "array",
            "items": {
              "pattern": "^[A-Z]{2}$",
              "type": "string"
            }
          },
          "passageCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "routeSource": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Warning"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "route": {
            "$ref": "#/components/schemas/RouteSummary"
          },
          "alternatives": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RouteAlternativeSummary"
            }
          },
          "passages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PassageDetail"
            }
          },
          "dataSources": {
            "$ref": "#/components/schemas/CalculateTollDataSources"
          },
          "fxConversion": {
            "description": "The DKK to NOK rate the Danish passages of the route in this response were converted with, so that a client can show it and the calculation can be audited. null when no passage was converted (a route without a Danish passage, or one whose Danish passages are unpriced). It describes the route and its passages, not the alternatives. GET of the calculation returns the same object.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/FxConversionDetail"
              }
            ]
          },
          "autopassComparison": {
            "description": "The totals of the route with and without an AutoPASS agreement and the saving. Both scenarios are priced from the same matched passages; the total of the requested scenario equals totalAmount. null when a passage is priced in only one of the two scenarios (for example a Danish bridge without an agreement price, or a ferry fare that exists only with or only without an agreement), or when a ferry crossing unpriced in the requested scenario (FERRY_CROSSING_UNPRICED) has a fare in the other one: no comparison is made up from a missing price. Also null when an idempotent replay returns a response stored before this field existed.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/AutopassComparisonDetail"
              }
            ]
          }
        }
      },
      "CalculationBatchesRequest": {
        "required": [
          "requests"
        ],
        "type": "object",
        "properties": {
          "requests": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalculateTollRequest"
            }
          }
        }
      },
      "Candidate": {
        "required": [
          "label",
          "type",
          "lat",
          "lon",
          "country",
          "municipality",
          "county",
          "postalCode",
          "provider",
          "category",
          "authorityClass"
        ],
        "type": "object",
        "properties": {
          "label": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "lat": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "lon": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "country": {
            "type": "string"
          },
          "municipality": {
            "type": [
              "null",
              "string"
            ]
          },
          "county": {
            "type": [
              "null",
              "string"
            ]
          },
          "postalCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "provider": {
            "description": "Deprecated: exposes supplier identity and is scheduled for removal. Use authorityClass instead.",
            "deprecated": true,
            "type": [
              "null",
              "string"
            ]
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "authorityClass": {
            "description": "Supplier-neutral authority class for the candidate.",
            "enum": [
              "NationalRegister",
              "CommercialSupplier",
              "BundledFallback",
              "OpenDataFallback"
            ],
            "default": "BundledFallback",
            "type": "string"
          }
        }
      },
      "DataSources": {
        "required": [
          "tollStations",
          "tollStationsLoadedAt"
        ],
        "type": "object",
        "properties": {
          "tollStations": {
            "description": "NVDB_V4 or DEMO.",
            "enum": [
              "NVDB_V4",
              "DEMO",
              null
            ],
            "type": [
              "null",
              "string"
            ]
          },
          "tollStationsLoadedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "ferryPrices": {
            "description": "AUTOPASS_FERJE_PRISAPI, DEMO_FERRY_FALLBACK, MIXED_FERRY_SOURCES, or null when no ferry passage was priced.",
            "type": [
              "null",
              "string"
            ]
          },
          "ferryPricesLoadedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "ferryPricesAttribution": {
            "description": "The attribution that the Creative Commons BY 4.0 licence of the ferry fares requires: \"Data from the AutoPASS ferjedatabanken operated by Skyttel (CC BY 4.0)\". Present exactly when at least one passage shown in the response is priced from AUTOPASS_FERJE_PRISAPI: a passage of the route itself or of any alternative returned in the create response (the read response has no alternatives), also when other passages come from another source. Null otherwise, so also when only the demo fallback priced a ferry. A client that shows ferry fares must show this text with them.",
            "type": [
              "null",
              "string"
            ]
          },
          "ferryPricingDegraded": {
            "type": "boolean"
          }
        }
      },
      "CalculateTollDataSources": {
        "required": [
          "tollStations",
          "tollStationsLoadedAt"
        ],
        "type": "object",
        "properties": {
          "tollStations": {
            "description": "NVDB_V4 or DEMO.",
            "enum": [
              "NVDB_V4",
              "DEMO",
              null
            ],
            "type": [
              "null",
              "string"
            ]
          },
          "tollStationsLoadedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "ferryPrices": {
            "description": "AUTOPASS_FERJE_PRISAPI, DEMO_FERRY_FALLBACK, MIXED_FERRY_SOURCES, or null when no ferry passage was priced.",
            "type": [
              "null",
              "string"
            ]
          },
          "ferryPricesLoadedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "ferryPricesAttribution": {
            "description": "The attribution that the Creative Commons BY 4.0 licence of the ferry fares requires: \"Data from the AutoPASS ferjedatabanken operated by Skyttel (CC BY 4.0)\". Present exactly when at least one passage shown in the response is priced from AUTOPASS_FERJE_PRISAPI: a passage of the route itself or of any alternative returned in the create response (the read response has no alternatives), also when other passages come from another source. Null otherwise, so also when only the demo fallback priced a ferry. A client that shows ferry fares must show this text with them.",
            "type": [
              "null",
              "string"
            ]
          },
          "ferryPricingDegraded": {
            "type": "boolean"
          },
          "crossBorder": {
            "description": "ROUTE_ENDPOINTS, ROUTE_GEOMETRY, TOLL_STATIONS, ROUTE_SPANS, or null when the route does not cross borders.",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "Error": {
        "required": [
          "requestIndex",
          "code",
          "message"
        ],
        "type": "object",
        "properties": {
          "requestIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "FerryQuayConnection": {
        "required": [
          "toNsrQuayId",
          "toQuayName"
        ],
        "type": "object",
        "properties": {
          "toNsrQuayId": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "toQuayName": {
            "type": "string"
          },
          "standardFare": {
            "description": "The standard fare of the reference vehicle (a passenger car in the shortest length class, 0 - 6 m, petrol) from this quay to the connection quay, valid now: what a calculation over the crossing charges without and with an AutoPASS agreement. null when no priced, valid fare exists for that pair and direction now: the samband is published without a price (zone 0, no amount), the pair has no fare row, the fare row is not valid now, the pair is shared by sambands whose fares differ, or the fares are not loaded. A fare is never 0 and never estimated. Optional: a client must accept a connection without it.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/FerryStandardFare"
              }
            ]
          }
        }
      },
      "FerryQuayItem": {
        "required": [
          "nsrQuayId",
          "name",
          "lat",
          "lon",
          "locationKind",
          "sambands"
        ],
        "type": "object",
        "properties": {
          "nsrQuayId": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64",
            "description": "The number of the NSR stop place id of the quay; equals the quay id of the AutoPASS ferry price database."
          },
          "name": {
            "type": "string"
          },
          "lat": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double",
            "description": "WGS84 latitude."
          },
          "lon": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double",
            "description": "WGS84 longitude."
          },
          "locationKind": {
            "description": "How lat and lon were taken from NVDB: POINT is the quay's own point, POLYGON_CENTROID the centroid of the quay's area geometry.",
            "type": "string",
            "enum": [
              "POINT",
              "POLYGON_CENTROID"
            ]
          },
          "sambands": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FerryQuaySamband"
            },
            "description": "The sambands that serve the quay, ordered by sambandsId. A quay served by several sambands lists each."
          }
        }
      },
      "FerryQuaySamband": {
        "required": [
          "sambandsId",
          "name",
          "connections"
        ],
        "type": "object",
        "properties": {
          "sambandsId": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32",
            "description": "The samband id, equal to the sambandsID of the AutoPASS ferry price database."
          },
          "name": {
            "type": "string"
          },
          "connections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FerryQuayConnection"
            },
            "description": "The other quays of the same samband, in the order NVDB lists them; empty when the samband has no other quay."
          }
        }
      },
      "FerryQuaysDataSources": {
        "required": [
          "ferryGeography",
          "loadedAt"
        ],
        "type": "object",
        "properties": {
          "ferryGeography": {
            "type": [
              "null",
              "string"
            ],
            "description": "The source of the ferry geography: NVDB_V4 when it is loaded, null when the catalogue is empty or not yet loaded."
          },
          "loadedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time",
            "description": "When NVDB was read for the loaded ferry geography; null when it has not been loaded."
          },
          "ferryFares": {
            "description": "The source of the standard fares: AUTOPASS_FERJE_PRISAPI when the fare table of the AutoPASS ferry price database is loaded, DEMO_FERRY_FALLBACK when ferry pricing is switched off (every standardFare is null then; the fallback never prices a ferry), null while the fare table is not loaded or too old (every standardFare is null). Optional.",
            "type": [
              "null",
              "string"
            ]
          },
          "ferryFaresLoadedAt": {
            "description": "When the fare table in use was last verified against the AutoPASS ferry price database, as dataSources.ferryPricesLoadedAt of a calculation; null unless ferryFares is AUTOPASS_FERJE_PRISAPI. Optional.",
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "ferryFaresAttribution": {
            "description": "The attribution that the Creative Commons BY 4.0 licence of the ferry fares requires: \"Data from the AutoPASS ferjedatabanken operated by Skyttel (CC BY 4.0)\". Present exactly when ferryFares is AUTOPASS_FERJE_PRISAPI, null otherwise. A client that shows a standardFare must show this text with it. Optional.",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "FerryQuaysPage": {
        "required": [
          "items",
          "pagination",
          "dataSources"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FerryQuayItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/FerryQuaysPagination"
          },
          "dataSources": {
            "$ref": "#/components/schemas/FerryQuaysDataSources"
          }
        }
      },
      "FerryQuaysPagination": {
        "required": [
          "limit",
          "offset",
          "total",
          "nextOffset"
        ],
        "type": "object",
        "properties": {
          "limit": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "offset": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "total": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "nextOffset": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32",
            "description": "The offset of the next page; null on the last page."
          }
        }
      },
      "FerryStandardFare": {
        "required": [
          "ordinaryAmount",
          "autopassAmount",
          "roadTollAmount",
          "currency",
          "validFrom",
          "validTo",
          "priceSource"
        ],
        "type": "object",
        "description": "The standard fare of a ferry connection for the reference vehicle: a passenger car in the shortest length class (vehicle class small without a length, priced as 0 - 6 m), petrol, at the time of the request, in the direction from the quay to the connection quay. The fare row is selected exactly as a calculation selects it (ADR-010), so a calculation over the same crossing for that vehicle charges the same amounts. A calculation for another vehicle, a longer vehicle, a zero-emission vehicle or another departure time can differ.",
        "properties": {
          "ordinaryAmount": {
            "description": "The amount without an AutoPASS agreement (the full fare), in NOK, including roadTollAmount. Always above 0.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "autopassAmount": {
            "description": "The amount with an AutoPASS agreement (the EasyGo fare of the AutoPASS ferry price database), in NOK, including the road toll of that agreement price (autopassRoadTollAmount), which can be lower than roadTollAmount. Always above 0; usually below ordinaryAmount, and equal to it when the row only charges a road toll that the agreement does not reduce.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "roadTollAmount": {
            "description": "The road toll the fare row charges in addition to the ferry fare (ADR-010), included in ordinaryAmount; null when the row charges no road toll. For a row that only charges a road toll it equals ordinaryAmount.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "currency": {
            "description": "Always NOK: the AutoPASS ferry price database carries no currency and publishes Norwegian kroner.",
            "type": "string",
            "examples": [
              "NOK"
            ]
          },
          "validFrom": {
            "description": "When the fare took effect, Europe/Oslo local time with its offset: the start of the fare row, or for a quay pair served by several sambands the latest start of their rows in force.",
            "type": "string",
            "format": "date-time"
          },
          "validTo": {
            "description": "When the fare stops applying (it no longer applies at this instant), Europe/Oslo local time with its offset: the end of the fare row, or for a quay pair served by several sambands the earliest end of their rows in force, after which the pair can be unpriced; null when there is no end.",
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "priceSource": {
            "description": "Where the fare comes from, with the value of passages[].priceSource in a calculation: AUTOPASS_FERJE_PRISAPI.",
            "type": "string",
            "examples": [
              "AUTOPASS_FERJE_PRISAPI"
            ]
          },
          "autopassRoadTollAmount": {
            "description": "The road toll (the EasyGo road toll of the fare row) that the fare row charges with an AutoPASS agreement in addition to the ferry fare (ADR-010), included in autopassAmount, so autopassAmount is the ferry fare with an agreement plus this amount. It can be lower than roadTollAmount, which is the full-price road toll included in ordinaryAmount. Absent or null when the agreement price carries no road toll. A calculation with an AutoPASS agreement over the same crossing charges the same road toll. Optional: a client must accept a fare without it.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "FxConversionDetail": {
        "required": [
          "from",
          "to",
          "midRate",
          "marginPercent",
          "effectiveRate",
          "fetchedAt",
          "source",
          "staleness"
        ],
        "type": "object",
        "description": "The DKK to NOK rate that the Danish passages of a calculation were converted with (ADR-013). effectiveRate = midRate x (1 + marginPercent / 100) is the rate that finalAmount was converted with: the sourceAmount of a Danish passage times effectiveRate, before any discount.",
        "properties": {
          "from": {
            "description": "Currency that was converted from (DKK).",
            "type": "string"
          },
          "to": {
            "description": "Currency that was converted to (NOK).",
            "type": "string"
          },
          "midRate": {
            "description": "The Norges Bank DKK/NOK mid-rate, in NOK per DKK.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "marginPercent": {
            "description": "The margin added to the mid-rate, in percent (2 for the 2 percent of ADR-013). It is disclosed here and is not a separate charge: it is part of effectiveRate.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "effectiveRate": {
            "description": "The rate applied to the DKK amounts: midRate x (1 + marginPercent / 100), rounded to six decimals.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "fetchedAt": {
            "description": "When the mid-rate was fetched from Norges Bank. For a stored rate that was reused because Norges Bank was unavailable this is the time it was stored, so it can be days before the calculation. For the fallback rate it is the time the fallback was first used.",
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "description": "Where the mid-rate came from. NORGES_BANK: a Norges Bank rate, fetched for this calculation or stored at most 24 hours earlier. NORGES_BANK_LAST_PUBLISHED: the last stored Norges Bank rate, 24 to 72 hours old, used because Norges Bank was unavailable. FX_STATIC_FALLBACK: the hardcoded fallback mid-rate (1.55), used because no Norges Bank rate was available; the NOK amounts are then approximate, and the calculation also carries the warning FX_STATIC_FALLBACK.",
            "type": "string",
            "enum": [
              "NORGES_BANK",
              "NORGES_BANK_LAST_PUBLISHED",
              "FX_STATIC_FALLBACK"
            ]
          },
          "staleness": {
            "description": "How current the rate is. LIVE: fetched for this calculation, or stored at most 24 hours earlier. STALE: Norges Bank was unavailable and the stored rate is 24 to 72 hours old. FALLBACK: no usable Norges Bank rate, the hardcoded fallback rate was used. A stored fallback rate that is reused keeps the source FX_STATIC_FALLBACK while its staleness follows its age (LIVE, then STALE), so use source, or the warning FX_STATIC_FALLBACK, to see that the rate is the fallback. A separate level for weekends and holidays (STALE_WEEKEND in ADR-013) is not implemented.",
            "type": "string",
            "enum": [
              "LIVE",
              "STALE",
              "FALLBACK"
            ]
          }
        }
      },
      "GeoPointRequest": {
        "required": [
          "lat",
          "lon"
        ],
        "type": "object",
        "properties": {
          "lat": {
            "description": "Latitude in decimal degrees (WGS84). The calculation validates 34 to 72.",
            "type": "number",
            "format": "double",
            "minimum": 34,
            "maximum": 72,
            "examples": [
              59.9139
            ]
          },
          "lon": {
            "description": "Longitude in decimal degrees (WGS84). The calculation validates -25 to 45.",
            "type": "number",
            "format": "double",
            "minimum": -25,
            "maximum": 45,
            "examples": [
              10.7522
            ]
          },
          "label": {
            "description": "Optional human-readable label.",
            "type": [
              "null",
              "string"
            ],
            "examples": [
              "Oslo"
            ]
          }
        },
        "additionalProperties": false
      },
      "GetCalculationResponse": {
        "required": [
          "calculationId",
          "totalAmount",
          "currency",
          "crossesBorder",
          "passageCount",
          "routeSource",
          "warnings",
          "createdAt",
          "passages",
          "dataSources"
        ],
        "type": "object",
        "properties": {
          "calculationId": {
            "type": "string"
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "currency": {
            "type": "string"
          },
          "crossesBorder": {
            "type": "boolean"
          },
          "passageCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "routeSource": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Warning"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "passages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PassageDetail"
            }
          },
          "dataSources": {
            "$ref": "#/components/schemas/DataSources"
          },
          "fxConversion": {
            "description": "The DKK to NOK rate the Danish passages were converted with, read back from the stored calculation: the same object as in the create response. null when no passage was converted (a route without a Danish passage, or one whose Danish passages are unpriced) and for a calculation stored before this field existed.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/FxConversionDetail"
              }
            ]
          },
          "autopassComparison": {
            "description": "The totals with and without an AutoPASS agreement and the saving, read back from the stored passages: the same object as in the create response. null when a passage is priced in only one of the two scenarios or the route had an unpriced ferry crossing with a fare in the other scenario, for a calculation stored before this field existed and for a calculation without passages.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/AutopassComparisonDetail"
              }
            ]
          }
        }
      },
      "HttpValidationProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "traceId": {
            "description": "Identifier that the API assigned to the request (the ASP.NET Core request trace identifier, for example 0HNPBITH56JTQ:00000013); it is not the W3C trace id. It identifies the failed request, so a client can quote it in a support request and the operator can find the call in the service's logs. The API adds it to the problem responses it writes; a client must not require it.",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "Pagination": {
        "required": [
          "limit",
          "offset",
          "total",
          "nextOffset"
        ],
        "type": "object",
        "properties": {
          "limit": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "offset": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "total": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "nextOffset": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "PassageDetail": {
        "required": [
          "index",
          "tollStationNvdbId",
          "tollStationName",
          "bompengeprosjektKey",
          "country",
          "timestamp",
          "basePrice",
          "appliedPeriod",
          "finalAmount",
          "currency",
          "suppressedByHourlyRule",
          "priceConfidence"
        ],
        "type": "object",
        "properties": {
          "index": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "tollStationNvdbId": {
            "type": "string"
          },
          "tollStationName": {
            "type": "string"
          },
          "bompengeprosjektKey": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "basePrice": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "appliedPeriod": {
            "type": "string"
          },
          "finalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "currency": {
            "type": "string"
          },
          "suppressedByHourlyRule": {
            "type": "boolean"
          },
          "priceSource": {
            "description": "Applied tariff source, such as OPERATOR_FJELLINJEN, OPERATOR_FERDE, NVDB_V4, TARIFF_TABLE (a Norwegian road passage priced from the operator price list by fuel group and period, for Fjellinjen and Bypakke Bergen), BROBIZZ_RECIPROCITY, DK_CURATED_TARIFF (a Danish bridge tariff curated from the operator price list) or DK_TARIFF_UNAVAILABLE (a Danish passage left unpriced because no current tariff exists).",
            "type": [
              "null",
              "string"
            ]
          },
          "priceVerifiedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "priceConfidence": {
            "enum": [
              "HIGH",
              "MEDIUM",
              "LOW"
            ],
            "type": "string"
          },
          "sourceAmount": {
            "description": "The passage price in sourceCurrency before conversion to NOK. Set for Danish passages, null for passages priced in NOK. finalAmount is this amount converted with the effective FX rate (Norges Bank DKK/NOK mid-rate plus 2 percent), then discounted where a discount applies. Also null for a Danish passage that is unpriced (see the DK_TARIFF_UNAVAILABLE warning).",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "sourceCurrency": {
            "description": "ISO currency code of sourceAmount (DKK for Danish passages). Null for passages priced in NOK.",
            "type": [
              "null",
              "string"
            ]
          },
          "amountWithAutopass": {
            "description": "What this passage costs in NOK with an AutoPASS agreement, after the discounts and the hourly rule of that scenario. 0 when the hourly rule covers the passage. null when the passage has no price with an agreement (null means not priced, never free) and for a calculation stored before this field existed. When priced, the amount of the requested scenario equals finalAmount.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "amountWithoutAutopass": {
            "description": "What this passage costs in NOK without an AutoPASS agreement, after the discounts and the hourly rule of that scenario. 0 when the hourly rule covers the passage. null when the passage has no price without an agreement (null means not priced, never free) and for a calculation stored before this field existed. When priced, the amount of the requested scenario equals finalAmount.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "agreementProgram": {
            "description": "The agreement behind the price a passage has with an agreement: ORESUNDGO for the Oresund bridge, whose agreement price is the OresundGO price (a separate OresundGO agreement is needed; an AutoPASS or BroBizz agreement alone does not give it), null for every other passage (Storebaelt and every Norwegian station: the plain AutoPASS price). Stored with the calculation when the passage is priced, so GET /calculations/{id} returns what was stored: null for a calculation made before the OresundGO price (its Oresund agreement amount is the earlier 470 DKK). A code only, never text for display: a client labels the amount. More codes may be added later, so a client must handle an unknown value.",
            "type": [
              "null",
              "string"
            ],
            "examples": [
              "ORESUNDGO"
            ]
          },
          "noTariffReason": {
            "description": "Read-only reason why the calculation did not charge this passage, so a client can tell a station without a price (an informational point, which finalAmount 0 would show as free) from one that is free. Set only for a passage at an informational station: the catalogue row of the station holds no price and the passage was priced from that row at nothing (priceSource NVDB_V4 or STANDARD, basePrice, finalAmount and both agreement amounts 0). It is the noTariffReason of the same station in GET /toll-stations: ferry_collection_point (the toll collection point of a ferry crossing without its own tariff; the ferry fare carries the price), private_road (a station on a private or forest road without a published tariff) or price_not_published (any other station without a tariff, for example a new AutoPASS station whose operator has not published a price yet). null for every priced passage, also one priced at a published rate of 0 (a tariff table or an operator price list, priceSource TARIFF_TABLE or OPERATOR_<operator>), one a discount or the hourly rule brought to 0, a Danish bridge and a ferry crossing. It changes no amount: totalAmount, basePrice, finalAmount and every other field are the same with or without it. Derived when the response is built and never stored, so GET /calculations/{id} returns it for a calculation made before this field existed, and the idempotency hash is unchanged. An idempotent replay derives it the same way, also for an Idempotency-Key stored before this field existed, so a replay and GET of the same calculation give the same value on every passage. Optional in the contract (added after v1); the API always returns it (null when the passage is priced). A client should treat a value it does not know as price_not_published.",
            "type": [
              "null",
              "string"
            ],
            "readOnly": true,
            "examples": [
              "private_road"
            ]
          }
        }
      },
      "PlaceLookupsRequest": {
        "anyOf": [
          {
            "required": [
              "focusLatitude",
              "focusLongitude"
            ]
          },
          {
            "properties": {
              "focusLatitude": {
                "type": "null"
              },
              "focusLongitude": {
                "type": "null"
              }
            }
          }
        ],
        "required": [
          "query"
        ],
        "type": "object",
        "properties": {
          "query": {
            "description": "Free-text search, 1 to 200 characters.",
            "type": "string",
            "examples": [
              "Karl Johans gate 1, Oslo"
            ]
          },
          "limit": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "country": {
            "pattern": "^\\s*(?:(?:[Nn][Oo]|[Dd][Kk]|[Ss][Ee]|[Ff][Ii]|[Aa][Xx])\\s*)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "region": {
            "type": [
              "null",
              "string"
            ]
          },
          "viewbox": {
            "description": "Comma-separated or semicolon-separated west,south,east,north coordinates in decimal degrees.",
            "type": [
              "null",
              "string"
            ]
          },
          "focusLatitude": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "minimum": -90,
            "maximum": 90,
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "focusLongitude": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "minimum": -180,
            "maximum": 180,
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "PlaceLookupsResponse": {
        "required": [
          "candidates"
        ],
        "type": "object",
        "properties": {
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Candidate"
            }
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          },
          "traceId": {
            "description": "Identifier that the API assigned to the request (the ASP.NET Core request trace identifier, for example 0HNPBITH56JTQ:00000013); it is not the W3C trace id. It identifies the failed request, so a client can quote it in a support request and the operator can find the call in the service's logs. The API adds it to the problem responses it writes; a client must not require it.",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "Result": {
        "required": [
          "batchId",
          "status",
          "totalRequests",
          "completed",
          "failed",
          "results",
          "errors",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "batchId": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "totalRequests": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "completed": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "failed": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalculateTollResponse"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Error"
            }
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReversePlaceLookupsRequest": {
        "required": [
          "lat",
          "lon"
        ],
        "type": "object",
        "properties": {
          "lat": {
            "description": "Latitude in decimal degrees (WGS84).",
            "maximum": 90,
            "minimum": -90,
            "type": "number",
            "format": "double"
          },
          "lon": {
            "description": "Longitude in decimal degrees (WGS84).",
            "maximum": 180,
            "minimum": -180,
            "type": "number",
            "format": "double"
          },
          "limit": {
            "description": "Maximum number of candidates, nearest first. Defaults to 1.",
            "maximum": 5,
            "minimum": 1,
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "country": {
            "description": "Optional country filter: NO, DK, SE, FI or AX (Aland is answered as FI).",
            "pattern": "^\\s*(?:(?:[Nn][Oo]|[Dd][Kk]|[Ss][Ee]|[Ff][Ii]|[Aa][Xx])\\s*)?$",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RouteAlternativeSummary": {
        "required": [
          "label",
          "totalAmount",
          "currency",
          "crossesBorder",
          "passageCount",
          "route",
          "warnings",
          "passages",
          "countriesTraversed"
        ],
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Alternative category label. Uses Raskest (quickest), Rimeligst (cheapest), Korteste (shortest), combined labels when the same route wins multiple categories, and Alternativ N as fallback."
          },
          "totalAmount": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "currency": {
            "type": "string"
          },
          "crossesBorder": {
            "type": "boolean"
          },
          "countriesTraversed": {
            "description": "Ordered, distinct ISO 3166-1 alpha-2 country codes traversed by this alternative route, including authoritative provider span countries when available.",
            "type": "array",
            "items": {
              "pattern": "^[A-Z]{2}$",
              "type": "string"
            }
          },
          "passageCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "route": {
            "$ref": "#/components/schemas/RouteSummary"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Warning"
            }
          },
          "passages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PassageDetail"
            }
          },
          "autopassComparison": {
            "description": "The totals of this alternative with and without an AutoPASS agreement and the saving, priced from the same matched passages. The total of the requested scenario equals totalAmount of the alternative. null when a passage of this alternative is priced in only one of the two scenarios, or when this alternative has an unpriced ferry crossing (FERRY_CROSSING_UNPRICED) with a fare in the other scenario.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/AutopassComparisonDetail"
              }
            ]
          }
        }
      },
      "RoutePoint": {
        "required": [
          "lat",
          "lon"
        ],
        "type": "object",
        "properties": {
          "lat": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "lon": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "RouteSummary": {
        "required": [
          "distanceMeters",
          "durationSeconds",
          "polyline"
        ],
        "type": "object",
        "properties": {
          "distanceMeters": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "durationSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "polyline": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RoutePoint"
            }
          }
        }
      },
      "StandardTariff": {
        "required": [
          "vehicle",
          "currency",
          "ordinaryAmount",
          "autopassAmount",
          "ordinaryAmountNok",
          "autopassAmountNok",
          "validFrom",
          "verifiedAt",
          "priceSource",
          "agreementProgram"
        ],
        "type": "object",
        "description": "The standard tariff of a toll station for the reference vehicle: a passenger car (takstgruppe 1, vehicle class small), petrol, off-peak, without a rush-hour multiplier, priced at the time of the request. The amounts follow the rules of a calculation: a fresh operator tariff before the NVDB catalogue price, the AutoPASS discount of a calculation for a Norwegian station, and for a Danish bridge the effective Danish tariff (ADR-032) converted at the rate a calculation uses. A calculation for another vehicle, another time or with the hourly rule can differ.",
        "properties": {
          "vehicle": {
            "$ref": "#/components/schemas/StandardTariffVehicle"
          },
          "currency": {
            "description": "Currency of ordinaryAmount and autopassAmount: the station's own currency, NOK for a Norwegian station and DKK for a Danish bridge.",
            "type": "string",
            "examples": [
              "NOK",
              "DKK"
            ]
          },
          "ordinaryAmount": {
            "description": "The amount without an AutoPASS agreement, in currency.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "autopassAmount": {
            "description": "The amount with an AutoPASS agreement, in currency. For a Norwegian station it is ordinaryAmount less the AutoPASS discount of a calculation (20 percent for a small vehicle); for Storebaelt it is the AutoPASS-BroBizz reciprocity price and for the Oresund bridge the OresundGO price (see agreementProgram). null only when a Danish bridge publishes no agreement price; a calculation then leaves the passage unpriced for a vehicle with an agreement.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "ordinaryAmountNok": {
            "description": "ordinaryAmount in NOK, converted at the DKK to NOK rate a calculation uses (Norges Bank mid-rate plus the ADR-013 margin). null for a NOK station.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "autopassAmountNok": {
            "description": "autopassAmount in NOK, at the same rate as ordinaryAmountNok. null for a NOK station and when autopassAmount is null.",
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "validFrom": {
            "description": "When the tariff took effect: the start of a verified operator tariff, or the date (at 00:00 UTC) a Danish tariff row takes effect. null for a price from the NVDB catalogue, which carries no start date.",
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "verifiedAt": {
            "description": "When the price was last verified: the verification time of an operator tariff, the NVDB sync time of a catalogue price, or the date (at 00:00 UTC) a Danish tariff was retrieved from the operator. null when unknown.",
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "priceSource": {
            "description": "Where the price comes from, with the values of passages[].priceSource in a calculation: NVDB_V4 for the NVDB catalogue, OPERATOR_<operator> for a verified operator tariff, TARIFF_TABLE for a Norwegian road passage priced from the operator price list by fuel group and period, DK_CURATED_TARIFF for a Danish bridge.",
            "type": "string",
            "examples": [
              "NVDB_V4",
              "DK_CURATED_TARIFF"
            ]
          },
          "agreementProgram": {
            "description": "The agreement behind autopassAmount: ORESUNDGO for the Oresund bridge, whose autopassAmount is the OresundGO price (a separate OresundGO agreement is needed; an AutoPASS or BroBizz agreement alone does not give it), null for every other station (Storebaelt and every Norwegian station: the plain AutoPASS price). A code only, never text for display: a client labels the amount. More codes may be added later, so a client must handle an unknown value.",
            "type": [
              "null",
              "string"
            ],
            "examples": [
              "ORESUNDGO"
            ]
          }
        }
      },
      "StandardTariffVehicle": {
        "required": [
          "class",
          "fuelType"
        ],
        "type": "object",
        "description": "The reference vehicle a standard tariff is priced for.",
        "properties": {
          "class": {
            "description": "Vehicle class: small (takstgruppe 1).",
            "type": "string",
            "enum": [
              "small"
            ]
          },
          "fuelType": {
            "description": "Fuel type: petrol, which earns no environmental discount.",
            "type": "string",
            "enum": [
              "petrol"
            ]
          }
        }
      },
      "TollStationItem": {
        "required": [
          "nvdbId",
          "name",
          "operator",
          "bompengeprosjekt",
          "country",
          "currency",
          "lat",
          "lon",
          "syncedAt"
        ],
        "type": "object",
        "properties": {
          "nvdbId": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "tollPointGroupId": {
            "description": "Read-only id of the toll point this station belongs to. The lane and direction variants of one toll point (same name, same toll project and country, at most 30 m apart, linked in chains) are separate stations with their own nvdbId and share one tollPointGroupId, which is the lowest nvdbId of the current members of the group; a station without such a neighbour has its own nvdbId. The id is stable within one catalogue snapshot and for an unchanged catalogue, but a catalogue sync that adds a lower nvdbId or removes the lowest member changes it, so do not persist it. The id is computed from the whole catalogue, so it is the same on every page, with or without the hasTariff filter, and in GET of one station. It changes no station id, tariff or calculation. Optional in the contract (added after v1); the API always returns it.",
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "name": {
            "type": "string"
          },
          "operator": {
            "type": "string"
          },
          "bompengeprosjekt": {
            "type": [
              "null",
              "string"
            ]
          },
          "country": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "lat": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "lon": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "syncedAt": {
            "type": "string",
            "format": "date-time"
          },
          "hasTariff": {
            "description": "True when the station charges a tariff now: a small or a large price above 0 applies at the time of the request (a fresh operator tariff, else the NVDB catalogue price; for a Danish bridge its effective, current tariff). False for a station without a price, with a price of 0, or a Danish bridge whose tariff is missing or stale; such a station adds nothing to a calculation.",
            "type": "boolean"
          },
          "standardTariff": {
            "description": "The standard tariff of a passenger car at this station. null when the station has no small-vehicle price above 0 now, which includes every station with hasTariff false and a station that only charges large vehicles.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/StandardTariff"
              }
            ]
          },
          "roadCategory": {
            "description": "Read-only category of the road the station collects on, so a client can tell a private toll road from a public road without reading the operator or the name. PUBLIC_ROAD: a toll point on a public road (NVDB road category E, R, F or K), or a station of a named toll project (a bompengeprosjekt other than GENERIC_STREKNING, such as an AutoPASS city package or a Danish bridge) whatever road NVDB registers it on. PRIVATE_ROAD: a toll point on a private road or a forest road (NVDB road category P or S) that belongs to no named toll project (bompengeprosjekt is GENERIC_STREKNING or null), the NVDB station kind 'Bomstasjon for lokal/privat veg'. FERRY_COLLECTION_POINT: the toll collection point of a ferry crossing (NVDB station kind 'Ferjeinnkrevingspunkt' or a station named 'Ferje ...'); the toll-station list leaves these stations out today, so the list does not return this value, but a client must accept it. UNKNOWN: the catalogue holds no NVDB road reference for the station, so it cannot tell; treat it as not private. The category is derived when the station is read from the stored catalogue: it never changes a tariff, an id or a calculation. Never null. Optional in the contract (added after v1); the API always returns it. A client should treat a value it does not know as UNKNOWN.",
            "type": "string",
            "readOnly": true,
            "enum": [
              "PUBLIC_ROAD",
              "PRIVATE_ROAD",
              "FERRY_COLLECTION_POINT",
              "UNKNOWN"
            ]
          },
          "noTariffReason": {
            "description": "Read-only reason why the station has no tariff, so a client can show it as an informational point without a price instead of at 0 (which reads as free). null when hasTariff is true. ferry_collection_point: the toll collection point of a ferry crossing without its own tariff (NVDB 'Ferjeinnkrevingspunkt', for example Halhjem and Sandvikvåg); the ferry fare carries the price, and the toll-station list leaves these stations out today, so the list does not return this value, but a client must accept it. private_road: a station on a private or forest road without a published tariff (roadCategory PRIVATE_ROAD, for example Gåsbu). price_not_published: any other station without a tariff, for example a new AutoPASS station whose operator has not published a price yet. A calculation does not charge a station with a reason; once a price is published (NVDB through the sync, an operator tariff or a project table) hasTariff turns true and the reason is null. Derived when the station is read; it never changes a tariff, an id or a calculation. Optional in the contract (added after v1); the API always returns it (null when there is a tariff). A client should treat a value it does not know as price_not_published.",
            "type": [
              "null",
              "string"
            ],
            "readOnly": true,
            "examples": [
              "private_road"
            ]
          }
        }
      },
      "TollStationPage": {
        "required": [
          "items",
          "pagination"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TollStationItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "VehicleRequest": {
        "required": [
          "class",
          "fuelType"
        ],
        "type": "object",
        "properties": {
          "class": {
            "description": "Vehicle class: \"large\" is priced as a large vehicle, any other value as small.",
            "type": "string",
            "examples": [
              "small"
            ]
          },
          "fuelType": {
            "description": "Fuel type: electric, hydrogen, hybrid or diesel; any other value is treated as petrol.",
            "type": "string",
            "examples": [
              "petrol"
            ]
          },
          "weightKg": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "autopassAgreement": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "lengthMeters": {
            "description": "Optional vehicle length in metres, greater than 0 and at most 30, with at most two decimals (a number, or the same value as a quoted decimal such as \"4.8\"; the pattern states the quoted form). It is used for ferry pricing only: a ferry crossing is priced from the fare class whose length range contains it (over the lower bound, up to and including the upper bound, so 6.00 is in \"0 - 6m\" and 6.01 in \"6.01 - 8m\"), whatever the value of class. When it is absent, a small vehicle is priced as 0 - 6 m and a large vehicle gets no ferry fare. A request that differs only in this value is a different request for the idempotency check.",
            "pattern": "^(?:0\\.(?:0[1-9]|[1-9][0-9]?)|(?:[1-9]|[12][0-9])(?:\\.[0-9]{1,2})?|30(?:\\.0{1,2})?)$",
            "maximum": 30,
            "minimum": 0.01,
            "multipleOf": 0.01,
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double",
            "examples": [
              4.8
            ]
          }
        }
      },
      "Warning": {
        "required": [
          "code",
          "message"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable warning code. The codes of the calculation itself are NO_TOLL_STATIONS_ON_ROUTE, FERRY_PRICE_UNAVAILABLE, FERRY_CROSSING_UNPRICED, DK_TARIFF_UNAVAILABLE and FX_STATIC_FALLBACK; the routing layer can add more, such as ROUTE_DEGRADED_STRAIGHT_LINE and HERE_ROUTE_SOURCE_UNAVAILABLE. FERRY_PRICE_UNAVAILABLE means that the ferry price source failed, so no ferry was priced on the route. FERRY_CROSSING_UNPRICED means that a ferry crossing was found on the route but has no usable fare (no valid fare for the date of departure, a vehicle that cannot be priced such as a large vehicle without vehicle.lengthMeters, or a crossing that could not be matched unambiguously), or that the route uses a ferry crossing outside the AutoPASS ferry data, such as an international crossing (for example Kristiansand - Hirtshals), which has no fare in the API; the crossing adds nothing to totalAmount, so show the total as incomplete. It is raised once per such crossing, at most 10 times, and one further FERRY_CROSSING_UNPRICED warning gives the number of the remaining crossings. FX_STATIC_FALLBACK means that the Danish passages were converted from DKK to NOK with the hardcoded fallback rate because no Norges Bank rate was available, so their NOK amounts are approximate. A calculation converted with a Norges Bank rate does not carry it."
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "TollCalculation.Api"
    }
  ]
}
