# NOAA Buoy Data API - Live Wave Height, Wind & Water Temp (`neverempty/noaa-buoy-data-api`) Actor

Live observations from NOAA NDBC buoys and coastal stations: wave height, dominant period, wind and gusts, water and air temperature, pressure. Give a station id or a lat,lon point and it finds the nearest buoy. Missing readings stay null, never zero, and every row says how old it is.

- **URL**: https://apify.com/neverempty/noaa-buoy-data-api.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Travel, Developer tools, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.70 / 1,000 buoy observations

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## NOAA Buoy Data API - Live Wave Height, Wind & Water Temp

Live observations from **NOAA NDBC** buoys and coastal stations: **wave height** and period, **wind** and
gusts, **water temperature**, air temperature, pressure, dew point, visibility.

Give it a station id, or just a `lat,lon` point and it finds the nearest station **that is actually publishing right now**.

- **No API key, no scraping of a web page, no proxy.** US government data, public domain.
- **Both unit systems on every row** — metres and feet, m/s and knots, Celsius and Fahrenheit.
- **Latest reading by default**, so one station costs one row. Ask for history when you want it.

### What this returns that the raw NDBC file does not

NDBC publishes a fixed-width text file per station with two header lines and about 45 days of readings.
Everything below is the work this Actor does on top of it.

| Column | Why it matters |
|---|---|
| `ageMinutes`, `observedAt` | **A buoy can keep a file online and stop reporting.** Station 41001 on 2026-08-31 still served a 340 KB file whose newest reading was **32 days old**. Every row says exactly how old it is, in UTC and in minutes, and the run warns when the newest reading is over 6 hours old. |
| Missing values as `null` | NDBC writes `MM` for a missing reading. Turning that into `0` would report a flat calm or no waves. Missing stays missing. |
| Both unit systems | `waveHeightMeters` / `waveHeightFeet`, `windSpeedMs` / `windSpeedKnots`, `waterTemperatureC` / `waterTemperatureF`, and the same for gusts, air temperature and dew point. |
| `stationName`, `stationOwner`, `stationType`, `latitude`, `longitude` | The observation file has none of this. It is joined from NDBC's active-station list. |
| `requestedPoint`, `distanceKm` | Give `"36.8,-122.4"` and you get the nearest station and how far away it is. |
| Only stations with data | NDBC lists 1,351 active stations but **only 943 publish a standard meteorological file** (DART tsunami buoys, TAO moorings and many fixed sites never do). A naive "nearest station" lands on one of the other 409 about 7% of the time and returns nothing. Chicago (41.88,-87.61) resolves here to CNII2 at 2.67 km with live data, not to the empty station 1.58 km away. |
| Column names read from the header | The parser matches NDBC's own column header line instead of counting characters, so a change in the file's layout cannot silently shift wind into the wave column. |

### Input

| Field | Type | Default | Meaning |
|---|---|---|---|
| `locations` | array | `["44013"]` | NDBC station ids (`44013`, `TPLM2`) and/or points (`"36.8,-122.4"`). |
| `latestOnly` | boolean | `true` | One row per station: the newest reading. Turn off for history. |
| `hours` | integer | `24` | How far back when `latestOnly` is off. 1 to 1100 (NDBC keeps about 45 days). NDBC buoys report about every 10 minutes; fixed coastal stations usually once an hour. |
| `maxRowsPerStation` | integer | `200` | Upper bound per station so a long window cannot run up a bill. |
| `maxDistanceKm` | integer | `300` | If the nearest station with data is further than this, the location is reported and **not charged**. |
| `maxRetries` | integer | `4` | Retries when NDBC does not answer. |

### Output

```json
{
  "rowType": "observation",
  "stationId": "44013",
  "stationName": "BOSTON 16 NM East of Boston, MA",
  "stationType": "buoy",
  "latitude": 42.346,
  "longitude": -70.651,
  "observedAt": "2026-08-31T16:00:00.000Z",
  "ageMinutes": 20,
  "windDirectionDegrees": 70,
  "windSpeedMs": 2,
  "windSpeedKnots": 3.9,
  "waveHeightMeters": 0.6,
  "waveHeightFeet": 1.97,
  "dominantWavePeriodSeconds": 4,
  "waterTemperatureC": 20.5,
  "waterTemperatureF": 68.9,
  "pressureHpa": 1021.2
}
```

### What it does when something is wrong

- **A station that publishes no standard meteorological file** — pulled for winter, in for servicing, or a
  DART/TAO/oil-rig station that never publishes this format → a `station-offline` row saying so,
  **not charged**, and the run does not fail.
- **A point with no station near it** → a `no-station-nearby` row with the distance, **not charged**.
  Denver resolves to a station 1,296 km away, which is past the default `maxDistanceKm` of 300.
- **NDBC not answering, or answering with something that is not an observation file** → an error row with
  the reason, and the run fails if nothing at all could be read. It is never reported as "no observations".
- **A window with no readings in it** → one row saying so, with the newest reading NDBC does have. Not charged.
- **Lines that do not match the column header** are counted and reported, never mixed into the rows.
- A run that hits the maximum total charge you set says so in the log and in `RUN_BUDGET_STOP`.
- Failures are collected in `RUN_ISSUES` and offline stations in `RUN_OFFLINE_STATIONS` in the run's key-value store.

### Measured

Run on 2026-09-01 with 8 inputs — 3 station ids, 1 offline station, 3 points (coastal, inland-lake city,
landlocked), 1 unusable string: **5 observation rows + 1 station-offline row + 1 no-station-nearby row in
14.6 seconds**. 943 of the 1,351 listed stations were publishing. `36.8,-122.4` resolved to Monterey buoy
46042 at 1.61 km, `41.88,-87.61` to CNII2 at 2.67 km. East Hatteras (41001) was flagged: its newest reading
was 781 hours old.

The build is covered by 58 automated checks, and 61 deliberate defects were injected one at a time to
confirm those checks actually catch them.

### Billing

Pay per event: one charge per observation row delivered. `station-offline`, `no-station-nearby`, error rows
and the "no observations in this window" row are **not** charged. With the default `latestOnly`, one station costs
one row per run.

### Support

Questions and bug reports: use the **Issues** tab on this Actor. Include the input you ran and the run id.

Data source: [NOAA National Data Buoy Center](https://www.ndbc.noaa.gov/) — official US government data,
public domain. This Actor is not affiliated with NOAA. Marine observations are not a substitute for an
official marine forecast or warning.

# Actor input Schema

## `locations` (type: `array`):

NDBC station ids such as "44013" or "TPLM2", and/or points written as "lat,lon" such as "42.3,-70.6". A point is resolved to the nearest station that actually publishes data right now - about 30% of the listed stations do not - and the distance in km comes back with every row. If none of the values are usable the run fails instead of quietly returning a default station.

## `latestOnly` (type: `boolean`):

On (default) returns one row per station: the newest reading. Turn it off to get a history - NDBC keeps about 45 days of readings, usually one every 10 minutes, so a long window is a lot of rows.

## `hours` (type: `integer`):

How far back to go when "Latest observation only" is off. 1 to 1100 (NDBC keeps about 45 days). NDBC buoys report about every 10 minutes; fixed coastal stations usually report once an hour.

## `maxRowsPerStation` (type: `integer`):

Upper bound per station, so a long window cannot run up a bill. The newest rows are kept and the run says in the log when it trimmed.

## `maxRetries` (type: `integer`):

How many times to retry when NDBC does not answer. A station that publishes no standard meteorological file is reported as offline rather than retried.

## `maxDistanceKm` (type: `integer`):

If the nearest station that publishes data is further away than this, the location is reported as "no station nearby" and is not charged. Inland points would otherwise resolve to a buoy a thousand kilometres away.

## Actor input object example

```json
{
  "locations": [
    "44013"
  ],
  "latestOnly": true,
  "hours": 24,
  "maxRowsPerStation": 200,
  "maxRetries": 4,
  "maxDistanceKm": 300
}
```

# Actor output Schema

## `results` (type: `string`):

One row per buoy observation, plus one row per station that is offline or could not be read.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/noaa-buoy-data-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/noaa-buoy-data-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call neverempty/noaa-buoy-data-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/noaa-buoy-data-api"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/R9a5TrvsJB10g2ZJD/builds/TKjg7IDGGfo9Pie55/openapi.json
