{
  "openapi": "3.1.0",
  "info": {
    "title": "Vignette ID public API",
    "summary": "Read-only catalogue of European e-vignettes, toll products and country toll rules.",
    "description": "Vignette ID sells official digital motorway vignettes (e-vignettes) and Alpine section-toll\ntickets for Austria, Switzerland, Czechia, Slovakia, Hungary, Slovenia, Romania, Bulgaria and\nMoldova. This document describes the public, unauthenticated read endpoints served from\nhttps://e-vignettes.eu: the list of covered countries, the editorial toll rules for each of them, and the\nlive product catalogue with prices.\n\nNo API key is required. Requests are rate limited per IP address and every response carries\nthe RateLimit-Policy and RateLimit header fields of\ndraft-ietf-httpapi-ratelimit-headers-09, alongside the legacy RateLimit-Limit /\nRateLimit-Remaining / RateLimit-Reset triplet, so a client can self-throttle without guessing.\nResponses are also edge-cached: honour Cache-Control rather than polling.\n\nPlacing an order is not part of this API. Issuing a vignette requires the authenticated partner\nAPI on vignette.id, which is also exposed as an MCP server at https://api.vignette.id/mcp\n(sandbox https://sandbox-api.vignette.id/mcp) for agents that would rather call tools than\nendpoints. Request credentials at https://e-vignettes.eu/developers.",
    "version": "1.0.0",
    "contact": {
      "name": "Vignette ID partner team",
      "email": "work@vignette.id",
      "url": "https://e-vignettes.eu/developers"
    },
    "termsOfService": "https://e-vignettes.eu/terms",
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Vignette-ID-Proprietary"
    }
  },
  "externalDocs": {
    "description": "Partner API reference — authenticated ordering endpoints and the MCP server",
    "url": "https://docs.vgnt.app"
  },
  "servers": [
    {
      "url": "https://e-vignettes.eu/api/v1",
      "description": "Production"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Countries",
      "description": "Which countries are covered and how their toll systems work."
    },
    {
      "name": "Products",
      "description": "Live vignette and section-toll catalogue with prices."
    }
  ],
  "paths": {
    "/countries": {
      "get": {
        "operationId": "listCountries",
        "tags": [
          "Countries"
        ],
        "summary": "List every country Vignette ID sells tolls for",
        "description": "Returns the nine covered countries with their ISO code, the national toll operator, a one-paragraph summary of the toll system and links to the human page and to that country's products. Use this first to discover valid `country` values for `listProducts`.",
        "responses": {
          "200": {
            "description": "The full country list. Never paginated: the list is nine items long.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The quota policy in force, per draft-ietf-httpapi-ratelimit-headers-09, e.g. \"public-read\";q=240;w=60.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";q=240;w=60"
                  ]
                }
              },
              "RateLimit": {
                "description": "Remaining quota (r) and seconds until the window resets (t), e.g. \"public-read\";r=239;t=43.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";r=239;t=43"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "Legacy triplet, kept for older clients: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    240
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Legacy triplet: requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    239
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Legacy triplet: seconds until the window resets.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CountryList"
                }
              }
            }
          },
          "405": {
            "description": "The endpoint is read-only; only GET and OPTIONS are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The per-IP quota for this policy is exhausted. `Retry-After` gives the seconds to wait; the RateLimit fields carry the same reset.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The quota policy in force, per draft-ietf-httpapi-ratelimit-headers-09, e.g. \"public-read\";q=240;w=60.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";q=240;w=60"
                  ]
                }
              },
              "RateLimit": {
                "description": "Remaining quota (r) and seconds until the window resets (t), e.g. \"public-read\";r=239;t=43.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";r=239;t=43"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "Legacy triplet, kept for older clients: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    240
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Legacy triplet: requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    239
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Legacy triplet: seconds until the window resets.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/countries/{code}": {
      "get": {
        "operationId": "getCountry",
        "tags": [
          "Countries"
        ],
        "summary": "Get the toll rules for one country",
        "description": "Returns everything the country guide states: which roads are tolled, vehicle categories and validity periods, activation and plate registration, fines and enforcement, tolls not covered by the vignette, a country FAQ, and the official sources each fact was checked against. Prices are not included here — call `listProducts` for those.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "ISO 3166-1 alpha-2 country code, lower case.",
            "schema": {
              "type": "string",
              "enum": [
                "at",
                "ch",
                "cz",
                "sk",
                "hu",
                "si",
                "ro",
                "bg",
                "md"
              ],
              "examples": [
                "at"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The country guide.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The quota policy in force, per draft-ietf-httpapi-ratelimit-headers-09, e.g. \"public-read\";q=240;w=60.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";q=240;w=60"
                  ]
                }
              },
              "RateLimit": {
                "description": "Remaining quota (r) and seconds until the window resets (t), e.g. \"public-read\";r=239;t=43.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";r=239;t=43"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "Legacy triplet, kept for older clients: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    240
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Legacy triplet: requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    239
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Legacy triplet: seconds until the window resets.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CountryDetail"
                }
              }
            }
          },
          "404": {
            "description": "No country with that code. `error.hint` lists the valid codes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The endpoint is read-only; only GET and OPTIONS are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The per-IP quota for this policy is exhausted. `Retry-After` gives the seconds to wait; the RateLimit fields carry the same reset.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The quota policy in force, per draft-ietf-httpapi-ratelimit-headers-09, e.g. \"public-read\";q=240;w=60.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";q=240;w=60"
                  ]
                }
              },
              "RateLimit": {
                "description": "Remaining quota (r) and seconds until the window resets (t), e.g. \"public-read\";r=239;t=43.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";r=239;t=43"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "Legacy triplet, kept for older clients: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    240
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Legacy triplet: requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    239
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Legacy triplet: seconds until the window resets.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products": {
      "get": {
        "operationId": "listProducts",
        "tags": [
          "Products"
        ],
        "summary": "List vignette and section-toll products with live prices",
        "description": "Returns the active catalogue. Each product is one country plus one vehicle type; its `offers` array holds one entry per validity period, with the total customer price, the underlying government price and the partner fee stated separately. Omit `country` to get every country in one call.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Restrict to one country, by ISO 3166-1 alpha-2 code (lower case). Omit for all nine.",
            "schema": {
              "type": "string",
              "enum": [
                "at",
                "ch",
                "cz",
                "sk",
                "hu",
                "si",
                "ro",
                "bg",
                "md"
              ],
              "examples": [
                "at"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "`vignette` for ordinary motorway vignettes, `tunnel` for the Alpine section tolls that a vignette does not cover.",
            "schema": {
              "type": "string",
              "enum": [
                "vignette",
                "tunnel"
              ],
              "default": "vignette"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching products. An empty `data` array means the filter matched nothing.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The quota policy in force, per draft-ietf-httpapi-ratelimit-headers-09, e.g. \"public-read\";q=240;w=60.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";q=240;w=60"
                  ]
                }
              },
              "RateLimit": {
                "description": "Remaining quota (r) and seconds until the window resets (t), e.g. \"public-read\";r=239;t=43.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";r=239;t=43"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "Legacy triplet, kept for older clients: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    240
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Legacy triplet: requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    239
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Legacy triplet: seconds until the window resets.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductList"
                }
              }
            }
          },
          "400": {
            "description": "A query parameter was not one of the allowed values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The endpoint is read-only; only GET and OPTIONS are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The per-IP quota for this policy is exhausted. `Retry-After` gives the seconds to wait; the RateLimit fields carry the same reset.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The quota policy in force, per draft-ietf-httpapi-ratelimit-headers-09, e.g. \"public-read\";q=240;w=60.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";q=240;w=60"
                  ]
                }
              },
              "RateLimit": {
                "description": "Remaining quota (r) and seconds until the window resets (t), e.g. \"public-read\";r=239;t=43.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"public-read\";r=239;t=43"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "Legacy triplet, kept for older clients: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    240
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Legacy triplet: requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    239
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Legacy triplet: seconds until the window resets.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    43
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The upstream catalogue could not be reached. Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every failure from this API, including an unmatched /api/* URL, uses this shape. Branch on `error.code`, show `error.message`, act on `error.hint`.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint",
              "status",
              "documentation_url"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code. Safe to branch on.",
                "enum": [
                  "not_found",
                  "invalid_parameter",
                  "method_not_allowed",
                  "rate_limited",
                  "upstream_unavailable",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable description of what failed."
              },
              "hint": {
                "type": "string",
                "description": "What to change to make the call succeed."
              },
              "status": {
                "type": "integer",
                "description": "HTTP status code, repeated in the body."
              },
              "documentation_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "Operator": {
        "type": "object",
        "description": "The national authority that issues the toll product.",
        "required": [
          "name",
          "system_name",
          "url"
        ],
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Official operator, e.g. \"ASFINAG\"."
          },
          "system_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the toll system if it has one, e.g. \"HU-GO\"."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Operator website."
          }
        }
      },
      "CountrySummary": {
        "type": "object",
        "required": [
          "code",
          "name",
          "iso_code",
          "operator",
          "summary",
          "page_url",
          "products_url",
          "last_reviewed"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2, lower case.",
            "examples": [
              "at"
            ]
          },
          "name": {
            "type": "string",
            "description": "English country name.",
            "examples": [
              "Austria"
            ]
          },
          "iso_code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2, upper case.",
            "examples": [
              "AT"
            ]
          },
          "operator": {
            "$ref": "#/components/schemas/Operator"
          },
          "summary": {
            "type": "string",
            "description": "One paragraph on how the country's toll works."
          },
          "page_url": {
            "type": "string",
            "format": "uri",
            "description": "Human country guide."
          },
          "products_url": {
            "type": "string",
            "format": "uri",
            "description": "Products filtered to this country."
          },
          "last_reviewed": {
            "type": "string",
            "format": "date",
            "description": "ISO date the facts were last verified against the operator."
          }
        }
      },
      "CountryDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CountrySummary"
          },
          {
            "type": "object",
            "required": [
              "subtitle",
              "sections",
              "faq",
              "sources"
            ],
            "properties": {
              "subtitle": {
                "type": "string"
              },
              "sections": {
                "type": "array",
                "description": "The country guide in reading order.",
                "items": {
                  "type": "object",
                  "required": [
                    "heading",
                    "paragraphs",
                    "bullets"
                  ],
                  "properties": {
                    "heading": {
                      "type": "string"
                    },
                    "paragraphs": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "bullets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "faq": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "question",
                    "answer"
                  ],
                  "properties": {
                    "question": {
                      "type": "string"
                    },
                    "answer": {
                      "type": "string"
                    }
                  }
                }
              },
              "sources": {
                "type": "array",
                "description": "Official pages each fact was checked against.",
                "items": {
                  "type": "object",
                  "required": [
                    "label",
                    "url"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "CountryList": {
        "type": "object",
        "required": [
          "object",
          "count",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list"
          },
          "count": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CountrySummary"
            }
          }
        }
      },
      "Offer": {
        "type": "object",
        "description": "One validity period of a product, priced.",
        "required": [
          "validity",
          "validity_label",
          "total_price",
          "government_price",
          "partner_fee",
          "currency",
          "restrictions"
        ],
        "properties": {
          "validity": {
            "type": "string",
            "description": "Validity in days (\"1\", \"10\", \"365\") or trips (\"1j\") for section tolls.",
            "examples": [
              "10"
            ]
          },
          "validity_label": {
            "type": "string",
            "description": "Human label.",
            "examples": [
              "10-day"
            ]
          },
          "total_price": {
            "type": "number",
            "description": "What the customer pays, fee included."
          },
          "government_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Price set by the national operator, where the operator publishes one."
          },
          "partner_fee": {
            "type": [
              "number",
              "null"
            ],
            "description": "Vignette ID's service fee."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217, upper case.",
            "examples": [
              "EUR"
            ]
          },
          "restrictions": {
            "type": "array",
            "description": "Conditions checkout must handle, e.g. a start date no earlier than tomorrow.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "id",
          "title",
          "type",
          "country",
          "vehicle_type",
          "status",
          "checkout_url",
          "offers"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable product identifier.",
            "examples": [
              "vignette-at-2a"
            ]
          },
          "title": {
            "type": "string",
            "examples": [
              "Vignette 2A"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "vignette",
              "tunnel"
            ]
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2, lower case."
          },
          "vehicle_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Vehicle class the product applies to. Null for section tolls, which are priced per crossing rather than per vehicle class.",
            "enum": [
              "car",
              "van",
              "moto",
              "trailer",
              null
            ]
          },
          "status": {
            "type": "string",
            "description": "Only `active` products are returned."
          },
          "checkout_url": {
            "type": "string",
            "format": "uri",
            "description": "Where a human buys this product."
          },
          "offers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Offer"
            }
          }
        }
      },
      "ProductList": {
        "type": "object",
        "required": [
          "object",
          "count",
          "filters",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list"
          },
          "count": {
            "type": "integer"
          },
          "filters": {
            "type": "object",
            "description": "The filters that produced this list, echoed back.",
            "required": [
              "country",
              "type"
            ],
            "properties": {
              "country": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": "string"
              }
            }
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          }
        }
      }
    }
  }
}