# Find nearest buoy to coordinates Source: https://docs.thebuoy.app/api-reference/buoys/find-nearest-buoy-to-coordinates /api-reference/openapi.json get /buoys/nearest Find the closest active buoy(s) to a given latitude and longitude within a maximum distance. Use the `limit` parameter to control how many results are returned (default: 1, max: 20). # Get a specific buoy reading Source: https://docs.thebuoy.app/api-reference/buoys/get-a-specific-buoy-reading /api-reference/openapi.json get /buoys/{buoy_id}/readings/{reading_id} Get detailed information about a specific reading from a buoy. # Get buoy details Source: https://docs.thebuoy.app/api-reference/buoys/get-buoy-details /api-reference/openapi.json get /buoys/{buoy_id} Get detailed information about a specific buoy. The buoy can be identified by ID or slug (friendly ID). # Get chart data for a buoy Source: https://docs.thebuoy.app/api-reference/buoys/get-chart-data-for-a-buoy /api-reference/openapi.json get /buoys/{buoy_id}/readings/chart Returns comprehensive chart data including readings, forecasts, and tide levels. # Get historical readings for a buoy Source: https://docs.thebuoy.app/api-reference/buoys/get-historical-readings-for-a-buoy /api-reference/openapi.json get /buoys/{buoy_id}/readings Get paginated historical readings for a buoy. Supports filtering by date range. **Pagination:** Default 20 per page, minimum 1, maximum 100 per page. # Get last readings for multiple buoys Source: https://docs.thebuoy.app/api-reference/buoys/get-last-readings-for-multiple-buoys /api-reference/openapi.json get /buoys/last_readings Bulk fetch the latest reading for a list of buoys by ID. By default returns the first 3 IDs supplied. Pass `limit` to fetch up to 100 buoys in a single request. **Tip:** To get last readings for all buoys in a country in one call, use `GET /buoys?country=FR` instead — the index response already includes `last_reading` for each buoy. # Get seasonal buoy reading series Source: https://docs.thebuoy.app/api-reference/buoys/get-seasonal-buoy-reading-series /api-reference/openapi.json get /buoys/{buoy_id}/readings/series Returns grouped year-over-year seasonal time series for a single buoy without pagination. Supports Météo-France and Candhis buoys and is intended for server-side consumers that need chart-ready seasonal comparison data. Metric availability is source-specific: Météo-France supports wave, water temperature, and wind metrics; Candhis supports wave metrics only. For Candhis, `significient_height` maps to H13D (H1/3), `period` maps to TH13D (significant wave period), and `peak_period` maps to TP (peak period). When `start_year` and `end_year` are omitted, the endpoint returns the last 10 complete seasons. Explicit year ranges can request up to 13 seasons, for example `2013..2025`. # List all buoys Source: https://docs.thebuoy.app/api-reference/buoys/list-all-buoys /api-reference/openapi.json get /buoys Get a paginated list of buoys with optional filtering by geographic bounds, source, or active status. Returns buoys in a standardized V2 format with pagination metadata. # Search buoys Source: https://docs.thebuoy.app/api-reference/buoys/search-buoys /api-reference/openapi.json get /buoys/search Search for buoys by name, slug, or source identifier. Returns matching buoys with minimal information. # Search for reading closest to a specific time Source: https://docs.thebuoy.app/api-reference/buoys/search-for-reading-closest-to-a-specific-time /api-reference/openapi.json get /buoys/{buoy_id}/readings/search Find the reading closest to a target datetime within a tolerance window. Useful for finding conditions at a specific past time. # Get all-in-one conditions (V2) Source: https://docs.thebuoy.app/api-reference/conditions/get-all-in-one-conditions-v2 /api-reference/openapi.json get /conditions Returns combined forecast, reading, and tide data for a location in a standardized V2 format. This endpoint combines: - Wave forecast (height, period, direction, energy) - Wind forecast (speed, direction) - Swell forecast (height, period, direction) - Nearest buoy reading (if available within 50km) - Tide information (height, direction, next change) **Improvements over V1:** - ✅ Standardized `{status, data, meta}` response format - ✅ Structured JSON data (not human-readable string) - ✅ Includes nearby buoy/weather station information - ✅ Better error handling - ✅ Optional `spot_id` parameter for enhanced context - ✅ Forecast metadata (update times, sources) # API Reference Source: https://docs.thebuoy.app/api-reference/introduction Buoy endpoints — list, search, readings, and charts. This reference covers the buoy endpoints. ## Base URL All endpoints are served from: ``` https://api.thebuoy.app/v2 ``` ## Methods All endpoints are `GET`. ## Authentication All endpoints except `GET /buoys/search` and `GET /buoys/nearest` require a Bearer token: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` See [Authentication](/authentication) for details. ## Response format Every response uses a consistent envelope: ```json theme={null} { "status": "success", "data": { ... }, "meta": { "timestamp": "2026-03-27T09:00:00Z", "page": 1, "per_page": 50, "total_pages": 4 } } ``` Error responses: ```json theme={null} { "error": "error_code", "message": "Human-readable description" } ``` ## Error codes | HTTP status | `error` value | Meaning | | ----------- | --------------------- | ------------------------------- | | `400` | `bad_request` | Missing or malformed parameters | | `401` | `unauthorized` | Invalid or missing API key | | `404` | `resource_not_found` | Resource doesn't exist | | `422` | `validation_failed` | Parameter validation error | | `429` | `rate_limit_exceeded` | Hourly quota exceeded | ## Pagination List endpoints accept `page` and `per_page` parameters. Pagination metadata is always in `meta`: ```json theme={null} "meta": { "page": 2, "per_page": 50, "total_pages": 10, "timestamp": "2026-03-27T09:00:00Z" } ``` ## Units | Measurement | Unit | | ----------------- | --------------------------------- | | Wave height | metres (m) | | Wave period | seconds (s) | | Wave direction | degrees (°), clockwise from north | | Wind speed | km/h | | Temperature | °C | | Distance (radius) | kilometres (km) | # List available satellites/missions Source: https://docs.thebuoy.app/api-reference/satellite-passes/list-available-satellitesmissions /api-reference/openapi.json get /buoys/satellites Discovery endpoint listing the satellite missions you can filter passes by, with each mission's pass count and metadata. Use a returned `slug` as the `mission` filter on `GET /buoys/satellite_passes`. Mission `description` is localized (English/French). Select the language with `?locale=` or the `Accept-Language` header; defaults to English. # List satellite altimeter passes Source: https://docs.thebuoy.app/api-reference/satellite-passes/list-satellite-altimeter-passes /api-reference/openapi.json get /buoys/satellite_passes List recorded satellite altimeter passes over virtual buoys. Filter to a single satellite with `mission` — a slug returned by `GET /buoys/satellites` (e.g. `cfosat`). An unknown slug returns `404`. # Authentication Source: https://docs.thebuoy.app/authentication All v2 API requests require an API key passed as a Bearer token. ## Getting an API Key API access is currently by invitation. To request credentials, email [thomas@thebuoy.app](mailto:thomas@thebuoy.app). Once your request is approved, you'll receive an API key. **Store it securely — it's shown only once and cannot be retrieved later.** If you lose it, we can generate a new one, which immediately invalidates the old one. If your key is compromised, contact support to rotate it. ## Using Your API Key Include your API key in every request using the `Authorization` header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` ### Header authentication (recommended) ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys?country=FR" ``` ### Query parameter (alternative) If you can't set headers, pass the key as a query parameter: ```bash theme={null} curl "https://api.thebuoy.app/v2/buoys?country=FR&api_key=YOUR_API_KEY" ``` Prefer the `Authorization` header — query parameters appear in server logs and browser history. ## Public Endpoints Two endpoints do not require authentication and are available to anyone: | Endpoint | Description | | --------------------------- | ------------------------------------ | | `GET /api/v2/buoys/search` | Search buoys by name or identifier | | `GET /api/v2/buoys/nearest` | Find the nearest buoy to coordinates | ## Authentication Errors ```json theme={null} { "error": "unauthorized", "message": "API key required" } ``` ```json theme={null} { "error": "unauthorized", "message": "Invalid API key" } ``` ## Rate Limit Headers Every authenticated response includes these headers so you can track your usage: ``` X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 987 X-RateLimit-Reset: 1735326000 ``` | Header | Description | | ----------------------- | ------------------------------------ | | `X-RateLimit-Limit` | Your hourly request quota | | `X-RateLimit-Remaining` | Requests remaining this hour | | `X-RateLimit-Reset` | Unix timestamp when the quota resets | When your quota is exceeded, the API returns `429 Too Many Requests`: ```json theme={null} { "error": "rate_limit_exceeded", "message": "You have exceeded 1000 requests per hour", "retry_after": 1847, "reset_at": "2026-03-27T15:00:00Z" } ``` See the [Rate Limits guide](/guides/rate-limits) for details on planning around quotas. # Fetching Buoys by Country Source: https://docs.thebuoy.app/guides/country-buoys Get all active buoys for a country — with their latest readings — in a single API call. ## Overview `GET /buoys?country=XX` returns every active buoy in a country with its latest reading — in one call. ## Getting all French buoys ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys?country=FR" ``` Pass any [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code. France has roughly 25–35 active buoys, so all results fit in the default page. **Response structure:** ```json theme={null} { "status": "success", "data": { "buoys": [ { "id": 12, "name": "Anglet", "lat": 43.4832, "lng": -1.5586, "source": "Candhis", "source_identifier": "64002", "slug": "anglet", "last_reading_time": "2026-03-27T08:00:00Z", "readings_count": 142300, "last_reading": { "significient_height": 1.8, "maximum_height": 2.4, "period": 9.5, "direction": 285, "water_temperature": 14.2, "time": "2026-03-27T08:00:00Z" }, "timezone": "Europe/Paris" } ], "count": 28 }, "meta": { "page": 1, "per_page": 500, "total_pages": 1, "timestamp": "2026-03-27T09:00:00Z" } } ``` When `?country=` is set, the per-page cap increases to 500 (from the default 100) since the geographic scope already constrains the result set. ## Building a cron job Here's a complete cron job pattern to collect the latest readings for all French buoys every 30 minutes: ```python Python theme={null} import requests import json from datetime import datetime API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.thebuoy.app/v2" def collect_france_buoy_readings(): headers = {"Authorization": f"Bearer {API_KEY}"} response = requests.get( f"{BASE_URL}/buoys", params={"country": "FR"}, headers=headers, timeout=30, ) response.raise_for_status() data = response.json() buoys = data["data"]["buoys"] collected_at = datetime.utcnow().isoformat() readings = [ { "buoy_id": b["id"], "buoy_name": b["name"], "lat": b["lat"], "lng": b["lng"], "source": b["source"], "timezone": b.get("timezone"), "collected_at": collected_at, "reading": b.get("last_reading"), } for b in buoys if b.get("last_reading") ] print(f"Collected {len(readings)} readings from {len(buoys)} buoys") return readings if __name__ == "__main__": readings = collect_france_buoy_readings() # Save to your database or message queue here print(json.dumps(readings[0], indent=2)) ``` ```javascript Node.js theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.thebuoy.app/v2"; async function collectFranceBuoyReadings() { const response = await fetch(`${BASE_URL}/buoys?country=FR`, { headers: { Authorization: `Bearer ${API_KEY}` }, }); if (!response.ok) { throw new Error(`API error: ${response.status}`); } const { data } = await response.json(); const collectedAt = new Date().toISOString(); return data.buoys .filter((b) => b.last_reading) .map((b) => ({ buoyId: b.id, buoyName: b.name, lat: b.lat, lng: b.lng, source: b.source, timezone: b.timezone, collectedAt, reading: b.last_reading, })); } collectFranceBuoyReadings() .then((readings) => { console.log(`Collected ${readings.length} readings`); // Save to your database here }) .catch(console.error); ``` ```ruby Ruby theme={null} require "net/http" require "json" require "uri" API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.thebuoy.app/v2" def collect_france_buoy_readings uri = URI("#{BASE_URL}/buoys") uri.query = URI.encode_www_form(country: "FR") req = Net::HTTP::Get.new(uri) req["Authorization"] = "Bearer #{API_KEY}" http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true response = http.request(req) raise "API error: #{response.code}" unless response.is_a?(Net::HTTPSuccess) data = JSON.parse(response.body) collected_at = Time.now.utc.iso8601 data["data"]["buoys"] .select { |b| b["last_reading"] } .map do |b| { buoy_id: b["id"], buoy_name: b["name"], lat: b["lat"], lng: b["lng"], source: b["source"], timezone: b["timezone"], collected_at: collected_at, reading: b["last_reading"] } end end readings = collect_france_buoy_readings puts "Collected #{readings.length} readings" puts JSON.pretty_generate(readings.first) ``` ## Cron schedule Buoy readings are typically updated every **30 minutes**, so 30 minutes is a reasonable polling interval. Polling more often returns duplicates. ```bash theme={null} # crontab — run every 30 minutes */30 * * * * /usr/bin/python3 /path/to/collect_buoys.py >> /var/log/buoy_collector.log 2>&1 ``` ## Handling missing readings Some buoys may temporarily have no reading (e.g., maintenance, transmission gaps). The `last_reading` field will be `null` in those cases. Always guard against this: ```python theme={null} readings = [b for b in buoys if b.get("last_reading") is not None] ``` ## Supported countries Any [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code works. Currently active networks include: | Code | Country | Primary sources | | ---- | ------------- | --------------------- | | `FR` | France | Candhis, Météo France | | `ES` | Spain | Puertos del Estado | | `PT` | Portugal | SNIRH | | `US` | United States | NOAA/NDBC | | `IS` | Iceland | Vegagerðin | Use `GET /api/v2/countries` to get the full list of countries with active buoys. # Bulk Last Readings Source: https://docs.thebuoy.app/guides/last-readings Efficiently fetch the latest reading for a known list of buoys. ## When to use this endpoint `GET /buoys/last_readings` is best when: * You already have a list of specific buoy IDs you care about (e.g. stored in your own database) * You want only reading data, without buoy metadata, for a minimal payload * You need targeted polling for a small, fixed set of buoys Don't have IDs yet? Use [`GET /buoys?country=FR`](/guides/country-buoys) — it returns buoys with readings in one call, and is the right starting point for building your ID list. ## Basic usage ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys/last_readings?ids=12,45,78" ``` By default, only the **first 3 IDs** in your list are returned. To fetch more, pass a `limit`: ```bash theme={null} # Fetch up to 100 buoys at once curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys/last_readings?ids=12,45,78,91,34&limit=5" ``` ## Parameters | Parameter | Type | Default | Max | Description | | --------- | ------- | -------- | ----- | --------------------------------------- | | `ids` | string | required | — | Comma-separated or array-style buoy IDs | | `limit` | integer | `3` | `100` | How many IDs from the list to process | **Array-style IDs** also work: ```bash theme={null} "?ids[]=12&ids[]=45&ids[]=78&limit=3" ``` ## Response ```json theme={null} { "status": "success", "data": { "buoys": [ { "id": 12, "name": "Anglet", "lat": 43.4832, "lng": -1.5586, "source": "Candhis", "last_reading": { "significient_height": 1.8, "maximum_height": 2.4, "period": 9.5, "direction": 285, "water_temperature": 14.2, "time": "2026-03-27T08:00:00Z" } } ], "missing_ids": [999] }, "meta": { "timestamp": "2026-03-27T09:00:00Z" } } ``` ### `missing_ids` Any IDs you requested that don't exist in the database are returned in `missing_ids`. This lets you detect stale IDs in your application without additional lookups. ## Payload design The endpoint returns only the fields needed for reading collection. Forecast fields (`tide_forecast`, `marine_forecast`, `weather_forecast`) are excluded to keep payloads small, even at 100 buoys per request. ## Polling 30+ buoys If you have more than 100 buoys, chunk the ID list and make multiple calls. Each call counts as one request against your rate limit: ```python theme={null} import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.thebuoy.app/v2" def fetch_last_readings(buoy_ids: list[int], chunk_size: int = 100) -> list[dict]: headers = {"Authorization": f"Bearer {API_KEY}"} all_buoys = [] for i in range(0, len(buoy_ids), chunk_size): chunk = buoy_ids[i : i + chunk_size] ids_param = ",".join(map(str, chunk)) response = requests.get( f"{BASE_URL}/buoys/last_readings", params={"ids": ids_param, "limit": len(chunk)}, headers=headers, timeout=15, ) response.raise_for_status() all_buoys.extend(response.json()["data"]["buoys"]) return all_buoys # Example: 150 buoy IDs → 2 requests my_buoy_ids = list(range(1, 151)) readings = fetch_last_readings(my_buoy_ids) print(f"Got readings for {len(readings)} buoys") ``` ## Choosing between country filter and last\_readings | Scenario | Recommended endpoint | | ------------------------------------------- | ---------------------------------------------------- | | First run — discover all France buoys | `GET /buoys?country=FR` | | Periodic poll — you already have IDs stored | `GET /buoys/last_readings?ids=...&limit=100` | | Small fixed list (≤ 3 buoys) | `GET /buoys/last_readings?ids=...` (no limit needed) | | Need buoy name, coords, and timezone too | `GET /buoys?country=FR` (all metadata included) | # Rate Limits Source: https://docs.thebuoy.app/guides/rate-limits Understand your request quota and handle 429 responses gracefully. ## Default limits Every API key has a default quota of **1,000 requests per hour**. The quota resets at the top of each clock hour (e.g. 14:00 → 15:00 UTC). Need a higher limit? Email [thomas@thebuoy.app](mailto:thomas@thebuoy.app). ## Rate limit headers Every authenticated response includes three headers: ``` X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 987 X-RateLimit-Reset: 1711548000 ``` | Header | Type | Description | | ----------------------- | -------------- | --------------------------------- | | `X-RateLimit-Limit` | integer | Your total hourly quota | | `X-RateLimit-Remaining` | integer | Requests left in the current hour | | `X-RateLimit-Reset` | Unix timestamp | When the quota window resets | ## When you exceed the limit If `X-RateLimit-Remaining` reaches 0, the next request returns: **HTTP 429 Too Many Requests** ```json theme={null} { "error": "rate_limit_exceeded", "message": "You have exceeded 1000 requests per hour", "retry_after": 1847, "reset_at": "2026-03-27T15:00:00Z" } ``` The response also includes a `Retry-After` header with the same value in seconds. ## Handling 429s in your code ```python Python theme={null} import requests import time def api_request_with_retry(url, headers, params=None, max_retries=3): for attempt in range(max_retries): response = requests.get(url, headers=headers, params=params, timeout=15) if response.status_code == 429: retry_after = int(response.json().get("retry_after", 60)) print(f"Rate limited. Retrying in {retry_after}s...") time.sleep(retry_after) continue response.raise_for_status() return response.json() raise Exception("Max retries exceeded") ``` ```javascript Node.js theme={null} async function apiRequestWithRetry(url, headers, params, maxRetries = 3) { const fullUrl = new URL(url); if (params) { Object.entries(params).forEach(([k, v]) => fullUrl.searchParams.set(k, v) ); } for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(fullUrl.toString(), { headers }); if (response.status === 429) { const body = await response.json(); const retryAfter = body.retry_after ?? 60; console.log(`Rate limited. Retrying in ${retryAfter}s...`); await new Promise((r) => setTimeout(r, retryAfter * 1000)); continue; } if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); } throw new Error("Max retries exceeded"); } ``` ## Planning your request budget For a cron job collecting French buoy readings every 30 minutes: | Action | Calls | Frequency | Calls / hour | | ----------------------- | ----- | ------------ | ------------ | | `GET /buoys?country=FR` | 1 | Every 30 min | **2** | That's just **2 requests per hour** — well within any quota. Even if you poll every 10 minutes, you'd only use 6 requests/hour for the entire French buoy network. For larger setups polling multiple countries or using `last_readings` in batches: | Action | Calls per run | Runs / hour | Calls / hour | | --------------------------------------------- | ------------- | ----------- | ------------ | | 3 countries × 1 call | 3 | 2 | **6** | | 200 buoys via `last_readings` (chunks of 100) | 2 | 2 | **4** | | All of the above | — | — | **\~10** | 1,000 requests/hour covers most monitoring use cases. ## Tips to stay within limits `GET /buoys?country=FR` returns all buoys with readings in **one call**. Using `last_readings` in batches for the same data costs multiple calls. Always start with the country filter. The list of buoys in a country doesn't change often. Fetch it once, store the IDs, and use `last_readings` for subsequent polls. Only re-sync the list weekly or when you see unexpected `missing_ids`. If you have multiple jobs polling the API, offset their schedules so they don't all fire at the same time and spike your usage. Read the headers on each response. If `remaining` is low, slow down before hitting the limit rather than handling 429s reactively. # Satellite Observations Source: https://docs.thebuoy.app/guides/satellite-observations Query wave data from satellite missions — CFOSAT, Jason-3, Sentinel-3, and more — alongside in-situ buoys. ## What are satellite observations? Satellite radar instruments — altimeters on most missions, a Ku-band scatterometer (SWIM) on CFOSAT — measure significant wave height along their orbital tracks, extending coverage to open ocean where no buoy reaches. Each satellite overpass is grouped into a **pass** — one orbital segment over the network, with its own time window, geographic bounds, and a count of the observations it produced. Passes are attributed to the **mission** that produced them (e.g. CFOSAT), so you can filter to a single satellite. Satellite observations surface through the buoy-namespaced endpoints below. ## Available missions Each pass is linked to one mission. The current set: | Mission | Agency | Instrument | | ------------ | ----------------------------- | --------------- | | CFOSAT | CNES / CNSA | SWIM | | Jason-3 | NASA / CNES / EUMETSAT / NOAA | Poseidon-3B | | Sentinel-3A | ESA / Copernicus | SRAL | | Sentinel-3B | ESA / Copernicus | SRAL | | Sentinel-6A | ESA / EUMETSAT / NASA / NOAA | Poseidon-4 | | SWOT nadir | NASA / CNES | Nadir altimeter | | Saral/AltiKa | ISRO / CNES | AltiKa | | CryoSat-2 | ESA | SIRAL | | HaiYang-2B | CNSA / NSOAS | Radar altimeter | | HaiYang-2C | CNSA / NSOAS | Radar altimeter | Fetch the live list from the discovery endpoint below. ## Discover missions `GET /buoys/satellites` lists every mission with its metadata and a count of recorded passes. Use a returned `slug` as the filter value for satellite passes. ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys/satellites" ``` ```json theme={null} { "status": "success", "data": { "satellites": [ { "slug": "cfosat", "name": "CFOSAT", "agency": "CNES / CNSA", "instrument": "SWIM", "description": "Joint French–Chinese mission measuring ocean surface wind and directional wave spectra with the SWIM scatterometer.", "image_url": null, "pass_count": 220 } ], "count": 12 }, "meta": { "timestamp": "2026-05-27T09:00:00Z" } } ``` ### Localized descriptions Mission `description` is available in **English and French**. Select the language with `?locale=` (or the `Accept-Language` header); it defaults to English. ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys/satellites?locale=fr" ``` ```json theme={null} { "slug": "cfosat", "name": "CFOSAT", "description": "Mission franco-chinoise mesurant le vent de surface et les spectres directionnels des vagues à l'aide du diffusiomètre SWIM." } ``` ## Filter passes by mission `GET /buoys/satellite_passes` returns recent passes. Pass `mission` (a slug from the discovery endpoint) to get only that satellite's passes. ```bash theme={null} # Only CFOSAT passes curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys/satellite_passes?mission=cfosat" ``` An unknown slug returns `404 resource_not_found`. Omit `mission` to get passes from all satellites. ### Parameters | Parameter | Type | Default | Description | | ------------- | ----------------- | ------- | ---------------------------------------------------------- | | `mission` | string | — | Filter to one mission, by `slug` (see `/buoys/satellites`) | | `active_only` | boolean | `true` | Only passes that have fresh, displayable buoy readings | | `since` | string (ISO 8601) | — | Only passes at or after this timestamp | | `limit` | integer | `20` | Max passes to return (max `100`) | ### Response ```json theme={null} { "status": "success", "data": { "satellite_passes": [ { "id": 1842, "name": "Bay of Biscay pass · CFOSAT · 14 May 10:00 UTC", "external_id": "cfosat-...", "satellite_pass_id": "cfosat-...", "platform": "CFOSAT", "mission": { "slug": "cfosat", "name": "CFOSAT" }, "started_at": "2026-05-14T10:00:00Z", "ended_at": "2026-05-14T10:20:00Z", "buoy_count": 6, "observation_count": 18, "bounds": { "south": 43.0, "west": -5.0, "north": 47.0, "east": -1.0 } } ], "count": 1 }, "meta": { "timestamp": "2026-05-27T09:00:00Z" } } ``` Each pass embeds a compact `mission` reference (`slug` + `name`); `platform` is the raw label stored on the pass. Use `bounds` to place the pass on a map and `buoy_count` / `observation_count` to gauge its density. ## Typical flow Call `GET /buoys/satellites` once to learn the available `slug`s and pass counts. Call `GET /buoys/satellite_passes?mission=` to pull that satellite's recent passes. Use each pass's `bounds`, time window, and counts to render coverage or feed a dashboard. # The Buoy API Source: https://docs.thebuoy.app/index Real-time ocean buoy data for developers. Wave heights, periods, water temperatures, and more from ocean buoy networks worldwide. ## What is the Buoy API? The Buoy API serves real-time and historical wave data from ocean buoys worldwide. Data is sourced from **Candhis**, **Météo France**, **NOAA/NDBC**, **Sofar Ocean**, and others. Every response uses a consistent `{status, data, meta}` envelope. Make your first API call in under 5 minutes. Understand how API key authentication works. Fetch all active buoys for a country in one call. Query wave data from satellite missions like CFOSAT and Jason-3. Browse the full endpoint documentation. ## Key Features Access the latest wave height, period, direction, and water temperature from hundreds of active buoys worldwide. Readings are typically updated every 30 minutes. Filter buoys by ISO country code (`?country=FR`), geographic bounding box, or a coordinates + radius query. Each response includes pagination metadata. Retrieve paginated historical readings per buoy with date range filters, or search for the reading closest to a specific timestamp. Build dashboards with optimized chart payloads: wave height, period, forecasts, and tide levels as aligned time series. Go beyond fixed buoys with open-ocean wave measurements from satellite missions — CFOSAT, Jason-3, Sentinel-3, SWOT, and more. Discover missions via `GET /buoys/satellites` and filter passes by mission. See the [Satellite Observations guide](/guides/satellite-observations). Every response includes `X-RateLimit-*` headers. Requests exceeding your hourly quota return a `429` with a `retry_after` value so your client can back off gracefully. ## API at a Glance | Property | Value | | ------------------ | --------------------------------- | | Base URL | `https://api.thebuoy.app/v2` | | Auth | `Authorization: Bearer ` | | Format | JSON (`application/json`) | | Default rate limit | 1,000 requests / hour | | Version | v2 (production) | ## Data Sources | Network | Region | Type | | ------------ | ------- | -------------------- | | Candhis | France | Wave buoys | | Météo France | France | Wave buoys | | NOAA / NDBC | USA | Wave & weather buoys | | Sofar Ocean | Global | Spotter buoys | | Vegagerðin | Iceland | Wave buoys | *** API access is currently by invitation. To request access, email [thomas@thebuoy.app](mailto:thomas@thebuoy.app) # Quickstart Source: https://docs.thebuoy.app/quickstart Make your first Buoy API call in under 5 minutes. ## Prerequisites You need an API key. If you don't have one yet, see [Authentication](/authentication) to request access. ## Step 1 — List active buoys Fetch a paginated list of active buoys. Each buoy includes its latest reading — no follow-up call needed. ```bash cURL theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys?per_page=5" ``` ```python Python theme={null} import requests headers = {"Authorization": "Bearer YOUR_API_KEY"} r = requests.get( "https://api.thebuoy.app/v2/buoys", params={"per_page": 5}, headers=headers, ) r.raise_for_status() print(r.json()) ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.thebuoy.app/v2/buoys?per_page=5", { headers: { Authorization: "Bearer YOUR_API_KEY" } } ); const data = await response.json(); console.log(data); ``` **Response:** ```json theme={null} { "status": "success", "data": { "buoys": [ { "id": 12, "name": "Anglet", "lat": 43.4832, "lng": -1.5586, "source": "Candhis", "source_identifier": "64002", "slug": "anglet", "last_reading_time": "2026-03-27T08:00:00Z", "readings_count": 142300, "last_reading": { "significient_height": 1.8, "maximum_height": 2.4, "period": 9.5, "direction": 285, "water_temperature": 14.2, "time": "2026-03-27T08:00:00Z" }, "timezone": "Europe/Paris" } ], "count": 312 }, "meta": { "page": 1, "per_page": 5, "total_pages": 63, "timestamp": "2026-03-27T09:00:00Z" } } ``` ## Step 2 — Filter by country To get all active buoys in a specific country, pass the ISO 3166-1 alpha-2 country code. ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys?country=FR" ``` Use this to bootstrap a cron job over all French buoys. See the [Country Buoys guide](/guides/country-buoys) for a complete pattern. ## Step 3 — Get targeted last readings If you already know the IDs of the buoys you care about, use `last_readings` to fetch only those — up to 100 at a time: ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.thebuoy.app/v2/buoys/last_readings?ids=12,45,78&limit=3" ``` **Response:** ```json theme={null} { "status": "success", "data": { "buoys": [ { "id": 12, "name": "Anglet", "lat": 43.4832, "lng": -1.5586, "source": "Candhis", "last_reading": { "significient_height": 1.8, "maximum_height": 2.4, "period": 9.5, "direction": 285, "water_temperature": 14.2, "time": "2026-03-27T08:00:00Z" } } ], "missing_ids": [] }, "meta": { "timestamp": "2026-03-27T09:00:00Z" } } ``` ## Step 4 — Find the nearest buoy No IDs? Find the closest buoy to any coordinates: ```bash theme={null} curl "https://api.thebuoy.app/v2/buoys/nearest?lat=43.48&lng=-1.56&max_distance=50" ``` `nearest` and `search` are public endpoints — no API key required. ## What's next? Full cron job pattern for collecting all readings from a country. Efficiently poll specific buoys with the bulk readings endpoint. Plan your request budget and handle 429 responses. Full endpoint documentation with interactive playground.