{
  "openapi": "3.0.3",
  "info": {
    "title": "OpenVan.camp AI Public API",
    "description": "Curated read-only OpenAPI schema for AI agents. Use this API to retrieve current vanlife news stories, source articles, events, fuel prices, currency rates, VanBasket food price index data, and VanSky weather suitability data. No authentication is required. Data is CC BY 4.0 and should be attributed to OpenVan.camp. Rate limit: 120 requests/minute per IP (see X-RateLimit-Limit and X-RateLimit-Remaining response headers). Pass `?source=<integration-domain>` for attribution tracking.",
    "version": "1.1.0",
    "termsOfService": "https://openvan.camp/en/terms",
    "contact": {
      "name": "OpenVan.camp",
      "email": "hello@openvan.camp",
      "url": "https://openvan.camp/en/developers"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    },
    "x-rateLimit": {
      "requests": 120,
      "window": "1m",
      "scope": "per-ip",
      "response-headers": {
        "X-RateLimit-Limit": "Allowed requests per window",
        "X-RateLimit-Remaining": "Remaining requests in current window"
      }
    },
    "x-attribution": {
      "required": false,
      "recommended": true,
      "parameter": "source",
      "format": "domain-or-app-name",
      "description": "Include ?source=<your-domain-or-app-name> in every request. Helps OpenVan.camp track AI adoption and credit integrations in public reports.",
      "citation": "OpenVan.camp (retrieved YYYY-MM-DD). https://openvan.camp"
    },
    "x-changelog": "https://openvan.camp/api/changelog.json"
  },
  "servers": [
    {
      "url": "https://openvan.camp",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Stories",
      "description": "Aggregated vanlife news stories translated into multiple languages."
    },
    {
      "name": "Events",
      "description": "Vanlife events, exhibitions, festivals, meetups, road trips and related source articles."
    },
    {
      "name": "Fuel Prices",
      "description": "Current retail fuel prices by country."
    },
    {
      "name": "Currency Rates",
      "description": "Exchange rates relative to EUR."
    },
    {
      "name": "VanBasket",
      "description": "Food price index by country, relative to world average."
    },
    {
      "name": "VanSky",
      "description": "Weather suitability data for van travel."
    }
  ],
  "paths": {
    "/api/stories": {
      "get": {
        "tags": ["Stories"],
        "operationId": "listStories",
        "summary": "List vanlife news stories",
        "description": "Returns a paginated list of active vanlife news stories. Titles and summaries are localized. Add source=<integration-name-or-domain> for attribution tracking.",
        "parameters": [
          {"$ref": "#/components/parameters/Locale"},
          {"$ref": "#/components/parameters/Source"},
          {
            "name": "category",
            "in": "query",
            "description": "Category slug, for example camping, travel, gear, festival or industry.",
            "schema": {"type": "string"}
          },
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {"type": "string", "minLength": 2, "maxLength": 2},
            "example": "DE"
          },
          {
            "name": "search",
            "in": "query",
            "description": "Full-text search by story title.",
            "schema": {"type": "string"},
            "example": "vanlife festival"
          },
          {"$ref": "#/components/parameters/Page"},
          {"$ref": "#/components/parameters/StoryLimit"}
        ],
        "responses": {
          "200": {
            "description": "Paginated story list",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/StoriesResponse"}
              }
            }
          }
        }
      }
    },
    "/api/story/{slug}": {
      "get": {
        "tags": ["Stories"],
        "operationId": "getStory",
        "summary": "Get one vanlife news story",
        "description": "Returns one story with localized title and summary, plus original source articles.",
        "parameters": [
          {"$ref": "#/components/parameters/StorySlug"},
          {"$ref": "#/components/parameters/Locale"},
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "Story details",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/Story"}
              }
            }
          },
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/api/events": {
      "get": {
        "tags": ["Events"],
        "operationId": "listEvents",
        "summary": "List vanlife events",
        "description": "Returns a paginated list of vanlife events. Useful for exhibitions, festivals, meetups, rallies, expos and road trips.",
        "parameters": [
          {"$ref": "#/components/parameters/Locale"},
          {"$ref": "#/components/parameters/Source"},
          {
            "name": "status",
            "in": "query",
            "description": "Event time status.",
            "schema": {"type": "string", "enum": ["upcoming", "ongoing", "past", "all"], "default": "upcoming"}
          },
          {
            "name": "type",
            "in": "query",
            "description": "Event type.",
            "schema": {"type": "string", "enum": ["expo", "festival", "forum", "meetup", "roadtrip"]}
          },
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {"type": "string", "minLength": 2, "maxLength": 2},
            "example": "DE"
          },
          {
            "name": "search",
            "in": "query",
            "description": "Search by event name.",
            "schema": {"type": "string"},
            "example": "Caravan Salon"
          },
          {"$ref": "#/components/parameters/Page"},
          {"$ref": "#/components/parameters/EventLimit"}
        ],
        "responses": {
          "200": {
            "description": "Paginated event list",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/EventsResponse"}
              }
            }
          }
        }
      }
    },
    "/api/event/{slug}": {
      "get": {
        "tags": ["Events"],
        "operationId": "getEvent",
        "summary": "Get one vanlife event",
        "description": "Returns event details including dates, location, description, official URL, social links and localized fields.",
        "parameters": [
          {"$ref": "#/components/parameters/EventSlug"},
          {"$ref": "#/components/parameters/Locale"},
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "Event details",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/Event"}
              }
            }
          },
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/api/event/{slug}/articles": {
      "get": {
        "tags": ["Events"],
        "operationId": "getEventArticles",
        "summary": "Get source articles for one event",
        "description": "Returns source articles linked to the event. If no articles match the requested locale, all published source articles may be returned.",
        "parameters": [
          {"$ref": "#/components/parameters/EventSlug"},
          {"$ref": "#/components/parameters/Locale"},
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "Event source articles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {"$ref": "#/components/schemas/SourceArticle"}
                }
              }
            }
          },
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/api/fuel/prices": {
      "get": {
        "tags": ["Fuel Prices"],
        "operationId": "getFuelPrices",
        "summary": "Get current fuel prices by country",
        "description": "Returns gasoline, diesel, LPG and other fuel prices by country. Prices are cached and include source attribution.",
        "parameters": [
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "Fuel prices by country code",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/FuelPricesResponse"}
              }
            }
          }
        }
      }
    },
    "/api/currency/rates": {
      "get": {
        "tags": ["Currency Rates"],
        "operationId": "getCurrencyRates",
        "summary": "Get exchange rates",
        "description": "Returns exchange rates for 150+ currencies relative to EUR.",
        "parameters": [
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "Currency rates",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/CurrencyRatesResponse"}
              }
            }
          }
        }
      }
    },
    "/api/vanbasket/countries": {
      "get": {
        "tags": ["VanBasket"],
        "operationId": "listVanBasketCountries",
        "summary": "List VanBasket food price index by country",
        "description": "Returns food price index for countries relative to world average. World average is 100.",
        "parameters": [
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "VanBasket countries",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VanBasketCountriesResponse"}
              }
            }
          }
        }
      }
    },
    "/api/vanbasket/compare": {
      "get": {
        "tags": ["VanBasket"],
        "operationId": "compareVanBasketCountries",
        "summary": "Compare food prices between two countries",
        "description": "Compares VanBasket food price index values between two countries.",
        "parameters": [
          {"$ref": "#/components/parameters/Source"},
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "Home country ISO 3166-1 alpha-2 code.",
            "schema": {"type": "string", "minLength": 2, "maxLength": 2},
            "example": "DE"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "Destination country ISO 3166-1 alpha-2 code.",
            "schema": {"type": "string", "minLength": 2, "maxLength": 2},
            "example": "TR"
          }
        ],
        "responses": {
          "200": {
            "description": "VanBasket comparison",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VanBasketCompareResponse"}
              }
            }
          },
          "404": {"$ref": "#/components/responses/NotFound"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    },
    "/api/vanbasket/countries/{code}": {
      "get": {
        "tags": ["VanBasket"],
        "operationId": "getVanBasketCountry",
        "summary": "Get VanBasket country details",
        "description": "Returns VanBasket data and historical snapshots for one country.",
        "parameters": [
          {"$ref": "#/components/parameters/CountryCode"},
          {"$ref": "#/components/parameters/Source"}
        ],
        "responses": {
          "200": {
            "description": "VanBasket country details",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VanBasketCountryResponse"}
              }
            }
          },
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/api/vansky/weather": {
      "get": {
        "tags": ["VanSky"],
        "operationId": "listVanSkyWeather",
        "summary": "List VanSky weather suitability by country",
        "description": "Returns country-level weather suitability data for van travel.",
        "responses": {
          "200": {
            "description": "VanSky countries",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VanSkyCountriesResponse"}
              }
            }
          }
        }
      }
    },
    "/api/vansky/weather/{code}": {
      "get": {
        "tags": ["VanSky"],
        "operationId": "getVanSkyCountryWeather",
        "summary": "Get VanSky weather suitability for one country",
        "description": "Returns detailed VanSky weather suitability data for one ISO 3166-1 alpha-2 country code.",
        "parameters": [
          {"$ref": "#/components/parameters/CountryCode"}
        ],
        "responses": {
          "200": {
            "description": "VanSky country details",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VanSkyCountryResponse"}
              }
            }
          },
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Locale": {
        "name": "locale",
        "in": "query",
        "description": "Language for localized fields. Defaults to en.",
        "schema": {"type": "string", "enum": ["en", "ru", "de", "fr", "es", "pt", "tr"], "default": "en"}
      },
      "Source": {
        "name": "source",
        "in": "query",
        "description": "Optional integration name or domain for attribution tracking.",
        "schema": {"type": "string", "maxLength": 100},
        "example": "myapp.com"
      },
      "Page": {
        "name": "page",
        "in": "query",
        "description": "Page number.",
        "schema": {"type": "integer", "minimum": 1, "default": 1}
      },
      "StoryLimit": {
        "name": "limit",
        "in": "query",
        "description": "Stories per page. Maximum 50.",
        "schema": {"type": "integer", "minimum": 1, "maximum": 50, "default": 20}
      },
      "EventLimit": {
        "name": "limit",
        "in": "query",
        "description": "Events per page. Maximum 100.",
        "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 30}
      },
      "StorySlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "Story slug.",
        "schema": {"type": "string"},
        "example": "vanlife-festival-germany-2026"
      },
      "EventSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "Event slug.",
        "schema": {"type": "string"},
        "example": "caravan-salon-duesseldorf-2026"
      },
      "CountryCode": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "ISO 3166-1 alpha-2 country code.",
        "schema": {"type": "string", "minLength": 2, "maxLength": 2},
        "example": "DE"
      }
    },
    "responses": {
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/ErrorResponse"}
          }
        }
      },
      "ValidationError": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/ErrorResponse"}
          }
        }
      }
    },
    "schemas": {
      "Attribution": {
        "type": "object",
        "properties": {
          "data_source": {"type": "string", "example": "openvan.camp"},
          "license": {"type": "string", "example": "CC BY 4.0"},
          "attribution_url": {"type": "string", "format": "uri", "example": "https://openvan.camp/"},
          "attribution_html": {"type": "string"},
          "your_source": {"type": "string"}
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "total": {"type": "integer"},
          "page": {"type": "integer"},
          "limit": {"type": "integer"},
          "pages": {"type": "integer"}
        }
      },
      "Country": {
        "type": "object",
        "properties": {
          "code": {"type": "string", "example": "de"},
          "slug": {"type": "string", "example": "germany"},
          "name": {"type": "string", "example": "Germany"},
          "flag_emoji": {"type": "string"}
        }
      },
      "Category": {
        "type": "object",
        "properties": {
          "slug": {"type": "string", "example": "festival"},
          "name": {"type": "string", "example": "Festivals"}
        }
      },
      "SourceArticle": {
        "type": "object",
        "properties": {
          "id": {"type": "integer"},
          "title": {"type": "string"},
          "summary": {"type": "string", "nullable": true},
          "original_url": {"type": "string", "format": "uri"},
          "source_name": {"type": "string"},
          "published_at": {"type": "string", "format": "date-time", "nullable": true},
          "language": {"type": "string"},
          "image_url": {"type": "string", "format": "uri", "nullable": true}
        }
      },
      "StorySummary": {
        "type": "object",
        "properties": {
          "slug": {"type": "string"},
          "title": {"type": "string"},
          "summary": {"type": "string", "nullable": true},
          "image_url": {"type": "string", "format": "uri", "nullable": true},
          "category": {"$ref": "#/components/schemas/Category"},
          "countries": {"type": "array", "items": {"$ref": "#/components/schemas/Country"}},
          "first_published_at": {"type": "string", "format": "date-time", "nullable": true},
          "articles_count": {"type": "integer"},
          "url": {"type": "string", "format": "uri"}
        }
      },
      "Story": {
        "allOf": [
          {"$ref": "#/components/schemas/StorySummary"},
          {
            "type": "object",
            "properties": {
              "last_updated_at": {"type": "string", "format": "date-time", "nullable": true},
              "sources": {"type": "array", "items": {"$ref": "#/components/schemas/SourceArticle"}},
              "_attribution": {"$ref": "#/components/schemas/Attribution"}
            }
          }
        ]
      },
      "StoriesResponse": {
        "type": "object",
        "properties": {
          "stories": {"type": "array", "items": {"$ref": "#/components/schemas/StorySummary"}},
          "pagination": {"$ref": "#/components/schemas/Pagination"},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {"type": "integer"},
          "slug": {"type": "string"},
          "event_name": {"type": "string"},
          "event_type": {"type": "string"},
          "event_type_label": {"type": "string"},
          "start_date": {"type": "string", "format": "date", "nullable": true},
          "end_date": {"type": "string", "format": "date", "nullable": true},
          "city": {"type": "string", "nullable": true},
          "country_code": {"type": "string", "nullable": true},
          "country": {"$ref": "#/components/schemas/Country"},
          "venue_name": {"type": "string", "nullable": true},
          "venue_address": {"type": "string", "nullable": true},
          "latitude": {"type": "number", "nullable": true},
          "longitude": {"type": "number", "nullable": true},
          "description": {"type": "string", "nullable": true},
          "summary": {"type": "string", "nullable": true},
          "official_url": {"type": "string", "format": "uri", "nullable": true},
          "image_url": {"type": "string", "format": "uri", "nullable": true},
          "social_links": {"type": "object", "additionalProperties": true},
          "organizer": {"type": "string", "nullable": true},
          "admission_price": {"type": "string", "nullable": true},
          "status": {"type": "string"},
          "quality_score": {"type": "number", "nullable": true},
          "articles_count": {"type": "integer", "nullable": true},
          "url": {"type": "string", "format": "uri"},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "EventsResponse": {
        "type": "object",
        "properties": {
          "events": {"type": "array", "items": {"$ref": "#/components/schemas/Event"}},
          "pagination": {"$ref": "#/components/schemas/Pagination"},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "FuelPricesResponse": {
        "type": "object",
        "properties": {
          "success": {"type": "boolean"},
          "data": {
            "type": "object",
            "additionalProperties": {"$ref": "#/components/schemas/FuelCountry"}
          },
          "meta": {"type": "object", "additionalProperties": true},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "FuelCountry": {
        "type": "object",
        "properties": {
          "country_code": {"type": "string", "example": "DE"},
          "country_name": {"type": "string", "example": "Germany"},
          "region": {"type": "string", "nullable": true},
          "currency": {"type": "string", "example": "EUR"},
          "local_currency": {"type": "string", "nullable": true},
          "unit": {"type": "string", "example": "liter"},
          "prices": {"type": "object", "additionalProperties": {"type": "number", "nullable": true}},
          "price_changes": {"type": "object", "additionalProperties": {"type": "number", "nullable": true}},
          "fetched_at": {"type": "string", "format": "date-time", "nullable": true},
          "sources": {"type": "array", "items": {"type": "string"}},
          "sources_count": {"type": "integer"},
          "is_excluded": {"type": "boolean"}
        }
      },
      "CurrencyRatesResponse": {
        "type": "object",
        "properties": {
          "success": {"type": "boolean"},
          "rates": {"type": "object", "additionalProperties": {"type": "number"}},
          "cached": {"type": "boolean"},
          "updated_at": {"type": "string", "format": "date-time"},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "VanBasketCountriesResponse": {
        "type": "object",
        "properties": {
          "success": {"type": "boolean"},
          "data": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": true}},
          "meta": {"type": "object", "additionalProperties": true},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "VanBasketCompareResponse": {
        "type": "object",
        "properties": {
          "success": {"type": "boolean"},
          "data": {"type": "object", "additionalProperties": true},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "VanBasketCountryResponse": {
        "type": "object",
        "properties": {
          "success": {"type": "boolean"},
          "data": {"type": "object", "additionalProperties": true},
          "_attribution": {"$ref": "#/components/schemas/Attribution"}
        }
      },
      "VanSkyWeather": {
        "type": "object",
        "description": "Current-day weather snapshot used for van scoring.",
        "properties": {
          "temp_day": {"type": "number", "description": "Daytime air temperature in °C.", "nullable": true},
          "temp_night": {"type": "number", "description": "Nighttime air temperature in °C.", "nullable": true},
          "feels_like_day": {"type": "number", "nullable": true},
          "feels_like_night": {"type": "number", "nullable": true},
          "temp_current": {"type": "number", "nullable": true},
          "feels_like_current": {"type": "number", "nullable": true},
          "precip_sum": {"type": "number", "description": "Total precipitation in mm.", "nullable": true},
          "precip_prob_max": {"type": "number", "description": "Max precipitation probability in %.", "nullable": true},
          "wind_max": {"type": "number", "description": "Max sustained wind km/h.", "nullable": true},
          "wind_gusts_max": {"type": "number", "description": "Max wind gust km/h.", "nullable": true},
          "wind_dir": {"type": "number", "description": "Wind direction in degrees.", "nullable": true},
          "shortwave_radiation_sum": {"type": "number", "nullable": true},
          "peak_sun_hours": {"type": "number", "description": "Effective sunlight hours for solar panels.", "nullable": true},
          "uv_max": {"type": "number", "nullable": true},
          "sunrise": {"type": "string", "format": "date-time", "nullable": true},
          "sunset": {"type": "string", "format": "date-time", "nullable": true},
          "weather_code": {"type": "integer", "description": "WMO weather code.", "nullable": true},
          "humidity": {"type": "number", "nullable": true},
          "cloud_cover": {"type": "number", "nullable": true}
        }
      },
      "VanSkyMarine": {
        "type": "object",
        "description": "Marine data for coastal countries; null for landlocked.",
        "properties": {
          "sea_temp": {"type": "number", "description": "Sea temperature in °C.", "nullable": true},
          "wave_height": {"type": "number", "description": "Wave height in meters.", "nullable": true},
          "wave_dir": {"type": "number", "description": "Wave direction in degrees.", "nullable": true},
          "wave_period": {"type": "number", "description": "Wave period in seconds.", "nullable": true}
        }
      },
      "VanSkyForecastDay": {
        "type": "object",
        "description": "Single forecast day snapshot.",
        "properties": {
          "date": {"type": "string", "format": "date"},
          "van_score": {"type": "integer", "description": "Overall vanlife suitability 0-100.", "minimum": 0, "maximum": 100},
          "sleep_score": {"type": "integer", "description": "Overnight comfort 0-100.", "minimum": 0, "maximum": 100},
          "temp_day": {"type": "number", "nullable": true},
          "temp_night": {"type": "number", "nullable": true},
          "solar_kwh": {"type": "number", "description": "Estimated solar energy kWh/day per 1kW panel.", "nullable": true},
          "weather_code": {"type": "integer", "nullable": true},
          "score_label": {"type": "string", "enum": ["ideal", "comfortable", "acceptable", "harsh", "extreme"]},
          "drive_score": {"type": "integer", "minimum": 0, "maximum": 100}
        }
      },
      "VanSkyRecommendation": {
        "type": "object",
        "properties": {
          "type": {"type": "string", "enum": ["info", "warning", "danger", "tip"]},
          "key": {"type": "string", "description": "Translation key, e.g. vansky.rec.awning_caution."},
          "params": {"type": "object", "additionalProperties": true, "description": "Numeric params for the translated message."}
        }
      },
      "VanSkyCountry": {
        "type": "object",
        "properties": {
          "code": {"type": "string", "description": "ISO 3166-1 alpha-2 country code.", "example": "DE"},
          "is_coastal": {"type": "boolean"},
          "region": {"type": "string", "example": "europe"},
          "weather": {"$ref": "#/components/schemas/VanSkyWeather"},
          "marine": {"$ref": "#/components/schemas/VanSkyMarine"},
          "forecast": {"type": "array", "items": {"$ref": "#/components/schemas/VanSkyForecastDay"}, "description": "Up to 7 days ahead."},
          "van_score": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Today's overall van suitability."},
          "sleep_score": {"type": "integer", "minimum": 0, "maximum": 100},
          "solar_kwh": {"type": "number", "description": "Today's solar yield estimate.", "nullable": true},
          "solar_score": {"type": "integer", "minimum": 0, "maximum": 100, "nullable": true},
          "drive_score": {"type": "integer", "minimum": 0, "maximum": 100, "nullable": true},
          "sea_score": {"type": "integer", "minimum": 0, "maximum": 100, "nullable": true},
          "score_label": {"type": "string", "enum": ["ideal", "comfortable", "acceptable", "harsh", "extreme"]},
          "condensation_risk": {"type": "string", "enum": ["low", "medium", "high"], "description": "Overnight condensation risk inside van."},
          "awning_status": {"type": "string", "enum": ["safe", "caution", "retract"], "description": "Whether it is safe to deploy an awning."},
          "drive_window": {
            "type": "object",
            "nullable": true,
            "description": "Suggested safe driving window today.",
            "properties": {
              "start": {"type": "string", "format": "date-time"},
              "end": {"type": "string", "format": "date-time"}
            }
          },
          "best_move_day": {"$ref": "#/components/schemas/VanSkyForecastDay"},
          "recommendations": {"type": "array", "items": {"$ref": "#/components/schemas/VanSkyRecommendation"}},
          "fetched_at": {"type": "string", "format": "date-time", "nullable": true}
        }
      },
      "VanSkyCountriesResponse": {
        "type": "object",
        "properties": {
          "data": {"type": "array", "items": {"$ref": "#/components/schemas/VanSkyCountry"}},
          "count": {"type": "integer"},
          "updated_at": {"type": "string", "format": "date-time", "nullable": true},
          "source": {"type": "string", "description": "Data provider, e.g. open-meteo."}
        }
      },
      "VanSkyCountryResponse": {
        "type": "object",
        "properties": {
          "data": {"$ref": "#/components/schemas/VanSkyCountry"},
          "updated_at": {"type": "string", "format": "date-time", "nullable": true},
          "source": {"type": "string"}
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {"type": "boolean"},
          "error": {"type": "string"},
          "message": {"type": "string"}
        }
      }
    }
  }
}
