Introduction
Open data API for fuel prices, currency rates and vanlife events. Free to use with attribution (CC BY 4.0).
Welcome to the **OpenVan.camp Public API** — open data for fuel prices, currency rates and vanlife events. The fuel endpoint returns the current country count in `meta.total_countries`.
## License
All data is available under **[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)**.
You are free to use it in your apps, bots, and articles — please cite **OpenVan.camp (openvan.camp)** as the source.
## Base URL
All endpoints are available at `https://openvan.camp`.
## Authentication
No authentication required. All endpoints are public.
## Rate Limits
Public endpoints are limited to **120 requests/minute per IP** (some helper endpoints 30–60/min).
Watch the `X-RateLimit-Limit` / `X-RateLimit-Remaining` response headers; exceeding the limit returns **HTTP 429**.
Data is cached — please don't poll more often than every 10 minutes.
## Attribution
Every response includes an `_attribution` object with license and attribution details:
```json
"_attribution": {
"data_source": "openvan.camp",
"license": "CC BY 4.0",
"attribution_url": "https://openvan.camp/",
"attribution_html": "Data: <a href=\"https://openvan.camp/\">OpenVan.camp</a> (CC BY 4.0)"
}
```
Pass `?source=yoursite.com` to identify your integration — no registration required. Your value is echoed back as `_attribution.your_source`:
```
GET /api/fuel/prices?source=myapp.com
```
<aside>Code examples are shown in the right panel. Switch language with the tabs at the top right.</aside>
Authenticating requests
This API is not authenticated.
Currency Rates
Get currency rates
Returns EUR-base exchange rates. By default the response is limited to ~165 ISO 4217 active fiat currencies that are realistic for travel/fuel-price use cases.
Source chain: national central banks (RUB/TRY/GEL/UAH/BYN/UZS) for precision, then Fawaz API / ExchangeRate-API / Frankfurter / ECB for the long tail.
Refresh: Redis-backed cache stored forever; refreshed by cron every 6 hours.
A separate hourly watchdog alerts in Telegram if cache > 25h old.
HTTP cache: Cache-Control: public, max-age=1800 (edge X-Accel-Expires: 1800).
Edge кеш 30 минут — соответствует фактической частоте обновления (раз в 6 часов).
Note: PHP-уровневый Cache-Control затирается nginx через fastcgi_hide_header +
cache-control-map.conf, поэтому фактический header в ответе всегда задаётся nginx.
Query parameters:
include=all— return the raw upstream set (~340 codes), including crypto, metals and historical/legacy codes (BTC, ETH, USDT, XAU, ATS, DEM, VEF, TRL, ZWL, etc.). Without this parameter, those are filtered out.
Market overrides: the meta.overrides field lists codes whose rate is a manual
market-rate override rather than an official feed. Currently only CUP (Cuba uses
a parallel market rate of ~120 CUP/USD; the official fixing of ~24 CUP/USD does not
reflect retail fuel pricing).
License: CC BY 4.0 — free to use with attribution to OpenVan.camp.
Example request:
curl --request GET \
--get "https://openvan.camp/api/currency/rates" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/currency/rates"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/currency/rates'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200, Success):
{
"success": true,
"rates": {
"EUR": 1,
"USD": 1.1641,
"GBP": 0.8729,
"RUB": 84.0742,
"TRY": 53.0915,
"GEL": 3.1146
},
"meta": {
"count": 165,
"base": "EUR",
"updated_at": "2026-05-19T12:24:28+00:00",
"refreshed_every_hours": 6,
"max_age_seconds": 90000,
"scope": "fiat",
"overrides": {
"CUP": {
"reason": "dual_exchange_rate",
"source": "market_rate_120_cup_per_usd",
"note": "Куба: официальный фиксинг ~24 CUP/USD расходится с реальным рынком ~120 CUP/USD."
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Endpoints
POST api/route-cost
Example request:
curl --request POST \
"https://openvan.camp/api/route-cost" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"waypoints\": [
\"Волгоград\",
\"Грузия\",
\"Анталия\"
],
\"tank\": 80,
\"cons\": 10,
\"fuel\": \"diesel\",
\"currency\": \"RUB\",
\"locale\": \"ru\"
}"
const url = new URL(
"https://openvan.camp/api/route-cost"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"waypoints": [
"Волгоград",
"Грузия",
"Анталия"
],
"tank": 80,
"cons": 10,
"fuel": "diesel",
"currency": "RUB",
"locale": "ru"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/route-cost'
payload = {
"waypoints": [
"Волгоград",
"Грузия",
"Анталия"
],
"tank": 80,
"cons": 10,
"fuel": "diesel",
"currency": "RUB",
"locale": "ru"
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
VanSky weather for all countries
Все страны — сводные данные для таблицы / карты. Ответ ~900 КБ, кешируется на 1 час.
Example request:
curl --request GET \
--get "https://openvan.camp/api/vansky/weather" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/vansky/weather"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/vansky/weather'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200, Success (truncated)):
{
"data": [
{
"country_code": "DE",
"country_slug": "germany",
"van_score": 90,
"temp_day": 24.1,
"temp_night": 14.3,
"solar_kwh": 2.8,
"sea_temp": 18.2,
"is_coastal": true,
"label": "ideal"
}
],
"count": 155,
"updated_at": "2026-07-15T02:00:00+03:00",
"source": "Open-Meteo (api.open-meteo.com)"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
VanSky weather for one country
Подробные данные одной страны (ISO 3166-1 alpha-2).
Example request:
curl --request GET \
--get "https://openvan.camp/api/vansky/weather/DE" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/vansky/weather/DE"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/vansky/weather/DE'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
Show headers
cache-control: max-age=3600, public
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 114
x-seo: Liked our optimization? We do technical SEO + GEO. Let's talk -> https://openvan.camp/en/about#contacts
access-control-allow-origin: *
{
"data": {
"code": "DE",
"marine": {
"sea_temp": 19.5,
"wave_dir": 295,
"swell_dir": 247,
"wave_height": 1.54,
"wave_period": 4.6,
"swell_height": 0,
"swell_period": 5.8,
"wind_wave_dir": 295,
"wind_wave_height": 1.54,
"wind_wave_period": 4.6
},
"region": "europe",
"weather": {
"sunset": "2026-07-26T18:58",
"uv_max": null,
"sunrise": "2026-07-26T03:41",
"humidity": 65,
"temp_day": 26.5,
"wind_dir": 240,
"wind_max": 15.7,
"dew_point": 12.6,
"precip_sum": 0.2,
"temp_night": 18.4,
"cloud_cover": 75,
"temp_current": 19.4,
"weather_code": 51,
"feels_like_day": 25.3,
"peak_sun_hours": 4.7,
"sunshine_hours": 11.4,
"wind_gusts_max": 37.4,
"precip_prob_max": 0,
"feels_like_night": 18,
"feels_like_current": 18.9,
"shortwave_radiation_sum": 16.89
},
"forecast": [
{
"date": "2026-07-26",
"temp_day": 26.5,
"solar_kwh": 2.1,
"van_score": 97,
"temp_night": 18.4,
"drive_score": 100,
"score_label": "ideal",
"sleep_score": 97,
"weather_code": 51
},
{
"date": "2026-07-27",
"temp_day": 25.5,
"solar_kwh": 2.1,
"van_score": 94,
"temp_night": 16.8,
"drive_score": 99,
"score_label": "ideal",
"sleep_score": 95,
"weather_code": 2
},
{
"date": "2026-07-28",
"temp_day": 28.4,
"solar_kwh": 2.8,
"van_score": 73,
"temp_night": 14,
"drive_score": 100,
"score_label": "comfortable",
"sleep_score": 88,
"weather_code": 0
},
{
"date": "2026-07-29",
"temp_day": 33.1,
"solar_kwh": 2.8,
"van_score": 53,
"temp_night": 16.8,
"drive_score": 100,
"score_label": "acceptable",
"sleep_score": 89,
"weather_code": 51
},
{
"date": "2026-07-30",
"temp_day": 36.2,
"solar_kwh": 2.7,
"van_score": 82,
"temp_night": 19.7,
"drive_score": 94,
"score_label": "ideal",
"sleep_score": 95,
"weather_code": 1
},
{
"date": "2026-07-31",
"temp_day": 33.7,
"solar_kwh": 2.2,
"van_score": 95,
"temp_night": 20.7,
"drive_score": 100,
"score_label": "ideal",
"sleep_score": 93,
"weather_code": 51
},
{
"date": "2026-08-01",
"temp_day": 28.8,
"solar_kwh": 2.3,
"van_score": 90,
"temp_night": 19,
"drive_score": 98,
"score_label": "ideal",
"sleep_score": 84,
"weather_code": 61
}
],
"sea_score": null,
"solar_kwh": 2.1,
"van_score": 97,
"confidence": "high",
"fetched_at": "2026-07-26T23:22:45.181608Z",
"is_coastal": true,
"week_score": 69,
"band_counts": {
"hard": 0,
"ideal": 33,
"extreme": 0,
"acceptable": 473,
"comfortable": 1269
},
"drive_score": 100,
"score_label": "ideal",
"sleep_score": 96,
"solar_score": 46,
"drive_window": null,
"awning_status": "caution",
"best_move_day": {
"date": "2026-07-26",
"temp_day": 26.5,
"solar_kwh": 2.1,
"van_score": 93,
"temp_night": 18.4,
"drive_score": 100,
"score_label": "ideal",
"sleep_score": 97,
"weather_code": 51
},
"sample_points": 1775,
"typical_score": 65,
"ideal_coverage": 0,
"sample_regions": 16,
"best_area_score": 72,
"recommendations": [
{
"key": "vansky.rec.awning_caution",
"type": "warning",
"params": {
"gusts": 37.4
}
},
{
"key": "vansky.rec.night_ideal",
"type": "success",
"params": {
"temp": 19.1
}
},
{
"key": "vansky.rec.day_comfortable",
"type": "success",
"params": {
"temp": 24.7
}
}
],
"condensation_risk": "low",
"aggregation_version": 2,
"comfortable_coverage": 81
},
"updated_at": "2026-07-27T02:23:24+03:00",
"source": "Open-Meteo (api.open-meteo.com)"
}
Example response (404, Not found):
{
"error": "Country not found or not supported"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Events
List events
Returns a paginated list of vanlife events (exhibitions, festivals, meetups, road trips). Filter by status, type, country, or search by name.
Example request:
curl --request GET \
--get "https://openvan.camp/api/events?locale=en&status=upcoming&type=festival&country=DE&search=Adventure+Northside&page=1&limit=30" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/events"
);
const params = {
"locale": "en",
"status": "upcoming",
"type": "festival",
"country": "DE",
"search": "Adventure Northside",
"page": "1",
"limit": "30",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/events'
params = {
'locale': 'en',
'status': 'upcoming',
'type': 'festival',
'country': 'DE',
'search': 'Adventure Northside',
'page': '1',
'limit': '30',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200, Success):
{
"events": [
{
"id": 960,
"slug": "adventure-northside-2026",
"event_name": "Adventure Northside 2026",
"event_type": "festival",
"event_type_emoji": "🎉",
"start_date": "2026-09-18",
"end_date": "2026-09-20",
"city": "Basthorst",
"country_code": "DE",
"country": {
"code": "de",
"slug": "germany",
"name": "Germany",
"flag_emoji": "🇩🇪"
},
"status": "upcoming",
"url": "https://openvan.camp/en/event/adventure-northside-2026"
}
],
"pagination": {
"total": 48,
"page": 1,
"limit": 30,
"pages": 2
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get event details
Returns full details for a single vanlife event by slug, including location, description, and social links.
Example request:
curl --request GET \
--get "https://openvan.camp/api/event/adventure-northside-2026?locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/event/adventure-northside-2026"
);
const params = {
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/event/adventure-northside-2026'
params = {
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200, Success):
{
"id": 960,
"slug": "adventure-northside-2026",
"event_name": "Adventure Northside 2026",
"event_type": "festival",
"event_type_emoji": "🎉",
"start_date": "2026-09-18",
"end_date": "2026-09-20",
"city": "Basthorst",
"country_code": "DE",
"description": "Northern Germany's Overland & Self-build expo and festival...",
"official_url": "https://adventurenorthside.de/",
"image_url": "https://...",
"status": "upcoming",
"url": "https://openvan.camp/en/event/adventure-northside-2026"
}
Example response (404, Not found):
{
"message": "No query results for model [App\\Models\\Event]."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get event articles
Returns source articles linked to this event.
When locale is provided, attempts to return only articles in that language.
If no articles match the requested locale, all articles are returned as a fallback
(they may be in the original source language, e.g. Japanese or German).
The language field on each article indicates the actual language of the source.
Example request:
curl --request GET \
--get "https://openvan.camp/api/event/adventure-northside-2026/articles?locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/event/adventure-northside-2026/articles"
);
const params = {
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/event/adventure-northside-2026/articles'
params = {
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200, Success):
[
{
"id": 1001,
"title": "Adventure Northside 2026 opens its doors",
"image_url": "https://...",
"published_at": "2026-03-11T10:00:00+00:00",
"source_name": "CamperVan Magazine",
"original_url": "https://...",
"language": "en"
}
]
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Fuel Prices
Get fuel prices
Returns current retail fuel prices for all supported countries.
Price keys follow FuelGradeCatalog::ORDER plus the legacy premium alias.
Data is updated weekly from 45+ official government sources and independent aggregators.
Prices are weighted averages from multiple sources per country. The sources array
lists all contributing data providers (sorted by trust weight, highest first).
License: CC BY 4.0 — free to use with attribution to OpenVan.camp.
Example request:
curl --request GET \
--get "https://openvan.camp/api/fuel/prices" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/fuel/prices"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/fuel/prices'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200, Success):
{
"success": true,
"data": {
"DE": {
"country_code": "DE",
"country_name": "Germany",
"region": "europe",
"currency": "EUR",
"local_currency": "EUR",
"unit": "liter",
"prices": {
"gasoline_regular": null,
"gasoline": 2.1313,
"gasoline_premium": null,
"gasoline_super": null,
"diesel_regular": null,
"diesel": 2.2845,
"diesel_premium": null,
"lpg": 1.113,
"cng": null,
"e85": null,
"kerosene": null,
"premium": null
},
"price_changes": {
"gasoline_regular": null,
"gasoline": -0.02,
"gasoline_premium": null,
"gasoline_super": null,
"diesel_regular": null,
"diesel": 0.01,
"diesel_premium": null,
"lpg": 0,
"cng": null,
"e85": null,
"kerosene": null,
"premium": null
},
"fetched_at": "2026-06-29T11:12:55+00:00",
"sources": [
"Fuelo.net",
"EU Weekly Oil Bulletin",
"Cargopedia.net"
],
"sources_count": 3,
"is_excluded": false
}
},
"meta": {
"total_countries": 135,
"updated_at": "2026-06-29 11:12:55",
"cache_ttl_hours": 6
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Stories
Vanlife news stories — clustered and translated into 7 languages. Each story aggregates multiple source articles from different publishers.
List stories
Returns a paginated list of vanlife news stories in the requested language.
Titles and summaries are translated; sources are original-language articles.
Example request:
curl --request GET \
--get "https://openvan.camp/api/stories?locale=en&category=festival&country=DE&search=vanlife+festival&page=1&limit=20" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/stories"
);
const params = {
"locale": "en",
"category": "festival",
"country": "DE",
"search": "vanlife festival",
"page": "1",
"limit": "20",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/stories'
params = {
'locale': 'en',
'category': 'festival',
'country': 'DE',
'search': 'vanlife festival',
'page': '1',
'limit': '20',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200, Success):
{
"stories": [
{
"slug": "adac-camper-des-jahres-der-wettbewerb-fuer",
"title": "ADAC launches 'Camper of the Year' competition for motorhome drivers",
"summary": "ADAC is holding the national 'Camper des Jahres 2026' competition for motorhome owners...",
"image_url": "https://...",
"category": {
"slug": "festival",
"name": "Festivals"
},
"countries": [
{
"code": "de",
"name": "Germany",
"flag_emoji": "🇩🇪"
}
],
"first_published_at": "2026-01-22T11:00:00+03:00",
"articles_count": 5,
"url": "https://openvan.camp/en/news/festival/adac-camper-des-jahres-der-wettbewerb-fuer"
}
],
"pagination": {
"total": 120,
"page": 1,
"limit": 20,
"pages": 6
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get story details
Returns full details for a single news story including all source articles.
The sources array contains original publisher articles with direct links.
Example request:
curl --request GET \
--get "https://openvan.camp/api/story/adac-camper-des-jahres-der-wettbewerb-fuer?locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/story/adac-camper-des-jahres-der-wettbewerb-fuer"
);
const params = {
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/story/adac-camper-des-jahres-der-wettbewerb-fuer'
params = {
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200, Success):
{
"slug": "adac-camper-des-jahres-der-wettbewerb-fuer",
"title": "ADAC launches 'Camper of the Year' competition for motorhome drivers",
"summary": "ADAC is holding the national 'Camper des Jahres 2026' competition for motorhome owners...",
"image_url": "https://...",
"category": {
"slug": "festival",
"name": "Festivals"
},
"countries": [
{
"code": "de",
"name": "Germany",
"flag_emoji": "🇩🇪"
}
],
"first_published_at": "2026-01-22T11:00:00+03:00",
"last_updated_at": "2026-07-04T04:16:47+03:00",
"articles_count": 5,
"url": "https://openvan.camp/en/news/festival/adac-camper-des-jahres-der-wettbewerb-fuer",
"sources": [
{
"title": "Nürburgring: Bundesweiter Wettbewerb «Camper des Jahres»",
"original_url": "https://www.blick-aktuell.de/Berichte/...",
"source_name": "BLICK aktuell",
"published_at": "2026-04-30T09:24:20+03:00",
"language": "de",
"image_url": "https://..."
}
]
}
Example response (404, Not found):
{
"error": "Story not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Semantic news search (RAG)
Semantic search over news via the same engine as the /{locale}/search page (SemanticSearchService: Jina embeddings + pgvector). Returns matching news stories ranked by similarity. Story summary shape matches /stories.
Example request:
curl --request GET \
--get "https://openvan.camp/api/news/search?q=ford+camper+2026&locale=ru&limit=8" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/news/search"
);
const params = {
"q": "ford camper 2026",
"locale": "ru",
"limit": "8",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/news/search'
params = {
'q': 'ford camper 2026',
'locale': 'ru',
'limit': '8',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 115
x-seo: Liked our optimization? We do technical SEO + GEO. Let's talk -> https://openvan.camp/en/about#contacts
access-control-allow-origin: *
{
"results": [
{
"slug": "panoramic-ford-f-camper-truck-costs-fraction",
"title": "Supertramp представила кемпер Paragon на базе Ford F-350",
"summary": "Компания Supertramp анонсировала новую модель кемпера Paragon на шасси Ford F-350. Базовая цена составляет 199 000 долларов, что значительно ниже конкурентов, таких как Earthroamer (от 825 000 долларов) и Winnebago Arka (330 000 долларов). Модель оснащена композитным модулем с вакуумной инфузией, спальными местами на четыре человека и опциональной палаткой на крыше для шести человек. Стандартная комплектация включает литий-ионную батарею на 270 А·ч, инвертор на 3000 Вт и солнечную систему на 660 Вт.",
"image_url": "https://assets.newatlas.com/dims4/default/00d5475/2147483647/strip/true/crop/3276x1720+0+514/resize/1200x630!/quality/85/?url=https%3A%2F%2Fnewatlas-brightspot.s3.amazonaws.com%2Fb8%2F31%2F5eb910d0450196ac9000fdc083c3%2F699761506-18360938500231222-6098785867930289037-n.jpg&na.image_optimisation=0",
"category": {
"slug": "industry",
"name": "Индустрия"
},
"countries": [],
"first_published_at": "2026-06-05T04:35:09+03:00",
"articles_count": 1,
"url": "https://openvan.camp/ru/news/industry/panoramic-ford-f-camper-truck-costs-fraction",
"score": 0.562
},
{
"slug": "ram-promaster-add-new-prebuilt-camper-van-thats",
"title": "Ram ProMaster 2027 получил пакет Vanlife для автодомов",
"summary": "Компания Ram представила пакет Vanlife для фургона ProMaster 2027 года, упрощающий переоборудование в кемпер. Цена — от $60 320.",
"image_url": "https://hips.hearstapps.com/hmg-prod/images/94800d47-567d-49f8-8bb7-2996f6c8af4c.jpg?crop=1.00xw:0.753xh;0,0.171xh&resize=1200:*",
"category": {
"slug": "industry",
"name": "Индустрия"
},
"countries": [
{
"code": "ru",
"name": "Россия",
"flag_emoji": "🇷🇺"
},
{
"code": "us",
"name": "США",
"flag_emoji": "🇺🇸"
}
],
"first_published_at": "2026-06-02T20:53:00+03:00",
"articles_count": 6,
"url": "https://openvan.camp/ru/news/industry/ram-promaster-add-new-prebuilt-camper-van-thats",
"score": 0.533
},
{
"slug": "ford-convierte-la-transit-custom-en-la-mejor",
"title": "Carado T457 на базе Ford Transit признан лучшим автодомом 2026 года по версии Red Dot Awards",
"summary": "Модель Carado T457, полуинтегрированный автодом на платформе Ford Transit, получил награду Red Dot Design Awards 2026. Длина — 7,4 м, ширина — 2,32 м, высота — 2,93 м. Цена от 71 690 евро.",
"image_url": "https://album.mediaset.es/eimg/2026/01/03/ford-logo-16-9-aspect-ratio-default-1027707_da39.jpg",
"category": {
"slug": "industry",
"name": "Индустрия"
},
"countries": [
{
"code": "de",
"name": "Германия",
"flag_emoji": "🇩🇪"
}
],
"first_published_at": "2026-02-18T10:20:01+03:00",
"articles_count": 2,
"url": "https://openvan.camp/ru/news/industry/ford-convierte-la-transit-custom-en-la-mejor",
"score": 0.529
},
{
"slug": "dfsk-tebar-promo-besar-di-prj-2026-mulai",
"title": "DFSK на PRJ 2026: кемпер Delima x Explora и анонс PHEV",
"summary": "На Jakarta Fair Kemayoran 2026 компания DFSK представила Camper Van Delima x Explora, электромобили и анонсировала технологию Plug-in Hybrid Electric Vehicle (PHEV). Действуют программы trade-in и бесплатного обслуживания.",
"image_url": "https://dapurletter.id/wp-content/uploads/2026/06/PRJ-DFSK-2.jpeg",
"category": {
"slug": "expo",
"name": "Выставки"
},
"countries": [
{
"code": "id",
"name": "Индонезия",
"flag_emoji": "🇮🇩"
}
],
"first_published_at": "2026-06-11T07:13:45+03:00",
"articles_count": 2,
"url": "https://openvan.camp/ru/news/expo/dfsk-tebar-promo-besar-di-prj-2026-mulai",
"score": 0.524
},
{
"slug": "chrysler-pacifica-awd-minit-campervan-brings-van",
"title": "2026 Chrysler Pacifica AWD Mini-T Campervan",
"summary": "Компания DLM-Distribution представила кемпер на базе Chrysler Pacifica с полным приводом. Модель помещается в стандартный гараж и оснащена спальным местом, холодильником, раковиной и солнечной батареей. Цена — $66 900.",
"image_url": "https://moparinsiders.com/wp-content/uploads/2026/05/2026-Chrysler-Pacifica-AWD-Mini-T-Campervan.-DLM-Distribution.-1.jpeg",
"category": {
"slug": "industry",
"name": "Индустрия"
},
"countries": [
{
"code": "us",
"name": "США",
"flag_emoji": "🇺🇸"
}
],
"first_published_at": "2026-05-17T19:00:21+03:00",
"articles_count": 1,
"url": "https://openvan.camp/ru/news/industry/chrysler-pacifica-awd-minit-campervan-brings-van",
"score": 0.524
},
{
"slug": "volkswagen-id-buzz-coming-back-america-van-camp",
"title": "Volkswagen возвращает кемпер ID. Buzz в США с новой комплектацией Tourer",
"summary": "Volkswagen представил комплектацию Tourer для электрического микроавтобуса ID. Buzz 2027 года, предназначенную для кемпинга. В оснащение входят складная кровать, шторки, режим Overnight и уличная мебель. Продажи начнутся в 2027 году, цены пока не объявлены.",
"image_url": "https://images-stag.jazelc.com/uploads/theautopian-m2en/ID_Camper_TS2.png",
"category": {
"slug": "industry",
"name": "Индустрия"
},
"countries": [
{
"code": "us",
"name": "США",
"flag_emoji": "🇺🇸"
}
],
"first_published_at": "2026-05-14T16:49:00+03:00",
"articles_count": 7,
"url": "https://openvan.camp/ru/news/industry/volkswagen-id-buzz-coming-back-america-van-camp",
"score": 0.523
}
],
"timing_ms": 60,
"_attribution": {
"data_source": "openvan.camp",
"license": "CC BY 4.0",
"attribution_url": "https://openvan.camp/",
"attribution_html": "Data: <a href=\"https://openvan.camp/\">OpenVan.camp</a> (CC BY 4.0)"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Daily news digest (raw content)
Returns the structured evening news digest for the requested locale, assembled once per evening by the site. Consumers (site social channels,
Example request:
curl --request GET \
--get "https://openvan.camp/api/news/digest?locale=ru" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/news/digest"
);
const params = {
"locale": "ru",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/news/digest'
params = {
'locale': 'ru',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200, Success):
{
"locale": "ru",
"date": "2026-07-25",
"intro": "Дороги сегодня подкинули всё…",
"groups": [
{
"key": "incident",
"title": "⚠️ Происшествия",
"items": [
{
"id": 123,
"flag": "🇩🇪",
"hook": "Заголовок-зацепка",
"detail": "Деталь сюжета",
"url": "https://openvan.camp/ru/news/incident/slug"
}
]
}
]
}
Example response (204, No digest yet):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
VanBasket Food Price Index
VanBasket Index shows how expensive food is in a country relative to the world average (World = 100). Based on World Bank ICP 2021 data, adjusted with IMF CPI.
Get all countries with VanBasket index.
Returns food price index for 90+ countries. World average = 100. Above 100 = more expensive, below 100 = cheaper.
Example request:
curl --request GET \
--get "https://openvan.camp/api/vanbasket/countries" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/vanbasket/countries"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/vanbasket/countries'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"success": true,
"data": {
"DE": {
"country_code": "DE",
"country_name": "Germany",
"vanbasket_index": 124.9,
"pct_vs_world": 24.9
}
},
"meta": {
"total_countries": 92
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Compare food prices between two countries.
Example request:
curl --request GET \
--get "https://openvan.camp/api/vanbasket/compare?from=DE&to=TR" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/vanbasket/compare"
);
const params = {
"from": "DE",
"to": "TR",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/vanbasket/compare'
params = {
'from': 'DE',
'to': 'TR',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": {
"from": {
"country_code": "DE",
"country_name": "Germany",
"vanbasket_index": 124.9
},
"to": {
"country_code": "TR",
"country_name": "Turkey",
"vanbasket_index": 88
},
"diff_percent": -29.5,
"budget_100": 70,
"cheaper": true
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get VanBasket country details
Detailed data for a single country including historical snapshots.
Example request:
curl --request GET \
--get "https://openvan.camp/api/vanbasket/countries/DE" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/vanbasket/countries/DE"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/vanbasket/countries/DE'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"success": true,
"data": {
"country": {
"country_code": "DE",
"vanbasket_index": 124.9
},
"snapshots": [
{
"snapshot_date": "2021-01-01",
"vanbasket_index": 124.9
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Visa & Border Rules
How long you may stay and how the days are counted — for all 199 × 199 passport/destination pairs, plus temporary vehicle import rules.
Every answer carries confidence and source_url, and says which layer it
came from: curated (hand-verified), window (counting window applied on
top of the dataset), zone (Schengen-style shared counter) or dataset.
Treat confidence: low as "the number of days is right, the way they are
counted is an assumption" — verify at the border.
Check a passport against a destination.
Returns the stay rule (days, counting window, whether a visa run resets the counter), the entry mode, and the vehicle import rule for the same destination.
If the country belongs to a shared-counter zone, the stay rule is the
zone's — entering Spain spends Schengen days, not "Spanish" ones — and
counted_against names the place the counter belongs to.
Example request:
curl --request GET \
--get "https://openvan.camp/api/visa/check?passport=RU&destination=TR&weight=le35&plate=third&locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/visa/check"
);
const params = {
"passport": "RU",
"destination": "TR",
"weight": "le35",
"plate": "third",
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/visa/check'
params = {
'passport': 'RU',
'destination': 'TR',
'weight': 'le35',
'plate': 'third',
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": {
"passport": {
"code": "RU",
"name": "Russia"
},
"destination": {
"code": "TR",
"name": "Turkey",
"kind": "country",
"zone": null
},
"entry_mode": "visa_free",
"stay": {
"visa_type": "visa_free",
"max_continuous": 90,
"max_total": 90,
"window": "rolling",
"window_days": 180,
"visa_run": false,
"confidence": "high",
"source_url": "https://www.mfa.gov.tr/visa-information-for-foreigners.en.mfa",
"note": "Безвиз 90/180.",
"layer": "curated"
},
"vehicle": {
"max_days": null,
"basis": "tied_to_person",
"green_card": "required",
"confidence": "high"
}
}
}
Example response (422):
{
"success": false,
"error": "Parameters \"passport\" and \"destination\" are required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Passport ranking by destinations reachable without a prior visa.
Score = visa free + visa on arrival + eTA. The breakdown is returned alongside so the number can be checked rather than taken on faith. Ties share a rank.
Example request:
curl --request GET \
--get "https://openvan.camp/api/visa/rank?limit=10&locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/visa/rank"
);
const params = {
"limit": "10",
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/visa/rank'
params = {
'limit': '10',
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": [
{
"rank": 1,
"passport": "SG",
"name": "Singapore",
"score": 168,
"visa_free": 130,
"visa_on_arrival": 30,
"eta": 8,
"e_visa": 20,
"visa_required": 10
}
],
"meta": {
"total": 199,
"scoring": "visa_free + visa_on_arrival + eta"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Change log of visa and vehicle rules.
One row per changed field, written both by imports and by editors. Commercial passport APIs charge for rule history; this one is open.
Example request:
curl --request GET \
--get "https://openvan.camp/api/visa/history?subject_type=requirement&subject_key=RU%3ETR&since=2026-01-01&limit=50" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/visa/history"
);
const params = {
"subject_type": "requirement",
"subject_key": "RU>TR",
"since": "2026-01-01",
"limit": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/visa/history'
params = {
'subject_type': 'requirement',
'subject_key': 'RU>TR',
'since': '2026-01-01',
'limit': '50',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": [
{
"subject_type": "requirement",
"subject_key": "RU>TR",
"field": "days",
"old_value": "60",
"new_value": "90",
"source": "import",
"changed_at": "2026-07-27T10:00:00+00:00"
}
],
"meta": {
"total": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
All destinations for one passport.
199 rows resolved through the full chain, sorted by destination name.
Counts by entry mode are in meta.
Example request:
curl --request GET \
--get "https://openvan.camp/api/visa/passport/DE?mode=visa_free&locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/visa/passport/DE"
);
const params = {
"mode": "visa_free",
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/visa/passport/DE'
params = {
'mode': 'visa_free',
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": [
{
"destination": "TR",
"name": "Turkey",
"slug": "turkey",
"mode": "visa_free",
"days": 90,
"stay": {
"window": "rolling",
"window_days": 180,
"confidence": "medium",
"layer": "window"
}
}
],
"meta": {
"passport": "DE",
"total": 199,
"by_mode": {
"visa_free": 130
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Compact payload for colouring a world map by one passport.
Same data as /passport/{code} stripped to what a choropleth needs:
m — entry mode, d — visa-free days if known.
Example request:
curl --request GET \
--get "https://openvan.camp/api/visa/map/US" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/visa/map/US"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/visa/map/US'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"success": true,
"data": {
"TR": {
"m": "visa_free",
"d": 90
},
"RU": {
"m": "visa_required",
"d": null
}
},
"meta": {
"passport": "US",
"legend": [
"visa_free",
"voa",
"eta",
"e_visa",
"visa_required",
"no_admission",
"self"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Temporary vehicle import rules for a country.
Only rules we can stand behind are returned: an official source, or two independent research runs agreeing, or manual verification. A country with nothing trustworthy returns an empty list rather than a guess — the cost of being wrong here is a vehicle held at the border.
Example request:
curl --request GET \
--get "https://openvan.camp/api/visa/vehicle/georgia?locale=en" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://openvan.camp/api/visa/vehicle/georgia"
);
const params = {
"locale": "en",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());import requests
import json
url = 'https://openvan.camp/api/visa/vehicle/georgia'
params = {
'locale': 'en',
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"success": true,
"data": [
{
"weight_class": "le35",
"plate_group": null,
"max_days": 90,
"basis": "per_entry",
"window_days": null,
"carnet_required": false,
"extension_possible": null,
"green_card": "border_insurance",
"confidence": "high",
"source_url": "https://georgiacb.com/en/articles/customs-procedure-temporary-importation-admission-im-53",
"curated": true
}
],
"meta": {
"place": "GE",
"name": "Georgia",
"green_card_by_plate": {
"eu": "plate_ok"
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.