Reference

API Reference

Base URL https://api.theweatheragent.com. All responses are JSON. Every response that carries relayed data includes an attribution string and a disclaimer field.

Data disclaimer. The Weather Agent relays and normalizes data from third-party upstream providers, and names the ones that actually served a response in that response's attribution field. Data is provided "as is", without warranty of any kind, and may be delayed, incomplete or inaccurate. It is not a substitute for official marine forecasts, notices to mariners, or your own judgment. Independently verify all data against official sources before acting on it — especially any decision affecting personal safety or navigation. Data is relayed from upstream sources and must be independently verified — it is not the sole basis for decisions affecting safety of life or property at sea.

Authentication

Send your API key in the X-API-Key header (an Authorization: Bearer <key> header also works). Keys are issued once, at email verification, and stored only as a hash — we cannot recover a lost key, but you can issue another.

X-API-Key: wa_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Quota is counted per account, not per key. Holding several keys does not grant additional allowance.

What counts as a call

OutcomeCounts as
200 from a data endpoint1 call
Cache hit (same request inside the same model cycle)0 calls
4xx / 4290 calls

Free tier is 10 calls/day per account, resetting at 00:00 UTC. Exceeding it returns 429 with a machine-readable quota object and a Retry-After header.

MCP server — free

Connect your own AI agent over the Model Context Protocol at no cost. The MCP server is free to use and needs no separate plan or signup beyond an API key. Data calls it makes count toward your normal API allowance — the same per-account daily quota as REST. Metadata lookups do not.

ToolWhat it doesCounts toward quota
get_point_forecastMarine/weather point with a value per sourceYes — 1 call
get_tide_extremesTide highs/lows for a dayYes — 1 call when available
generate_fishing_reportReport built from relayed dataYes — base + 1 per ladder leg
list_sourcesSource catalog and licencesNo — metadata
get_usageQuota readoutNo — metadata
get_disclaimerThe disclaimer, attribution and plan tableNo — metadata
# stdio WEATHERAGENT_API_KEY=*** npx weatheragent-mcp --transport stdio # HTTP / SSE (server) WEATHERAGENT_API_KEY=*** \ WEATHERAGENT_MCP_TRANSPORT=http \ WEATHERAGENT_MCP_PORT=8790 \ npx weatheragent-mcp

Auth is required. Free does not mean anonymous — the server needs your API key so data calls are attributed to your account and quota can be enforced. There is no separate MCP price and no MCP-only upsell.

Ladder legs stay a paid feature via MCP exactly as over REST. The plan is read from your account, not from the MCP server's configuration, so it cannot be self-granted locally.

Agents do not read our docs. Because any third-party agent may consume this server, the disclaimer is a mandatory field in every data-bearing tool response rather than free prose — it is structurally impossible to return relayed data without it. Data is relayed from upstream sources and must be independently verified; it is not the sole basis for decisions affecting safety of life or property at sea.

Endpoints

GET/healthNo auth

Liveness and configuration readout. Useful for status pages and uptime checks. Also carries the per-source cycle block — the upstream model cycle a downstream consumer keys its own cache on. Public and quota-free: polling it costs no calls.

curl https://api.theweatheragent.com/health
{ "ok": true, "service": "weatheragent-api", "version": "0.1.0-p0", "schema_version": "2026-09-12", "sources_enabled": ["ndbc", "nws"], "auth_mode": "on", "cache_bound": true, "db_bound": true, "cycle": { "as_of": "2026-09-13T13:04:48.453Z", "sources": { "ndbc": { "cadence_minutes": 10, "cycle": "2026-09-13T13:00:00.000Z", "next": "2026-09-13T13:10:00.000Z", "seconds_to_next": 311 }, "nws": { "cadence_minutes": 60, "cycle": "2026-09-13T13:00:00.000Z", "next": "2026-09-13T14:00:00.000Z", "seconds_to_next": 3311 } } }, "time": "2026-09-13T13:04:48.453Z" }

The cycle block. Each enabled source reports the upstream cycle boundary currently in force, the next boundary, and seconds_to_next, so a consumer can schedule its own refresh instead of guessing. This is public and unauthenticated on purpose: polling it must not cost quota, and it is readable before your first keyed data call.

The block comes from the same function the point routes bucket their caches with, so it can never advertise a cycle the cache did not use. meta.cache_cycle on a point or batched-point response is the per-request equivalent — key your own artifacts on either; they agree.

FieldMeaning
as_ofServer time the snapshot was computed.
sources.<id>.cycleISO cycle boundary in force now — the exact value used in cache keys.
sources.<id>.nextISO boundary of the next cycle.
sources.<id>.seconds_to_nextSeconds from as_of to next.
sources.<id>.cadence_minutesWidth of that source's cycle bucket (NDBC observations ~10 min, NWS hourly grids 60 min).
StatusMeaning
200Service up. Inspect cache_bound / db_bound for dependency health.
GET/v2/marine/pointAuth

Marine and weather values at a coordinate, returned as one value per source per timestamp plus a spread/agreement statistic. Sources are never averaged into a single number.

lat required
Latitude in [-90, 90].
lng required
Longitude in [-180, 180].
params
Comma-separated. Defaults to waveHeight,wavePeriod,windSpeed. Unknown ids are rejected with 400, never silently dropped.
hours
Forecast window, 1–240. Default 48.
units
metric (default) or imperial.

Available params: waveHeight, wavePeriod, waveDirection, swellHeight, swellPeriod, swellDirection, windSpeed, windDirection, windGust, airTemperature, waterTemperature, pressure, currentSpeed, currentDirection, visibility, precipitation, cloudCover.

curl "https://api.theweatheragent.com/v2/marine/point?lat=27.97&lng=-83.36&params=waveHeight,windSpeed&hours=6" \ -H "X-API-Key: wa_live_..."
{ "request": { "lat": 27.97, "lng": -83.36, "params": ["waveHeight","windSpeed"], "hours": 6, "units": "metric" }, "hours": [ { "time": "2026-09-11T23:00:00.000Z", "waveHeight": { "ndbc": 0.4 }, "windSpeed": { "nws": 2.572 } } ], "meta": { "sources_ok": ["ndbc","nws"], "sources_failed": [], "cache": "miss", "stale": false, "freshness": { "newest_valid": "2026-09-19T05:00:00.000Z", "horizon_hours": 172, "newest_observation": "2026-09-12T00:00:00.000Z", "observation_age_minutes": 61 }, "spread": { "waveHeight": { "min":0.4,"max":0.4,"median":0.4,"agree":1,"n":1 } }, "units": "metric", "attribution": "...", "disclaimer": "..." } }
StatusMeaning
200Data returned. Check meta.sources_ok / sources_failed and meta.stale.
400Bad lat, lng, hours, units, or an unknown params id.
401Missing or invalid API key.
422No enabled source can serve the requested parameters.
429Daily quota exhausted. See the 429 contract below.
502Every resolved source failed. No partial or invented value is returned.

Reading the data: spread.agree counts the sources inside an agreement band of max(param band, 10% of the median). A forecast's newest_valid is in the future by design — use newest_observation for how current the observations are. Read the spread where a spread exists: wind and atmospheric parameters are covered by two sources, so spread.n is 2 and agreement is meaningful. Wave parameters come from a single source (NDBC buoy observations), so spread.n is 1 — there is no consensus to read, and a short ladder delta sits inside one station's coverage rather than across several. Read meta.notices, meta.nearest_station and meta.spread.<param>.n before treating any wave value as more than one instrument's reading.

GET/v2/marine/pointsAuth

The same per-source values as /v2/marine/point, resolved for up to 24 coordinates in one call — a batched ladder. Every point shares one params, hours and units. Each point is resolved and reported independently, so one point failing never fails the batch.

points required
Semicolon-separated list of lat,lng pairs — a comma within a point, a semicolon between points. Maximum 24 entries; 25 is rejected with 400.
params
Comma-separated, shared by every point. Same ids, same default and same 400-on-unknown-id rule as /v2/marine/point.
hours
Shared forecast window, 1–240. Default 48.
units
metric (default) or imperial, shared.
curl "https://api.theweatheragent.com/v2/marine/points?points=27.97,-83.36;27.50,-83.20&params=waveHeight,windSpeed&hours=2" \ -H "X-API-Key: wa_live_..."
{ "request": { "points": [ { "lat": 27.97, "lng": -83.36 }, { "lat": 27.5, "lng": -83.2 } ], "params": ["waveHeight","windSpeed"], "hours": 2, "units": "metric" }, "points": [ { "request": { "lat": 27.97, "lng": -83.36, "params": ["waveHeight","windSpeed"], "hours": 2, "units": "metric" }, "hours": [ { "time": "2026-09-13T11:00:00.000Z", "waveHeight": { "ndbc": 0.4 }, "windSpeed": { "nws": 2.058 } }, { "time": "2026-09-13T12:00:00.000Z", "waveHeight": { "ndbc": 0.4 }, "windSpeed": { "nws": 2.058 } } ], "meta": { "sources_ok": ["ndbc","nws"], "sources_failed": [], "cache": "miss", "cache_cycle": "2026-09-13T13:00:00.000Z", "nearest_station": { "id": "42036", "name": "WEST TAMPA", "distance_km": 118, "params": ["windSpeed","windDirection","waterTemperature"], "missing_requested": [] }, "upstream_ms": 1355, "spread": { "waveHeight": { "min":0.4,"max":0.4,"median":0.4,"agree":1,"n":1 }, "windSpeed": { "min":2.058,"max":2.058,"median":2.058,"agree":1,"n":1 } }, "stale": false, "units": "metric" } }, { "request": { "lat": 27.5, "lng": -83.2, "params": ["waveHeight","windSpeed"], "hours": 2, "units": "metric" }, "error": "all_sources_failed", "message": "Every resolved source failed for this point. No data is returned rather than a partial or invented value.", "failures": [ { "source": "ndbc", "error": "no NDBC station within range" }, { "source": "nws", "error": "NWS points 404" } ] } ], "batch": { "points_requested": 2, "points_ok": 1, "points_failed": 1, "cache": "miss", "cache_hits": 0, "quota_consumed": 1, "upstream_ms": 1658 }, "schema_version": "2026-09-12", "attribution": "...", "disclaimer": "...", "request_id": "..." }

Inline failure. A point that cannot be served appears in place in the points array as { request, error, message, failures }, with a per-source failures list, instead of failing the whole call. The HTTP status stays 200 and every other point is still returned. A point that resolves only some sources is a normal result — read meta.sources_ok / meta.sources_failed. Only when every source fails does the point become an inline error.

Each entry is the single-point shape. A successful entry is { request, hours, meta } — exactly what /v2/marine/point returns — so the same parsing code can read either route. A failed entry keeps the same request block and carries error instead of hours.

Which station answered. meta.nearest_station (and meta.stations.ndbc) names the instrument that produced the observation: id, name, distance_km, the params it actually reported, and missing_requested — the requested parameters this source could serve but that instrument does not measure. A non-empty missing_requested means no sensor, not no-data: e.g. a wind-only Gulf buoy asked for waveHeight reports missing_requested: ["waveHeight"] instead of an empty, undiagnosable result. When wave parameters are requested the resolver prefers a station that measures seas over a nearer one that does not, so read distance_km before assuming the closest station answered. Sources with no station concept (the NWS grid) contribute nothing here and leave nearest_station null.

meta.notices — stated limitations. An array of plain-language caveats that apply to this response, so a human or an agent can qualify the data instead of presenting thin coverage as solid. Two are generated today: a parameter that came from only one source (“… came from a single source — there is no cross-source spread to read…”, which is the normal case for wave parameters), and a station-based answer whose station is more than 50 km from the requested point. It is empty when nothing needs qualifying, and it is never used to soften a failure — a failure is an error, not a notice.

Billing. Only points that return fresh data are metered. A fresh point costs 1 call; a cache hit costs 0 and a failed point costs 0. batch.quota_consumed is the authoritative count for the call, and batch.cache reports miss, hit or mixed across the points. A batch of 24 fresh points is 24 calls, not 1. As with /v2/marine/point, key your own artifacts on meta.cache_cycle (see /health) — both come from the same function.

batch fieldMeaning
points_requestedEntries parsed from points.
points_okPoints that returned data.
points_failedPoints with an inline error.
cachemiss, hit or mixed across the batch.
cache_hitsPoints served from cache.
quota_consumedCalls billed — fresh data-bearing points only.
upstream_msWall time spent resolving the batch upstream.
StatusMeaning
200Batch processed. Inspect batch.points_ok / points_failed and each point's inline error.
400Bad grammar, a coordinate out of range, more than 24 points, or an unknown params id. Reported as points entry N: ....
401Missing or invalid API key. Same gate as /v2/marine/point.
429Daily quota exhausted. See the 429 contract below.
GET/v2/tide/extremes/pointAuth

Planned (P1). High/low tide times and heights for a day, relative to MSL, units stated. Documented here so the surface is known; the MCP tide tool reports it as not-yet-available rather than inventing data.

GET/v2/sourcesNo auth

The source catalog: id, dataset, resolution, cadence, area, licence, whether commercial use is permitted, and whether attribution is required. Use this to render correct attribution.

{ "sources": [ { "id": "ndbc", "name": "NOAA NDBC", "transport": "obs", "licence": "US Government work — public domain", "commercial_ok": true, "attribution_required": false }, { "id": "nws", "name": "NOAA / National Weather Service", "transport": "relay", "licence": "US Government work — public domain" } ], "note": "Every response carries one value per source per timestamp..." }

UK Met Office is deliberately excluded: its global wave product is CC-BY-SA (share-alike), which is incompatible with our terms through MVP.

GET/v2/disclaimerNo auth

Returns the current disclaimer and attribution strings. If you cache or re-serve our data, fetch these and attach them — they change when the source set changes.

{ "disclaimer": "The Weather Agent relays and normalizes data from third-party upstream providers...", "attribution": "Contains data from NOAA (National Data Buoy Center buoy observations; National Weather Service gridpoint forecasts) — US Government work — public domain. Provided by The Weather Agent." }
POST/v2/auth/signupNo auth

Create an account and send an email verification link. The free tier is gated on a verified address. Request JSON: { "email": "[email protected]", "device_fp": "optional" }.

curl -X POST https://api.theweatheragent.com/v2/auth/signup \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]"}'
HTTP/2 202 { "status": "verification_sent", "message": "Check your email to activate your API key.", "account_id": "...", "plan": "free" }
StatusMeaning
202Verification email sent (or re-sent).
400Missing or invalid email address.
409That address already has a verified account.
429Too many new accounts created from this connection recently.
503Accounts store unavailable.

Note: outbound email delivery is deferred, so verification currently runs on a log transport and the response includes a dev_verify_url. That field disappears once a real mail provider is configured.

GET/v2/auth/verifyNo auth

Consume a verification token and issue an API key. Tokens are single-use and expire after 24 hours (the optional query parameters token and web are accepted; web=1 renders a short HTML confirmation page instead of JSON).

curl "https://api.theweatheragent.com/v2/auth/verify?token=..."
{ "api_key": "wa_live_...", "key_prefix": "wa_live_fd452a", "warning": "Store this key now. It is shown once and stored only as a hash.", "account": { "id": "...", "email": "[email protected]", "plan": "free" }, "quota": { "limit": 10, "resets_at": "2026-09-13T00:00:00.000Z", "basis": "per_account" } }
StatusMeaning
200Verified; key issued. This is the only time the key is shown.
400token missing, invalid_token, token_expired, or token_already_used.
GET/v2/usageAuth (optional)

Quota readout: used, limit, resets_at, plan, plus the definition of what counts as a call.

{ "quota": { "used": 3, "limit": 10, "resets_at": "2026-09-13T00:00:00.000Z", "plan": "free" }, "counts_as": { "ok_200": 1, "cache_hit": 0, "error_4xx": 0, "report_base": 1, "report_ladder_leg": 1 } }
GETPOST/v2/keysAuth

List your keys, or issue an additional one. A key's secret is returned only at creation.

GET /v2/keys → { "keys": [ { "key_prefix": "wa_live_fd452a", "scopes": "data,report,mcp", "revoked_at": null } ] } POST /v2/keys → { "api_key": "wa_live_...", "warning": "Store this key now..." }
StatusMeaning
401Missing or invalid API key.

Quota is per account: additional keys do not add allowance.

DELETE/v2/keys/:idAuth

Revoke a key. Revocation is immediate and the key stops resolving on the next request.

StatusMeaning
200Revoked.
401Missing or invalid API key.
404No such active key on your account.

The 429 contract

Quota exhaustion is machine-readable. The body carries a quota object and the response carries a Retry-After header in seconds.

HTTP/2 429 Retry-After: 83000 { "error": "quota_exceeded", "message": "Daily quota exhausted (10/10). Quota is per account and resets at 00:00 UTC. A cache hit does not consume quota.", "quota": { "used": 10, "limit": 10, "resets_at": "...", "plan": "free" }, "request_id": "..." }

Reports and metering

A fishing report is metered per leg: the base report is 1 call, tide adds 1, and each ladder leg you request adds 1. Ladder legs are optional and user-specified, so a report's total depends on what you ask for — there is no fixed report cost. Ladder legs require a paid plan.

Errors

ShapeNotes
{ "error": "...", "message": "...", "request_id": "..." }Errors also carry attribution and disclaimer.
404Body echoes the method actually received, e.g. No route for GET /v2/auth/signup. A wrong-method request reports the method Hono saw, which is the fastest way to diagnose a probe that sent the wrong verb.
Attribution. Generated from the sources this deployment actually serves, so it can never name a provider that did not contribute. Today all wired sources are NOAA: Contains data from NOAA (National Data Buoy Center buoy observations; National Weather Service gridpoint forecasts) — US Government work — public domain. Provided by The Weather Agent.

Disclaimer. The Weather Agent relays and normalizes data from third-party upstream providers. Data is provided "as is", without warranty of any kind, and may be delayed, incomplete or inaccurate. It is not a substitute for official marine forecasts, notices to mariners, or your own judgment. Independently verify all data against official sources before acting on it — especially any decision affecting personal safety or navigation.

© 2026 The Weather Agent