{
  "openapi": "3.0.3",
  "info": {
    "title": "TidesAtlas API",
    "version": "1.0.0",
    "description": "Worldwide tide predictions (high/low times and heights) for 13,000+ ports in 180 countries, plus exact-coordinate tides from the FES2022 ocean model, weather, marine (waves, swell, sea temperature), surface currents, and sun & moon data through 13 REST endpoints. The free plan includes 50 one-time credits; paid plans offer daily quotas.",
    "termsOfService": "https://tidesatlas.com/en/legal",
    "contact": {
      "name": "TidesAtlas",
      "url": "https://tidesatlas.com/en/api/docs",
      "email": "support@tidesatlas.com"
    },
    "x-logo": {
      "url": "https://tidesatlas.com/assets/images/favicon-512.png",
      "altText": "TidesAtlas"
    }
  },
  "externalDocs": {
    "description": "TidesAtlas API documentation",
    "url": "https://tidesatlas.com/en/api/docs"
  },
  "servers": [
    {
      "url": "https://tidesatlas.com/api/v1"
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "ApiKeyQuery": []
    }
  ],
  "tags": [
    {
      "name": "Tides",
      "description": "Tides pack: tide predictions, stations, countries, observations."
    },
    {
      "name": "Weather",
      "description": "Weather pack: weather, marine, astronomy, conditions, radar, currents."
    },
    {
      "name": "Complete",
      "description": "Complete pack only: history and webhooks."
    }
  ],
  "paths": {
    "/tides": {
      "get": {
        "operationId": "getTides",
        "tags": [
          "Tides"
        ],
        "summary": "Tide times & heights",
        "description": "Returns high and low tide times with heights. Use a station slug for calibrated port predictions, or pass coordinates to let TidesAtlas pick the best source automatically (station within 50 km, otherwise the FES2022 model at the exact point). Responses for stations in France include French tidal coefficients. Every response includes a `tide_source` block describing which curve is served.",
        "parameters": [
          {
            "$ref": "#/components/parameters/portTides"
          },
          {
            "$ref": "#/components/parameters/countryIso"
          },
          {
            "$ref": "#/components/parameters/latTides"
          },
          {
            "$ref": "#/components/parameters/lonTides"
          },
          {
            "$ref": "#/components/parameters/date"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-14 (default: 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 14,
              "default": 1
            },
            "example": 3
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TidesResponse"
                },
                "examples": {
                  "station": {
                    "summary": "Calibrated station (France, with coefficients)",
                    "value": {
                      "port": {
                        "name": "Dunkerque",
                        "slug": "dunkerque",
                        "lat": 51.048091,
                        "lon": 2.366698,
                        "timezone": "Europe/Paris",
                        "country": "France",
                        "country_slug": "france",
                        "region": "Hauts-de-France"
                      },
                      "data_source": "ticon",
                      "tide_source": {
                        "type": "own_station",
                        "confidence": "high"
                      },
                      "datum": {
                        "reference": "LAT",
                        "native": "LAT",
                        "available": [
                          "MHHW",
                          "MHW"
                        ]
                      },
                      "date": "2026-10-24",
                      "days": 1,
                      "count": 4,
                      "extremes": [
                        {
                          "datetime": "2026-10-24T06:37:36+02:00",
                          "timestamp": 1792816656,
                          "height_m": 1.13,
                          "type": "low"
                        },
                        {
                          "datetime": "2026-10-24T12:26:07+02:00",
                          "timestamp": 1792837567,
                          "height_m": 5.63,
                          "type": "high",
                          "coefficient": 74
                        },
                        {
                          "datetime": "2026-10-24T18:54:33+02:00",
                          "timestamp": 1792860873,
                          "height_m": 0.81,
                          "type": "low"
                        }
                      ],
                      "coefficients": {
                        "available": true,
                        "source": "Computed from Brest harmonic constituents (French tidal coefficient scale)",
                        "scale": "20 (neap) to 120 (extreme spring)",
                        "coverage": "2025-10-01 to 2032-09-28",
                        "note": "Tidal coefficients are a French maritime convention. Only available for French stations.",
                        "daily": [
                          {
                            "date": "2026-10-24",
                            "morning": 74,
                            "afternoon": 81,
                            "max": 81
                          }
                        ]
                      },
                      "has_observations": false
                    }
                  },
                  "model": {
                    "summary": "Open ocean coordinates (FES2022 model)",
                    "value": {
                      "point": {
                        "lat": 35,
                        "lon": -30,
                        "timezone": "UTC"
                      },
                      "data_source": "fes2022",
                      "datum": {
                        "reference": "LAT",
                        "native": "LAT",
                        "available": [
                          "LAT",
                          "MSL"
                        ]
                      },
                      "date": "2026-10-24",
                      "days": 1,
                      "count": 4,
                      "extremes": [
                        {
                          "datetime": "2026-10-24T00:10:00+00:00",
                          "height_m": 1.044,
                          "type": "high"
                        },
                        {
                          "datetime": "2026-10-24T06:10:00+00:00",
                          "height_m": 0.18,
                          "type": "low"
                        }
                      ],
                      "model": "FES2022 (34 constituents) via pyfes",
                      "attribution": {
                        "source": "FES2022 by LEGOS/NOVELTIS/CLS, distributed by AVISO+ with support from CNES",
                        "url": "https://www.aviso.altimetry.fr/en/data/products/auxiliary-products/global-tide-fes.html"
                      },
                      "nearest": {
                        "name": "Horta Azores",
                        "slug": "horta-azores",
                        "distance_km": 411.57,
                        "note": "Nearest station is 411.57 km away — used FES2022 model instead for higher accuracy at requested coordinates."
                      }
                    }
                  }
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "name,slug,lat,lon\nTokyo,tokyo,35.648617,139.77\n"
              },
              "application/geo+json": {
                "schema": {
                  "$ref": "#/components/schemas/GeoJSONFeatureCollection"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/tides/point": {
      "get": {
        "operationId": "getTidesAtPoint",
        "tags": [
          "Tides"
        ],
        "summary": "FES2022 tide model at exact coordinates",
        "description": "Always uses the FES2022 global ocean tide model (34 harmonic constituents) at the exact coordinates provided — never snaps to a station. Optionally returns an interval-sampled height series for plotting a tide curve. Returns 404 if the point is on land or outside the ocean mask.",
        "parameters": [
          {
            "$ref": "#/components/parameters/latReq"
          },
          {
            "$ref": "#/components/parameters/lonReq"
          },
          {
            "$ref": "#/components/parameters/dateUtc"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-14 (default: 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 14,
              "default": 1
            },
            "example": 3
          },
          {
            "name": "datum",
            "in": "query",
            "required": false,
            "description": "Vertical reference: `LAT` (default) or `MSL`.",
            "schema": {
              "type": "string",
              "enum": [
                "LAT",
                "MSL"
              ],
              "default": "LAT"
            },
            "example": "LAT"
          },
          {
            "name": "interval",
            "in": "query",
            "required": false,
            "description": "Minutes between samples, 5-60 (default: 10). Sets the spacing of the optional height series and the resolution of extremes detection.",
            "schema": {
              "type": "integer",
              "minimum": 5,
              "maximum": 60,
              "default": 10
            },
            "example": 30
          },
          {
            "name": "heights",
            "in": "query",
            "required": false,
            "description": "Set to `1` to also return an interval-sampled height series (default: off).",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1
              ],
              "default": 0
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TidesPointResponse"
                },
                "example": {
                  "query": {
                    "lat": 48.38,
                    "lon": -4.49,
                    "date": "2026-10-24",
                    "days": 1,
                    "datum": "LAT",
                    "interval_minutes": 60
                  },
                  "model": "FES2022 (34 constituents) via pyfes",
                  "datum": "LAT",
                  "datum_offset_msl": -3.5276,
                  "count": 4,
                  "extremes": [
                    {
                      "datetime": "2026-10-24T02:00:00+00:00",
                      "height_m": 5.925,
                      "type": "high"
                    },
                    {
                      "datetime": "2026-10-24T08:00:00+00:00",
                      "height_m": 0.94,
                      "type": "low"
                    }
                  ],
                  "attribution": {
                    "source": "FES2022 by LEGOS/NOVELTIS/CLS, distributed by AVISO+ with support from CNES",
                    "url": "https://www.aviso.altimetry.fr/en/data/products/auxiliary-products/global-tide-fes.html",
                    "license": "AVISO Products License"
                  },
                  "heights": [
                    {
                      "datetime": "2026-10-24T00:00:00+00:00",
                      "height_m": 4.673
                    },
                    {
                      "datetime": "2026-10-24T01:00:00+00:00",
                      "height_m": 5.582
                    }
                  ],
                  "heights_count": 24
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/ports": {
      "get": {
        "operationId": "searchPorts",
        "tags": [
          "Tides"
        ],
        "summary": "Search stations",
        "description": "Search and list available tide stations worldwide. Note that the search parameter is `search` (not `q`) and `country` takes a country slug.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Search by name or slug, e.g. `aberdeen`.",
            "schema": {
              "type": "string"
            },
            "example": "tokyo"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Filter by country slug, e.g. `united-kingdom`.",
            "schema": {
              "type": "string"
            },
            "example": "japan"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results, 1-500 (default: 50).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            },
            "example": 50
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortsResponse"
                },
                "example": {
                  "count": 2,
                  "total": 2,
                  "offset": 0,
                  "limit": 2,
                  "ports": [
                    {
                      "name": "Nishi-Tokyo-Shi",
                      "slug": "nishi-tokyo-shi",
                      "lat": 35.72526,
                      "lon": 139.5383,
                      "timezone": "Asia/Tokyo",
                      "country": "Japan",
                      "country_slug": "japan",
                      "code_iso": "JP",
                      "region": "Tokyo"
                    },
                    {
                      "name": "Tokyo",
                      "slug": "tokyo",
                      "lat": 35.648617,
                      "lon": 139.77,
                      "timezone": "Asia/Tokyo",
                      "country": "Japan",
                      "country_slug": "japan",
                      "code_iso": "JP",
                      "region": "Tokyo"
                    }
                  ]
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "name,slug,lat,lon\nTokyo,tokyo,35.648617,139.77\n"
              },
              "application/geo+json": {
                "schema": {
                  "$ref": "#/components/schemas/GeoJSONFeatureCollection"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/countries": {
      "get": {
        "operationId": "listCountries",
        "tags": [
          "Tides"
        ],
        "summary": "List all countries",
        "description": "Returns all countries with their station count.",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CountriesResponse"
                },
                "example": {
                  "count": 185,
                  "countries": [
                    {
                      "name": "United States",
                      "slug": "united-states",
                      "code_iso": "US",
                      "station_count": 2048
                    },
                    {
                      "name": "India",
                      "slug": "india",
                      "code_iso": "IN",
                      "station_count": 1447
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/weather": {
      "get": {
        "operationId": "getWeather",
        "tags": [
          "Weather"
        ],
        "summary": "Weather forecast",
        "description": "Returns hourly and daily weather forecast including temperature, wind, precipitation, UV index and conditions (TidesAtlas weather engine, ECMWF AIFS multi-model blend). Each point is cached server-side for up to 3 hours, never across UTC days; check the `cached` and `stale` fields.",
        "parameters": [
          {
            "$ref": "#/components/parameters/port"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-7 (default: 3).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7,
              "default": 3
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeatherResponse"
                },
                "example": {
                  "lat": 48.3829,
                  "lon": -4.49504,
                  "timezone": "Europe/Paris",
                  "days": 1,
                  "source": "TidesAtlas weather engine — ECMWF-AIFS multi-model blend, bias-corrected & verified",
                  "daily": [
                    {
                      "date": "2026-10-24",
                      "temperature_max_c": 16.6,
                      "temperature_min_c": 12.2,
                      "precipitation_mm": 7.8,
                      "wind_max_kmh": 42,
                      "uv_index_max": 1.3,
                      "condition": "thunderstorm",
                      "sunrise": "08:51",
                      "sunset": "19:12"
                    },
                    {
                      "date": "2026-10-25",
                      "temperature_max_c": 16.8,
                      "temperature_min_c": 12.8,
                      "precipitation_mm": 9.5,
                      "wind_max_kmh": 43.2,
                      "uv_index_max": 0,
                      "condition": "thunderstorm",
                      "sunrise": "07:53",
                      "sunset": "18:11"
                    }
                  ],
                  "hourly": [
                    {
                      "datetime": "2026-10-24T06:00:00+00:00",
                      "temperature_c": 12.2,
                      "feels_like_c": 12.2,
                      "humidity_pct": 81,
                      "precipitation_mm": 0,
                      "pressure_hpa": 1018.6,
                      "cloud_cover_pct": 100,
                      "wind_speed_kmh": 23,
                      "wind_direction_deg": 238,
                      "wind_gust_kmh": 46.9,
                      "uv_index": 0,
                      "condition": "overcast"
                    },
                    {
                      "datetime": "2026-10-24T09:00:00+00:00",
                      "temperature_c": 12.7,
                      "feels_like_c": 12.7,
                      "humidity_pct": 89,
                      "precipitation_mm": 1.2,
                      "pressure_hpa": 1015.7,
                      "cloud_cover_pct": 100,
                      "wind_speed_kmh": 36.1,
                      "wind_direction_deg": 223,
                      "wind_gust_kmh": 58.4,
                      "uv_index": 0.6,
                      "condition": "light_rain"
                    }
                  ],
                  "port": {
                    "name": "Brest",
                    "slug": "brest",
                    "country": "France"
                  },
                  "cached": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/marine": {
      "get": {
        "operationId": "getMarine",
        "tags": [
          "Weather"
        ],
        "summary": "Ocean, wave & water-temperature forecast",
        "description": "Returns wave height, swell, wind waves and sea state (NOAA GFS-Wave / WaveWatch III) plus sea-surface temperature (MUR SST). An inland point returns 422.",
        "parameters": [
          {
            "$ref": "#/components/parameters/port"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-7 (default: 3).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7,
              "default": 3
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarineResponse"
                },
                "example": {
                  "lat": 48.39,
                  "lon": -4.6,
                  "timezone": "UTC",
                  "days": 1,
                  "source": "TidesAtlas weather engine. Waves: NOAA GFS-Wave / WaveWatch III. Water temperature: MUR SST (NASA JPL / GHRSST, ~1km).",
                  "daily": [
                    {
                      "date": "2026-10-24",
                      "wave_height_max_m": 5.61,
                      "wave_period_max_s": 12,
                      "wave_direction_dominant": 276,
                      "swell_height_max_m": 2.2,
                      "swell_period_max_s": 8.5,
                      "sea_state": "rough"
                    },
                    {
                      "date": "2026-10-25",
                      "wave_height_max_m": 5.65,
                      "wave_period_max_s": 12.5,
                      "wave_direction_dominant": 280,
                      "swell_height_max_m": 0,
                      "swell_period_max_s": 12.6,
                      "sea_state": "rough"
                    }
                  ],
                  "hourly": [
                    {
                      "datetime": "2026-10-24T06:00:00+00:00",
                      "wave_height_m": 3.25,
                      "wave_direction_deg": 290,
                      "wave_period_s": 11.8,
                      "swell_height_m": 0,
                      "swell_direction_deg": 290,
                      "swell_period_s": 11.8,
                      "wind_wave_height_m": 3.25,
                      "wind_wave_direction": 282,
                      "wind_wave_period_s": 11.8,
                      "sea_surface_temperature_c": 15
                    },
                    {
                      "datetime": "2026-10-24T09:00:00+00:00",
                      "wave_height_m": 3.27,
                      "wave_direction_deg": 293,
                      "wave_period_s": 11.8,
                      "swell_height_m": 1.77,
                      "swell_direction_deg": 293,
                      "swell_period_s": 11.8,
                      "wind_wave_height_m": 2.75,
                      "wind_wave_direction": 256,
                      "wind_wave_period_s": 9.4,
                      "sea_surface_temperature_c": 15
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "The requested point is inland.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": true,
                  "message": "No marine data at this location: the requested point is inland. Provide coastal or offshore coordinates, or use ?port=slug.",
                  "status": 422
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/astronomy": {
      "get": {
        "operationId": "getAstronomy",
        "tags": [
          "Weather"
        ],
        "summary": "Sun & moon data",
        "description": "Returns sunrise, sunset, civil dawn and dusk, day length, moon phase, illumination, moonrise and moonset (local time of the station, UTC for raw coordinates).",
        "parameters": [
          {
            "$ref": "#/components/parameters/port"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "$ref": "#/components/parameters/date"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-7 (default: 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7,
              "default": 1
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AstronomyResponse"
                },
                "example": {
                  "lat": 48.3829,
                  "lon": -4.49504,
                  "timezone": "Europe/Paris",
                  "date": "2026-10-24",
                  "days": 1,
                  "data": [
                    {
                      "date": "2026-10-24",
                      "sun": {
                        "sunrise": "08:52",
                        "sunset": "19:13",
                        "dawn": "08:19",
                        "dusk": "19:45",
                        "day_length": "10:21"
                      },
                      "moon": {
                        "phase": "waxing_gibbous",
                        "illumination_pct": 97.5,
                        "age_days": 13.3,
                        "moonrise": "01:58",
                        "moonset": "16:04"
                      }
                    }
                  ],
                  "port": {
                    "name": "Brest",
                    "slug": "brest",
                    "country": "France"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/conditions": {
      "get": {
        "operationId": "getConditions",
        "tags": [
          "Weather"
        ],
        "summary": "All conditions (unified)",
        "description": "Returns weather, marine and astronomy data in a single call. Use `include` to select specific datasets.",
        "parameters": [
          {
            "$ref": "#/components/parameters/port"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-7 (default: 3).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7,
              "default": 3
            },
            "example": 3
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of datasets: `weather`, `marine`, `astronomy` (default: all).",
            "schema": {
              "type": "string",
              "default": "weather,marine,astronomy"
            },
            "example": "weather,astronomy"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConditionsResponse"
                },
                "example": {
                  "lat": 48.3829,
                  "lon": -4.49504,
                  "timezone": "Europe/Paris",
                  "days": 1,
                  "port": {
                    "name": "Brest",
                    "slug": "brest",
                    "country": "France"
                  },
                  "weather": {
                    "daily": [
                      {
                        "date": "2026-10-24",
                        "temperature_max_c": 16.6,
                        "temperature_min_c": 12.2,
                        "precipitation_mm": 7.8,
                        "wind_max_kmh": 42,
                        "uv_index_max": 1.3,
                        "condition": "thunderstorm",
                        "sunrise": "08:51",
                        "sunset": "19:12"
                      }
                    ],
                    "hourly": [
                      {
                        "datetime": "2026-10-24T06:00:00+00:00",
                        "temperature_c": 12.2,
                        "feels_like_c": 12.2,
                        "humidity_pct": 81,
                        "precipitation_mm": 0,
                        "pressure_hpa": 1018.6,
                        "cloud_cover_pct": 100,
                        "wind_speed_kmh": 23,
                        "wind_direction_deg": 238,
                        "wind_gust_kmh": 46.9,
                        "uv_index": 0,
                        "condition": "overcast"
                      }
                    ]
                  },
                  "marine": {
                    "daily": [
                      {
                        "date": "2026-10-24",
                        "wave_height_max_m": 5.61,
                        "wave_period_max_s": 12,
                        "wave_direction_dominant": 276,
                        "swell_height_max_m": 2.2,
                        "swell_period_max_s": 8.5,
                        "sea_state": "rough"
                      }
                    ],
                    "hourly": [
                      {
                        "datetime": "2026-10-24T06:00:00+00:00",
                        "wave_height_m": 3.25,
                        "wave_direction_deg": 290,
                        "wave_period_s": 11.8,
                        "swell_height_m": 0,
                        "swell_direction_deg": 290,
                        "swell_period_s": 11.8,
                        "wind_wave_height_m": 3.25,
                        "wind_wave_direction": 282,
                        "wind_wave_period_s": 11.8,
                        "sea_surface_temperature_c": 14.9
                      }
                    ]
                  },
                  "astronomy": [
                    {
                      "date": "2026-10-24",
                      "sun": {
                        "sunrise": "08:52",
                        "sunset": "19:13",
                        "dawn": "08:19",
                        "dusk": "19:45",
                        "day_length": "10:21"
                      },
                      "moon": {
                        "phase": "waxing_gibbous",
                        "illumination_pct": 97.5,
                        "age_days": 13.3,
                        "moonrise": "01:58",
                        "moonset": "16:04"
                      }
                    }
                  ],
                  "sources": {
                    "weather": "TidesAtlas weather engine — ECMWF-AIFS multi-model blend, bias-corrected & verified",
                    "marine": "TidesAtlas weather engine — NOAA GFS-Wave / WaveWatch III",
                    "astronomy": "Calculated (NOAA Solar Calculator / Lunar Ephemeris)"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/radar": {
      "get": {
        "operationId": "getRadar",
        "tags": [
          "Weather"
        ],
        "summary": "Rain radar",
        "description": "Returns real-time rain radar tile URLs (RainViewer, updated every ~10 minutes) for animated precipitation maps. Tile URLs use the standard slippy-map format.",
        "parameters": [
          {
            "$ref": "#/components/parameters/port"
          },
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RadarResponse"
                },
                "example": {
                  "lat": 48.39,
                  "lon": -4.49,
                  "generated": 1792869023,
                  "source": "RainViewer (rainviewer.com)",
                  "tile_size": 256,
                  "max_zoom": 7,
                  "color_scheme": 1,
                  "frames": {
                    "past": [
                      {
                        "time": "2026-10-24T17:10:00Z",
                        "timestamp": 1792861800,
                        "tile_url": "https://tilecache.rainviewer.com/v2/radar/8f3fefa828fd/256/{z}/{x}/{y}/1/1_1.png"
                      },
                      {
                        "time": "2026-10-24T17:20:00Z",
                        "timestamp": 1792862400,
                        "tile_url": "https://tilecache.rainviewer.com/v2/radar/80805051f420/256/{z}/{x}/{y}/1/1_1.png"
                      }
                    ],
                    "nowcast": []
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/observations": {
      "get": {
        "operationId": "getObservations",
        "tags": [
          "Tides"
        ],
        "summary": "Sea level observations",
        "description": "Returns real-time observed sea level heights from French REFMAR/SHOM tide gauges (~100 stations), sub-sampled to 10-minute intervals. A secondary port with no gauge of its own returns 404.",
        "parameters": [
          {
            "name": "port",
            "in": "query",
            "required": true,
            "description": "Station slug (French REFMAR stations with their own gauge), e.g. `brest`.",
            "schema": {
              "type": "string"
            },
            "example": "brest"
          },
          {
            "$ref": "#/components/parameters/countryIso"
          },
          {
            "$ref": "#/components/parameters/date"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-7 (default: 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7,
              "default": 1
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObservationsResponse"
                },
                "example": {
                  "port": {
                    "name": "Brest",
                    "slug": "brest",
                    "country": "France"
                  },
                  "station_id": 3,
                  "lat": 48.3829,
                  "lon": -4.49504,
                  "timezone": "Europe/Paris",
                  "source": "REFMAR/SHOM (Licence Etalab 2.0)",
                  "interval_minutes": 10,
                  "data": [
                    {
                      "date": "2026-10-24",
                      "observations": [
                        {
                          "datetime": "2026-10-24T02:00",
                          "height_m": 4.89,
                          "timestamp": 1792800000
                        },
                        {
                          "datetime": "2026-10-24T02:10",
                          "height_m": 5.06,
                          "timestamp": 1792800600
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/currents": {
      "get": {
        "operationId": "getCurrents",
        "tags": [
          "Weather"
        ],
        "summary": "Surface currents (open water)",
        "description": "Hourly total surface currents (ocean circulation + tide + Stokes drift) up to 5 days ahead, anywhere at sea, from Copernicus Marine (~8 km grid). Speed in knots, direction the current flows towards. Every response carries a `scope` flag; tidal races, narrow passes and harbour entrances are not resolved. Not for navigation. A point on land returns 400.",
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": false,
            "description": "Latitude of a position at sea (alternative: `port`).",
            "schema": {
              "type": "number",
              "format": "double",
              "minimum": -90,
              "maximum": 90
            },
            "example": 48.3
          },
          {
            "name": "lon",
            "in": "query",
            "required": false,
            "description": "Longitude of a position at sea (alternative: `port`).",
            "schema": {
              "type": "number",
              "format": "double",
              "minimum": -180,
              "maximum": 180
            },
            "example": -5.2
          },
          {
            "$ref": "#/components/parameters/port"
          },
          {
            "$ref": "#/components/parameters/countryIso"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days, 1-5 (default: 3).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5,
              "default": 3
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentsResponse"
                },
                "example": {
                  "lat": 48.3,
                  "lon": -5.2,
                  "days": 1,
                  "unit": {
                    "speed": "knots",
                    "direction": "degrees, direction the current flows TOWARDS"
                  },
                  "scope": "open_water",
                  "scope_note": "Model resolution ~8 km: reliable in open water. Small islands and reefs may be invisible to the model. Values cached per ~0.1 deg cell for up to 6 h.",
                  "model_point": {
                    "lat": 48.3333,
                    "lon": -5.1667,
                    "distance_km": 4.4
                  },
                  "hours": [
                    {
                      "t": "2026-10-24 19:00",
                      "u_ms": 0.261,
                      "v_ms": -0.91,
                      "speed_kn": 1.84,
                      "dir_deg": 164
                    },
                    {
                      "t": "2026-10-24 20:00",
                      "u_ms": 0.285,
                      "v_ms": -0.712,
                      "speed_kn": 1.49,
                      "dir_deg": 158
                    }
                  ],
                  "model": "Copernicus Marine GLOBAL_ANALYSISFORECAST_PHY_001_024 (1/12 deg, hourly, total = circulation + tide + Stokes drift)",
                  "attribution": "Generated using E.U. Copernicus Marine Service Information; https://doi.org/10.48670/moi-00016",
                  "disclaimer": "Not for navigation."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/history": {
      "get": {
        "operationId": "getHistory",
        "tags": [
          "Complete"
        ],
        "summary": "Historical data",
        "description": "Returns historical tide predictions and weather data for past dates. Tides via the TICON engine, weather via the ERA5 reanalysis (ECMWF/Copernicus). The whole requested range must be in the past. Complete pack only.",
        "parameters": [
          {
            "name": "port",
            "in": "query",
            "required": true,
            "description": "Station slug.",
            "schema": {
              "type": "string"
            },
            "example": "brest"
          },
          {
            "$ref": "#/components/parameters/countryIso"
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "description": "Start date in the past, `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2025-06-01"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of days: 1-365 for tides, 1-90 for weather (default: 7).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 7
            },
            "example": 3
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated: `tides`, `weather` (default: `tides`).",
            "schema": {
              "type": "string",
              "default": "tides"
            },
            "example": "tides,weather"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryResponse"
                },
                "example": {
                  "port": {
                    "name": "Brest",
                    "slug": "brest",
                    "lat": 48.3829,
                    "lon": -4.49504,
                    "timezone": "Europe/Paris",
                    "country": "France",
                    "country_slug": "france",
                    "region": "Bretagne"
                  },
                  "date": "2025-06-01",
                  "days": 2,
                  "include": [
                    "tides",
                    "weather"
                  ],
                  "tides": {
                    "count": 8,
                    "extremes": [
                      {
                        "datetime": "2025-06-01T03:43:58+02:00",
                        "timestamp": 1748742238,
                        "height_m": 1.88,
                        "type": "low"
                      },
                      {
                        "datetime": "2025-06-01T09:41:29+02:00",
                        "timestamp": 1748763689,
                        "height_m": 5.9,
                        "type": "high"
                      }
                    ]
                  },
                  "weather": {
                    "source": "TidesAtlas weather engine — ERA5 reanalysis (ECMWF / Copernicus C3S) from the public ARCO-ERA5 archive",
                    "count": 2,
                    "daily": [
                      {
                        "date": "2025-06-01",
                        "temperature_max_c": 16.4,
                        "temperature_min_c": 13,
                        "precipitation_mm": 0.3,
                        "wind_max_kmh": 24
                      },
                      {
                        "date": "2025-06-02",
                        "temperature_max_c": 16.9,
                        "temperature_min_c": 11.9,
                        "precipitation_mm": 0,
                        "wind_max_kmh": 19.8
                      }
                    ]
                  }
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "name,slug,lat,lon\nTokyo,tokyo,35.648617,139.77\n"
              },
              "application/geo+json": {
                "schema": {
                  "$ref": "#/components/schemas/GeoJSONFeatureCollection"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "tags": [
          "Complete"
        ],
        "summary": "List webhooks",
        "description": "Create, manage and test webhooks for real-time alerts. Supported events: `spring_tide` (high tide > 4 m) and `storm_warning` (wind > 60 km/h or waves > 4 m). Deliveries are signed with HMAC-SHA256 in the `X-TidesAtlas-Signature` header (`sha256=<hex>`). Webhooks are auto-disabled after 3 consecutive failures. Complete pack only.",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                },
                "example": {
                  "count": 1,
                  "limit": 3,
                  "webhooks": [
                    {
                      "id": 1,
                      "url": "https://example.com/webhook",
                      "events": [
                        "spring_tide",
                        "storm_warning"
                      ],
                      "port_slug": "brest",
                      "active": true,
                      "failure_count": 0,
                      "last_triggered_at": null,
                      "created_at": "2026-03-28 07:26:42"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "manageWebhooks",
        "tags": [
          "Complete"
        ],
        "summary": "Create, update, delete or test a webhook",
        "description": "Create, manage and test webhooks for real-time alerts. Supported events: `spring_tide` (high tide > 4 m) and `storm_warning` (wind > 60 km/h or waves > 4 m). Deliveries are signed with HMAC-SHA256 in the `X-TidesAtlas-Signature` header (`sha256=<hex>`). Webhooks are auto-disabled after 3 consecutive failures. Complete pack only.",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": true,
            "description": "Operation to perform.",
            "schema": {
              "type": "string",
              "enum": [
                "list",
                "create",
                "update",
                "delete",
                "test"
              ]
            },
            "example": "create"
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Webhook ID (required for `update`, `delete` and `test`).",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Webhook fields (for `create` and `update`).",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              },
              "example": {
                "url": "https://example.com/webhook",
                "events": [
                  "spring_tide",
                  "storm_warning"
                ],
                "port_slug": "brest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result of `list`, `update`, `delete` or `test`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/WebhookList"
                    },
                    {
                      "$ref": "#/components/schemas/WebhookActionResult"
                    },
                    {
                      "$ref": "#/components/schemas/WebhookTestResult"
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Webhook created (`create`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key sent in the `X-API-Key` header (recommended)."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "API key sent as the `api_key` query parameter."
      }
    },
    "parameters": {
      "portTides": {
        "name": "port",
        "in": "query",
        "required": false,
        "description": "Station slug, e.g. `aberdeen`. Use `/ports` to search. Provide either `port` or `lat` + `lon`.",
        "schema": {
          "type": "string"
        },
        "example": "aberdeen"
      },
      "countryIso": {
        "name": "country",
        "in": "query",
        "required": false,
        "description": "Optional ISO 3166-1 alpha-2 country code to disambiguate a slug shared by several cities (e.g. `port=san-juan&country=PH`). When a slug is ambiguous, `/tides` sets `tide_source.slug_ambiguous: true`.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z]{2}$"
        },
        "example": "PH"
      },
      "latTides": {
        "name": "lat",
        "in": "query",
        "required": false,
        "description": "Latitude (decimal degrees). Smart routing: if a calibrated station is within 50 km its prediction is returned (`data_source: \"ticon\"`); otherwise the FES2022 global ocean model is used at the exact coordinates (`data_source: \"fes2022\"`).",
        "schema": {
          "type": "number",
          "format": "double",
          "minimum": -90,
          "maximum": 90
        },
        "example": 57.14
      },
      "lonTides": {
        "name": "lon",
        "in": "query",
        "required": false,
        "description": "Longitude (decimal degrees). See `lat`.",
        "schema": {
          "type": "number",
          "format": "double",
          "minimum": -180,
          "maximum": 180
        },
        "example": -2.08
      },
      "latReq": {
        "name": "lat",
        "in": "query",
        "required": true,
        "description": "Latitude, decimal degrees, -90 to 90.",
        "schema": {
          "type": "number",
          "format": "double",
          "minimum": -90,
          "maximum": 90
        },
        "example": 48.383
      },
      "lonReq": {
        "name": "lon",
        "in": "query",
        "required": true,
        "description": "Longitude, decimal degrees, -180 to 180.",
        "schema": {
          "type": "number",
          "format": "double",
          "minimum": -180,
          "maximum": 180
        },
        "example": -4.495
      },
      "port": {
        "name": "port",
        "in": "query",
        "required": false,
        "description": "Station slug (use `/ports` to search), e.g. `brest`. Alternative to `lat`/`lon`.",
        "schema": {
          "type": "string"
        },
        "example": "brest"
      },
      "lat": {
        "name": "lat",
        "in": "query",
        "required": false,
        "description": "Latitude in decimal degrees (-90 to 90). Use together with `lon` as an alternative to `port`.",
        "schema": {
          "type": "number",
          "format": "double",
          "minimum": -90,
          "maximum": 90
        },
        "example": 48.39
      },
      "lon": {
        "name": "lon",
        "in": "query",
        "required": false,
        "description": "Longitude in decimal degrees (-180 to 180). Use together with `lat` as an alternative to `port`.",
        "schema": {
          "type": "number",
          "format": "double",
          "minimum": -180,
          "maximum": 180
        },
        "example": -4.49
      },
      "date": {
        "name": "date",
        "in": "query",
        "required": false,
        "description": "Start date, `YYYY-MM-DD` (default: today).",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "example": "2026-05-14"
      },
      "dateUtc": {
        "name": "date",
        "in": "query",
        "required": false,
        "description": "Start date (UTC), `YYYY-MM-DD` (default: today).",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "example": "2026-05-14"
      },
      "format": {
        "name": "format",
        "in": "query",
        "required": false,
        "description": "Response format: `json`, `csv` or `geojson` (default: `json`).",
        "schema": {
          "type": "string",
          "enum": [
            "json",
            "csv",
            "geojson"
          ],
          "default": "json"
        },
        "example": "json"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Your quota (daily limit, or total credits on the free plan).",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests (or credits) left.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Quota reset time (Unix timestamp, midnight UTC). Not sent for one-time free credits.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid or missing parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Missing parameter: provide ?port=slug or ?lat=XX&lon=XX",
              "status": 400
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Missing API key. Provide it via X-API-Key header or ?api_key= parameter.",
              "status": 401
            }
          }
        }
      },
      "Forbidden": {
        "description": "Account deactivated, email not verified, or endpoint not included in your pack.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Your Tides pack does not include the /weather endpoint. Upgrade to Complete at https://tidesatlas.com/api/dashboard",
              "status": 403
            }
          }
        }
      },
      "NotFound": {
        "description": "Station (or data) not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Station 'xyz' not found. Use GET /api/v1/ports to search.",
              "status": 404
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Plan quota or per-IP rate limit exceeded.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Daily rate limit exceeded (500/day). Upgrade at https://tidesatlas.com/api/dashboard",
              "status": 429
            }
          }
        },
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        }
      },
      "BadGateway": {
        "description": "Upstream data engine unavailable and no cached data for this point.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Weather data temporarily unavailable.",
              "status": 502
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Data temporarily unavailable; retry shortly.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": true,
              "message": "Current data temporarily unavailable for this location.",
              "status": 503
            }
          }
        },
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "boolean",
            "enum": [
              true
            ],
            "example": true
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message.",
            "example": "Missing API key. Provide it via X-API-Key header or ?api_key= parameter."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code, repeated in the body.",
            "example": 401
          }
        },
        "required": [
          "error",
          "message",
          "status"
        ]
      },
      "PortRef": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Brest"
          },
          "slug": {
            "type": "string",
            "example": "brest"
          },
          "country": {
            "type": "string",
            "example": "France"
          }
        },
        "required": [
          "name",
          "slug"
        ],
        "description": "Station the request was resolved to (present when `port` was used)."
      },
      "Station": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Dunkerque"
          },
          "slug": {
            "type": "string",
            "example": "dunkerque"
          },
          "lat": {
            "type": "number",
            "example": 51.048091
          },
          "lon": {
            "type": "number",
            "example": 2.366698
          },
          "timezone": {
            "type": "string",
            "description": "IANA time zone of the station.",
            "example": "Europe/Paris"
          },
          "country": {
            "type": "string",
            "example": "France"
          },
          "country_slug": {
            "type": "string",
            "example": "france"
          },
          "region": {
            "type": "string",
            "example": "Hauts-de-France"
          }
        },
        "required": [
          "name",
          "slug",
          "lat",
          "lon",
          "timezone"
        ]
      },
      "TideExtreme": {
        "type": "object",
        "properties": {
          "datetime": {
            "type": "string",
            "format": "date-time",
            "description": "Local time of the extreme (ISO 8601 with offset). UTC for model points.",
            "example": "2026-05-14T11:49:00+02:00"
          },
          "timestamp": {
            "type": "integer",
            "description": "Unix timestamp (station predictions only).",
            "example": 1778752140
          },
          "height_m": {
            "type": "number",
            "description": "Height in metres above the datum given in `datum.reference`.",
            "example": 5.56
          },
          "type": {
            "type": "string",
            "enum": [
              "high",
              "low"
            ],
            "example": "high"
          },
          "coefficient": {
            "type": "integer",
            "description": "French tidal coefficient (20-120). High tides at French stations only.",
            "example": 71
          }
        },
        "required": [
          "datetime",
          "height_m",
          "type"
        ]
      },
      "MeasuredAccuracy": {
        "type": "object",
        "properties": {
          "gauge": {
            "type": "string",
            "example": "uhslc-245"
          },
          "window": {
            "type": "string",
            "example": "2026-06-16 to 2026-07-31"
          },
          "high_water": {
            "type": "object",
            "properties": {
              "time_min": {
                "type": "number"
              },
              "height_cm": {
                "type": "number"
              }
            }
          },
          "low_water": {
            "type": "object",
            "properties": {
              "time_min": {
                "type": "number"
              },
              "height_cm": {
                "type": "number"
              }
            }
          },
          "note": {
            "type": "string"
          }
        },
        "description": "Median absolute errors versus tide-gauge observations over the stated window (only when a verification ≤ 60 days old exists). Informational only."
      },
      "TideSource": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "own_station",
              "reference_port",
              "model_point"
            ],
            "description": "Which curve is served.",
            "example": "own_station"
          },
          "confidence": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ],
            "example": "high"
          },
          "reference_slug": {
            "type": "string",
            "description": "Reference station (secondary ports).",
            "example": "le-crouesty"
          },
          "reference_name": {
            "type": "string",
            "example": "Le Crouesty"
          },
          "distance_km": {
            "type": "number",
            "description": "Distance to the reference station.",
            "nullable": true,
            "example": 10.9
          },
          "corrections_applied": {
            "type": "boolean",
            "description": "Whether local harmonic corrections were applied to the reference curve."
          },
          "method": {
            "type": "string",
            "enum": [
              "harmonic_transfer_ram",
              "same_site"
            ],
            "example": "harmonic_transfer_ram"
          },
          "model": {
            "type": "string",
            "example": "FES2022"
          },
          "attribution": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "slug_ambiguous": {
            "type": "boolean",
            "description": "True when several cities share the requested slug; pass `country` to disambiguate."
          },
          "note_ambiguity": {
            "type": "string"
          },
          "measured_accuracy": {
            "$ref": "#/components/schemas/MeasuredAccuracy"
          }
        },
        "required": [
          "type",
          "confidence"
        ],
        "description": "Transparency block describing the source of the tide curve."
      },
      "Datum": {
        "type": "object",
        "properties": {
          "reference": {
            "type": "string",
            "description": "Datum the heights are referenced to.",
            "example": "LAT"
          },
          "native": {
            "type": "string",
            "description": "Native datum of the station (LAT or CD).",
            "example": "LAT"
          },
          "available": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Datums available for this station."
          }
        },
        "required": [
          "reference",
          "native",
          "available"
        ]
      },
      "Coefficients": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean"
          },
          "source": {
            "type": "string"
          },
          "scale": {
            "type": "string",
            "example": "20 (neap) to 120 (extreme spring)"
          },
          "coverage": {
            "type": "string",
            "example": "2025-10-01 to 2032-09-28"
          },
          "note": {
            "type": "string"
          },
          "daily": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "morning": {
                  "type": "integer",
                  "nullable": true,
                  "example": 71
                },
                "afternoon": {
                  "type": "integer",
                  "nullable": true,
                  "example": 77
                },
                "max": {
                  "type": "integer",
                  "example": 77
                }
              }
            }
          }
        },
        "required": [
          "available",
          "daily"
        ],
        "description": "French tidal coefficients (French stations only)."
      },
      "TidesStationResponse": {
        "type": "object",
        "properties": {
          "port": {
            "$ref": "#/components/schemas/Station"
          },
          "data_source": {
            "type": "string",
            "enum": [
              "ticon"
            ],
            "example": "ticon"
          },
          "tide_source": {
            "$ref": "#/components/schemas/TideSource"
          },
          "datum": {
            "$ref": "#/components/schemas/Datum"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "extremes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TideExtreme"
            }
          },
          "coefficients": {
            "$ref": "#/components/schemas/Coefficients"
          },
          "nearest": {
            "type": "object",
            "properties": {
              "distance_km": {
                "type": "number"
              },
              "note": {
                "type": "string"
              }
            },
            "description": "Present when the request used `lat`/`lon` and was snapped to a station within 50 km."
          },
          "has_observations": {
            "type": "boolean",
            "description": "True only for stations with their own real-time tide gauge."
          },
          "observations": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "example": "REFMAR/SHOM"
              },
              "interval_minutes": {
                "type": "integer",
                "example": 10
              },
              "data": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "dt": {
                      "type": "integer",
                      "description": "Unix timestamp."
                    },
                    "height": {
                      "type": "number",
                      "description": "Observed height (m)."
                    },
                    "time": {
                      "type": "string",
                      "description": "Local time HH:MM.",
                      "example": "00:10"
                    }
                  }
                }
              }
            },
            "description": "Real-time gauge observations (only when `has_observations` is true)."
          }
        },
        "required": [
          "port",
          "data_source",
          "tide_source",
          "datum",
          "date",
          "days",
          "count",
          "extremes",
          "has_observations"
        ],
        "description": "Prediction from a calibrated station (TICON-4 / NOAA / FES2022-derived station)."
      },
      "TidesModelResponse": {
        "type": "object",
        "properties": {
          "point": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "timezone": {
                "type": "string",
                "example": "UTC"
              }
            }
          },
          "data_source": {
            "type": "string",
            "enum": [
              "fes2022"
            ],
            "example": "fes2022"
          },
          "datum": {
            "$ref": "#/components/schemas/Datum"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "extremes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TideExtreme"
            }
          },
          "model": {
            "type": "string",
            "example": "FES2022 (34 constituents) via pyfes"
          },
          "attribution": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "nearest": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "slug": {
                "type": "string"
              },
              "distance_km": {
                "type": "number"
              },
              "note": {
                "type": "string"
              }
            },
            "description": "Nearest station (more than 50 km away)."
          }
        },
        "required": [
          "point",
          "data_source",
          "datum",
          "date",
          "days",
          "count",
          "extremes",
          "model"
        ],
        "description": "Prediction from the FES2022 global ocean model at the exact coordinates (no station within 50 km)."
      },
      "TidesResponse": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/TidesStationResponse"
          },
          {
            "$ref": "#/components/schemas/TidesModelResponse"
          }
        ]
      },
      "TidesPointResponse": {
        "type": "object",
        "properties": {
          "query": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "date": {
                "type": "string",
                "format": "date"
              },
              "days": {
                "type": "integer"
              },
              "datum": {
                "type": "string",
                "enum": [
                  "LAT",
                  "MSL"
                ]
              },
              "interval_minutes": {
                "type": "integer"
              }
            }
          },
          "model": {
            "type": "string",
            "example": "FES2022 (34 constituents) via pyfes"
          },
          "datum": {
            "type": "string",
            "enum": [
              "LAT",
              "MSL"
            ]
          },
          "datum_offset_msl": {
            "type": "number",
            "description": "Offset of the chosen datum relative to MSL (m).",
            "nullable": true,
            "example": -3.5276
          },
          "count": {
            "type": "integer"
          },
          "extremes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TideExtreme"
            }
          },
          "heights": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "datetime": {
                  "type": "string",
                  "format": "date-time"
                },
                "height_m": {
                  "type": "number"
                }
              },
              "required": [
                "datetime",
                "height_m"
              ]
            },
            "description": "Interval-sampled height series (only with `heights=1`)."
          },
          "heights_count": {
            "type": "integer",
            "description": "Only with `heights=1`."
          },
          "attribution": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              },
              "license": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "query",
          "model",
          "datum",
          "count",
          "extremes",
          "attribution"
        ]
      },
      "PortListItem": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Tokyo"
          },
          "slug": {
            "type": "string",
            "example": "tokyo"
          },
          "lat": {
            "type": "number",
            "example": 35.648617
          },
          "lon": {
            "type": "number",
            "example": 139.77
          },
          "timezone": {
            "type": "string",
            "example": "Asia/Tokyo"
          },
          "country": {
            "type": "string",
            "example": "Japan"
          },
          "country_slug": {
            "type": "string",
            "example": "japan"
          },
          "code_iso": {
            "type": "string",
            "example": "JP"
          },
          "region": {
            "type": "string",
            "example": "Tokyo"
          }
        },
        "required": [
          "name",
          "slug",
          "lat",
          "lon"
        ]
      },
      "PortsResponse": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "description": "Number of stations in this page."
          },
          "total": {
            "type": "integer",
            "description": "Total number of matching stations."
          },
          "offset": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "ports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortListItem"
            }
          }
        },
        "required": [
          "count",
          "ports"
        ]
      },
      "CountriesResponse": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "example": 185
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "example": "United Kingdom"
                },
                "slug": {
                  "type": "string",
                  "example": "united-kingdom"
                },
                "code_iso": {
                  "type": "string",
                  "example": "GB"
                },
                "station_count": {
                  "type": "integer",
                  "example": 238
                }
              },
              "required": [
                "name",
                "slug",
                "code_iso",
                "station_count"
              ]
            }
          }
        },
        "required": [
          "count",
          "countries"
        ]
      },
      "WeatherCondition": {
        "type": "string",
        "enum": [
          "clear",
          "mostly_clear",
          "partly_cloudy",
          "overcast",
          "fog",
          "freezing_fog",
          "light_drizzle",
          "drizzle",
          "heavy_drizzle",
          "freezing_drizzle",
          "heavy_freezing_drizzle",
          "light_rain",
          "rain",
          "heavy_rain",
          "freezing_rain",
          "heavy_freezing_rain",
          "light_snow",
          "snow",
          "heavy_snow",
          "snow_grains",
          "light_showers",
          "showers",
          "heavy_showers",
          "snow_showers",
          "heavy_snow_showers",
          "thunderstorm",
          "thunderstorm_hail",
          "heavy_thunderstorm_hail",
          "unknown"
        ],
        "description": "Weather condition (derived from WMO weather codes).",
        "example": "partly_cloudy"
      },
      "SeaState": {
        "type": "string",
        "enum": [
          "calm_glassy",
          "calm_rippled",
          "smooth",
          "slight",
          "moderate",
          "rough",
          "very_rough",
          "high",
          "very_high",
          "unknown"
        ],
        "description": "Sea state on the Douglas scale (from wave height).",
        "example": "smooth"
      },
      "WeatherDaily": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "temperature_max_c": {
            "type": "number",
            "nullable": true
          },
          "temperature_min_c": {
            "type": "number",
            "nullable": true
          },
          "precipitation_mm": {
            "type": "number",
            "nullable": true
          },
          "wind_max_kmh": {
            "type": "number",
            "nullable": true
          },
          "uv_index_max": {
            "type": "number",
            "nullable": true
          },
          "condition": {
            "$ref": "#/components/schemas/WeatherCondition"
          },
          "sunrise": {
            "type": "string",
            "description": "Local time HH:MM (empty string when the sun does not rise).",
            "example": "08:51"
          },
          "sunset": {
            "type": "string",
            "description": "Local time HH:MM (empty string when the sun does not set).",
            "example": "19:12"
          }
        },
        "required": [
          "date"
        ]
      },
      "WeatherHourly": {
        "type": "object",
        "properties": {
          "datetime": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-24T06:00:00+00:00"
          },
          "temperature_c": {
            "type": "number",
            "nullable": true
          },
          "feels_like_c": {
            "type": "number",
            "nullable": true
          },
          "humidity_pct": {
            "type": "number",
            "nullable": true
          },
          "precipitation_mm": {
            "type": "number",
            "nullable": true
          },
          "pressure_hpa": {
            "type": "number",
            "nullable": true
          },
          "cloud_cover_pct": {
            "type": "number",
            "nullable": true
          },
          "wind_speed_kmh": {
            "type": "number",
            "nullable": true
          },
          "wind_direction_deg": {
            "type": "number",
            "nullable": true
          },
          "wind_gust_kmh": {
            "type": "number",
            "nullable": true
          },
          "uv_index": {
            "type": "number",
            "nullable": true
          },
          "condition": {
            "$ref": "#/components/schemas/WeatherCondition"
          }
        },
        "required": [
          "datetime"
        ]
      },
      "WeatherResponse": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "timezone": {
            "type": "string",
            "example": "Europe/Paris"
          },
          "days": {
            "type": "integer"
          },
          "source": {
            "type": "string"
          },
          "daily": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WeatherDaily"
            }
          },
          "hourly": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WeatherHourly"
            }
          },
          "port": {
            "$ref": "#/components/schemas/PortRef"
          },
          "cached": {
            "type": "boolean",
            "description": "Present (true) when served from the server-side cache."
          },
          "stale": {
            "type": "boolean",
            "description": "Present (true) when the engine was unavailable and the same day's cached response was served."
          }
        },
        "required": [
          "lat",
          "lon",
          "timezone",
          "days",
          "source",
          "daily",
          "hourly"
        ]
      },
      "MarineDaily": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "wave_height_max_m": {
            "type": "number",
            "nullable": true
          },
          "wave_period_max_s": {
            "type": "number",
            "nullable": true
          },
          "wave_direction_dominant": {
            "type": "number",
            "description": "Degrees.",
            "nullable": true
          },
          "swell_height_max_m": {
            "type": "number",
            "nullable": true
          },
          "swell_period_max_s": {
            "type": "number",
            "nullable": true
          },
          "sea_state": {
            "$ref": "#/components/schemas/SeaState"
          }
        },
        "required": [
          "date"
        ]
      },
      "MarineHourly": {
        "type": "object",
        "properties": {
          "datetime": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-24T06:00:00+00:00"
          },
          "wave_height_m": {
            "type": "number",
            "nullable": true
          },
          "wave_direction_deg": {
            "type": "number",
            "nullable": true
          },
          "wave_period_s": {
            "type": "number",
            "nullable": true
          },
          "swell_height_m": {
            "type": "number",
            "nullable": true
          },
          "swell_direction_deg": {
            "type": "number",
            "nullable": true
          },
          "swell_period_s": {
            "type": "number",
            "nullable": true
          },
          "wind_wave_height_m": {
            "type": "number",
            "nullable": true
          },
          "wind_wave_direction": {
            "type": "number",
            "nullable": true
          },
          "wind_wave_period_s": {
            "type": "number",
            "nullable": true
          },
          "sea_surface_temperature_c": {
            "type": "number",
            "description": "Sea-surface (water) temperature, °C (MUR SST).",
            "nullable": true
          }
        },
        "required": [
          "datetime"
        ]
      },
      "MarineResponse": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "timezone": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "source": {
            "type": "string"
          },
          "daily": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MarineDaily"
            }
          },
          "hourly": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MarineHourly"
            }
          },
          "port": {
            "$ref": "#/components/schemas/PortRef"
          },
          "cached": {
            "type": "boolean",
            "description": "Present (true) when served from the server-side cache."
          },
          "stale": {
            "type": "boolean",
            "description": "Present (true) when the engine was unavailable and the same day's cached response was served."
          }
        },
        "required": [
          "lat",
          "lon",
          "timezone",
          "days",
          "source",
          "daily",
          "hourly"
        ]
      },
      "AstronomyDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "sun": {
            "type": "object",
            "properties": {
              "sunrise": {
                "type": "string",
                "description": "Local time HH:MM.",
                "nullable": true,
                "example": "08:52"
              },
              "sunset": {
                "type": "string",
                "nullable": true,
                "example": "19:13"
              },
              "dawn": {
                "type": "string",
                "description": "Civil dawn.",
                "nullable": true,
                "example": "08:19"
              },
              "dusk": {
                "type": "string",
                "description": "Civil dusk.",
                "nullable": true,
                "example": "19:45"
              },
              "day_length": {
                "type": "string",
                "description": "H:MM.",
                "nullable": true,
                "example": "10:21"
              }
            }
          },
          "moon": {
            "type": "object",
            "properties": {
              "phase": {
                "type": "string",
                "enum": [
                  "new_moon",
                  "waxing_crescent",
                  "first_quarter",
                  "waxing_gibbous",
                  "full_moon",
                  "waning_gibbous",
                  "last_quarter",
                  "waning_crescent"
                ],
                "example": "waxing_gibbous"
              },
              "illumination_pct": {
                "type": "number",
                "example": 97.5
              },
              "age_days": {
                "type": "number",
                "example": 13.3
              },
              "moonrise": {
                "type": "string",
                "nullable": true,
                "example": "01:58"
              },
              "moonset": {
                "type": "string",
                "nullable": true,
                "example": "16:04"
              }
            }
          }
        },
        "required": [
          "date",
          "sun",
          "moon"
        ]
      },
      "AstronomyResponse": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "timezone": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AstronomyDay"
            }
          },
          "port": {
            "$ref": "#/components/schemas/PortRef"
          }
        },
        "required": [
          "lat",
          "lon",
          "timezone",
          "date",
          "days",
          "data"
        ]
      },
      "DatasetError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Weather data temporarily unavailable"
          }
        },
        "required": [
          "error"
        ],
        "description": "Returned in place of a dataset when its engine is unavailable."
      },
      "ConditionsResponse": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "timezone": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "port": {
            "$ref": "#/components/schemas/PortRef"
          },
          "weather": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "daily": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WeatherDaily"
                    }
                  },
                  "hourly": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WeatherHourly"
                    }
                  }
                },
                "required": [
                  "daily",
                  "hourly"
                ]
              },
              {
                "$ref": "#/components/schemas/DatasetError"
              }
            ]
          },
          "marine": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "daily": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/MarineDaily"
                    }
                  },
                  "hourly": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/MarineHourly"
                    }
                  }
                },
                "required": [
                  "daily",
                  "hourly"
                ]
              },
              {
                "$ref": "#/components/schemas/DatasetError"
              }
            ]
          },
          "astronomy": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AstronomyDay"
            }
          },
          "sources": {
            "type": "object",
            "properties": {
              "weather": {
                "type": "string"
              },
              "marine": {
                "type": "string"
              },
              "astronomy": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "lat",
          "lon",
          "timezone",
          "days",
          "sources"
        ]
      },
      "RadarFrame": {
        "type": "object",
        "properties": {
          "time": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-24T17:10:00Z"
          },
          "timestamp": {
            "type": "integer"
          },
          "tile_url": {
            "type": "string",
            "description": "Slippy-map tile URL template with `{z}`, `{x}`, `{y}` placeholders."
          }
        },
        "required": [
          "time",
          "timestamp",
          "tile_url"
        ]
      },
      "RadarResponse": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "generated": {
            "type": "integer",
            "description": "Unix timestamp of the radar index."
          },
          "source": {
            "type": "string",
            "example": "RainViewer (rainviewer.com)"
          },
          "tile_size": {
            "type": "integer",
            "example": 256
          },
          "max_zoom": {
            "type": "integer",
            "example": 7
          },
          "color_scheme": {
            "type": "integer",
            "example": 1
          },
          "frames": {
            "type": "object",
            "properties": {
              "past": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RadarFrame"
                }
              },
              "nowcast": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RadarFrame"
                }
              }
            },
            "required": [
              "past",
              "nowcast"
            ]
          },
          "port": {
            "$ref": "#/components/schemas/PortRef"
          }
        },
        "required": [
          "lat",
          "lon",
          "generated",
          "source",
          "frames"
        ]
      },
      "ObservationsResponse": {
        "type": "object",
        "properties": {
          "port": {
            "$ref": "#/components/schemas/PortRef"
          },
          "station_id": {
            "type": "integer",
            "description": "REFMAR station identifier.",
            "example": 3
          },
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "timezone": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "example": "REFMAR/SHOM (Licence Etalab 2.0)"
          },
          "interval_minutes": {
            "type": "integer",
            "example": 10
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "observations": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "datetime": {
                        "type": "string",
                        "description": "Local time, `YYYY-MM-DDTHH:MM`.",
                        "example": "2026-10-24T02:00"
                      },
                      "height_m": {
                        "type": "number"
                      },
                      "timestamp": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "datetime",
                      "height_m",
                      "timestamp"
                    ]
                  }
                }
              },
              "required": [
                "date",
                "observations"
              ]
            }
          }
        },
        "required": [
          "port",
          "station_id",
          "source",
          "interval_minutes",
          "data"
        ]
      },
      "CurrentsResponse": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "days": {
            "type": "integer"
          },
          "unit": {
            "type": "object",
            "properties": {
              "speed": {
                "type": "string",
                "example": "knots"
              },
              "direction": {
                "type": "string"
              }
            }
          },
          "scope": {
            "type": "string",
            "enum": [
              "open_water",
              "near_shore"
            ],
            "description": "Honesty flag: `open_water` (reliable) or `near_shore` (indicative only)."
          },
          "scope_note": {
            "type": "string"
          },
          "model_point": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "distance_km": {
                "type": "number",
                "description": "Distance from the requested point to the model grid point."
              }
            }
          },
          "hours": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "t": {
                  "type": "string",
                  "description": "Time (UTC), `YYYY-MM-DD HH:MM`.",
                  "example": "2026-10-24 19:00"
                },
                "u_ms": {
                  "type": "number",
                  "description": "Eastward velocity, m/s."
                },
                "v_ms": {
                  "type": "number",
                  "description": "Northward velocity, m/s."
                },
                "speed_kn": {
                  "type": "number",
                  "description": "Speed in knots."
                },
                "dir_deg": {
                  "type": "number",
                  "description": "Direction the current flows towards, degrees."
                }
              },
              "required": [
                "t",
                "speed_kn",
                "dir_deg"
              ]
            }
          },
          "model": {
            "type": "string"
          },
          "attribution": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string",
            "example": "Not for navigation."
          }
        },
        "required": [
          "lat",
          "lon",
          "days",
          "scope",
          "model_point",
          "hours"
        ]
      },
      "HistoryResponse": {
        "type": "object",
        "properties": {
          "port": {
            "$ref": "#/components/schemas/Station"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer"
          },
          "include": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "tides",
                "weather"
              ]
            }
          },
          "tides": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "extremes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TideExtreme"
                }
              }
            }
          },
          "weather": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string"
              },
              "count": {
                "type": "integer"
              },
              "daily": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "temperature_max_c": {
                      "type": "number",
                      "nullable": true
                    },
                    "temperature_min_c": {
                      "type": "number",
                      "nullable": true
                    },
                    "precipitation_mm": {
                      "type": "number",
                      "nullable": true
                    },
                    "wind_max_kmh": {
                      "type": "number",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        },
        "required": [
          "port",
          "date",
          "days",
          "include"
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "spring_tide",
                "storm_warning"
              ]
            }
          },
          "port_slug": {
            "type": "string",
            "nullable": true
          },
          "active": {
            "type": "boolean"
          },
          "failure_count": {
            "type": "integer"
          },
          "last_triggered_at": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "example": "2026-03-28 07:26:42"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "active"
        ]
      },
      "WebhookList": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "limit": {
            "type": "integer",
            "description": "Maximum number of webhooks for your plan."
          },
          "webhooks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          }
        },
        "required": [
          "count",
          "limit",
          "webhooks"
        ]
      },
      "WebhookInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS endpoint receiving deliveries.",
            "example": "https://example.com/webhook"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "spring_tide",
                "storm_warning"
              ]
            },
            "description": "Events to subscribe to (required on create)."
          },
          "port_slug": {
            "type": "string",
            "description": "Station slug to watch.",
            "example": "brest"
          },
          "active": {
            "type": "boolean",
            "description": "Update only: enable/disable the webhook (re-enabling resets the failure count)."
          }
        }
      },
      "WebhookCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "spring_tide",
                "storm_warning"
              ]
            }
          },
          "port_slug": {
            "type": "string",
            "nullable": true
          },
          "secret": {
            "type": "string",
            "description": "HMAC-SHA256 signing secret. Shown only once."
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "secret"
        ]
      },
      "WebhookActionResult": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "example": "Webhook updated."
          },
          "id": {
            "type": "integer"
          }
        },
        "required": [
          "message",
          "id"
        ]
      },
      "WebhookTestResult": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "example": "Test delivery successful."
          },
          "status_code": {
            "type": "integer",
            "description": "HTTP status returned by your endpoint."
          },
          "webhook_id": {
            "type": "integer"
          }
        },
        "required": [
          "message",
          "status_code",
          "webhook_id"
        ]
      },
      "GeoJSONFeatureCollection": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "FeatureCollection"
            ]
          },
          "features": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "Feature"
                  ]
                },
                "properties": {
                  "type": "object",
                  "additionalProperties": true
                },
                "geometry": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "coordinates": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      }
                    }
                  },
                  "additionalProperties": true
                }
              },
              "additionalProperties": true
            }
          }
        },
        "required": [
          "type",
          "features"
        ]
      }
    }
  }
}
