← Developer API

API reference

Generated from the OpenAPI spec, version 1.0.0-beta

Derived marine forecasts for third-party developers (keyed, free beta).

Point forecasts, model agreement, the automatic model choice, tropical-cyclone tracks and US federal buoy observations, computed from Predict Sea's published forecast releases by the same code the website runs.

Forecast guidance only — not for navigation. Automated, unedited model output that may be late, incomplete or wrong; not an official forecast or warning. Always check the official marine forecasts and warnings for your area. Every answer carries this notice and the attribution its data requires: show both to your users (API terms: https://www.predictsea.com/api-terms.html).

Authentication: the X-Api-Key header only. A key in the query string is refused (400 key_in_query). Get a key at https://api.predictsea.com/signup (free beta; self-serve keys start at 30 requests a minute and 1,000 weight a day; more on request).

Units: wind and gusts in knots, directions in degrees true the wind or waves come FROM, wave height in metres, period in seconds, pressure in hPa (mean sea level), cloud in %, rain in mm/h, temperature in °C, visibility in km, distances in nautical miles, times ISO 8601 UTC. A value the model does not provide is null.

Versioning: v1 changes additively (new fields, new endpoints). A breaking change is a new /v2, with at least 90 days of overlap and notice by email.

Basics

Base URLhttps://api.predictsea.com/v1
AuthenticationThe X-Api-Key header. psk_ + 40 characters. Header only — never in a URL, a web page or an app binary.
Specificationhttps://api.predictsea.com/v1/openapi.yaml (OpenAPI 3.1.0, version 1.0.0-beta)
Contactpredictsea@outlook.com
LicenceData under its upstream licences (see each answer's attribution); service under the API terms

Endpoints

GET /v1/catalog

Models, regions, basins and layers of the served release

Weight 1. Lists each model's run, freshness (fresh / stale / old, relative to its publication cadence) and whether it has point data (points).

Authentication: the X-Api-Key header.

Responses

StatusMeaning
200The catalogue.
400missing_param, invalid_param, unknown_param, unknown_model, model_without_points, unknown_scope, key_in_query.
401missing_key, invalid_key.
403key_revoked.
429rate_limited (per-minute bucket), quota_exceeded (daily quota; resets 00:00 UTC), too_many_failures (failed key attempts from your address). Header: Retry-After.
503no_release (Retry-After 60), feed_stale (storms / obs, Retry-After 600), shutting_down (Retry-After 5), overloaded (the service is busy, Retry-After 2). Header: Retry-After.

Answer: data

Inside the envelope (CatalogData).

FieldTypeMeaning
data.modelsarray of object
data.models[].idstring
data.models[].namestring
data.models[].providerstring
data.models[].globalboolean
data.models[].domainobject | nulllat_min lat_max lon_min lon_max of a regional model.
data.models[].paramsarray of string
data.models[].extrasarray of string
data.models[].pointsbooleanHas point data: usable with /point and /compare.
data.models[].grid_degarray of number | null
data.models[].initstring | null (date-time)
data.models[].cadence_hinteger | null
data.models[].age_hnumber | null
data.models[].freshnessstringOne of fresh, stale, old.
data.models[].forecast_hoursarray of integer
data.regionsarray of object
data.regions[].namestring
data.regions[].titlestring
data.regions[].lat_minnumber
data.regions[].lat_maxnumber
data.regions[].lon_minnumber
data.regions[].lon_maxnumber
data.basinsarray of object
data.basins[].idstring
data.basins[].namestring
data.basins[].regionsarray of string
data.layersarray of string

GET /v1/point

Point forecast — every hour of one model at a position

One model's full run at a position, sampled from its point tile by the normative rule of the data contract (the numbers the website's point table shows). Default model: the tiled wind model the website's automatic choice picks where the position opens (picked: auto). Weight 1.

Authentication: the X-Api-Key header.

Parameters

NameRequiredTypeDefaultDescription
latyesstring, pattern ^-?\d{1,3}(\.\d{1,6})?$Latitude, −90..90, plain decimal (at most 6 decimals, no exponent).
lonyesstring, pattern ^-?\d{1,3}(\.\d{1,6})?$Longitude, −180..360 (folded to −180..180; the answer echoes the folded value).
modelnostring, pattern ^[a-z0-9_]{1,16}$A model id with points true in /catalog (default — Auto).
max_leadnointeger, 0–384Only rows / times up to this many hours after the run's start (0..384).
fromnostring: run, nowrunrun (default): every hour of the run; now: hours valid at or after now − 3 h.

Responses

StatusMeaning
200The series.
400missing_param, invalid_param, unknown_param, unknown_model, model_without_points, unknown_scope, key_in_query.
401missing_key, invalid_key.
403key_revoked.
404no_data: no point data at this position (land, or no wind, pressure or waves); auto: no model serves the layer there. Metered.
429rate_limited (per-minute bucket), quota_exceeded (daily quota; resets 00:00 UTC), too_many_failures (failed key attempts from your address). Header: Retry-After.
503no_release (Retry-After 60), feed_stale (storms / obs, Retry-After 600), shutting_down (Retry-After 5), overloaded (the service is busy, Retry-After 2). Header: Retry-After.

Answer: data

Inside the envelope (PointData).

FieldTypeMeaning
data.latnumber
data.lonnumberFolded to −180..180.
data.modelstring
data.pickedstringOne of auto, request.
data.unitsobject of string values
data.seriesarray of object
data.series[].validstring (date-time)
data.series[].fhrintegerHours after the run's start.
data.series[].wind_ktnumber | null10 m wind, kt, 0.1.
data.series[].gust_ktnumber | null
data.series[].wind_dirinteger | null° true the wind comes from, 0..359.
data.series[].wave_mnumber | nullSignificant wave height, m, 0.01.
data.series[].wave_dirinteger | null° true the waves come from.
data.series[].wave_period_snumber | null
data.series[].mslp_hpanumber | null
data.series[].cloud_pctinteger | null
data.series[].rain_mmhnumber | null
data.series[].temp_cnumber | null2 m air temperature.
data.series[].vis_kmnumber | null

GET /v1/compare

Model agreement at a position

One metric across the fresh global models, on 6-hourly valid times, with the spread per time, its agreement level (hi below thresholds.hi, md below thresholds.md, else lo) and the website's verdict. Weight = the number of models compared.

Authentication: the X-Api-Key header.

Parameters

NameRequiredTypeDefaultDescription
latyesstring, pattern ^-?\d{1,3}(\.\d{1,6})?$Latitude, −90..90, plain decimal (at most 6 decimals, no exponent).
lonyesstring, pattern ^-?\d{1,3}(\.\d{1,6})?$Longitude, −180..360 (folded to −180..180; the answer echoes the folded value).
metricnostring: wind, gust, dir, wave, preswindwind / gust (kt), dir (° from, circular spread), wave (m), pres (hPa).
modelsnostringall (every fresh model carrying the metric), or 2–13 comma-separated model ids with point data. Default: up to 5, GFS / ECMWF / ICON first while fresh.
max_leadnointeger, 0–384Only rows / times up to this many hours after the run's start (0..384).

Responses

StatusMeaning
200The comparison.
400missing_param, invalid_param, unknown_param, unknown_model, model_without_points, unknown_scope, key_in_query.
401missing_key, invalid_key.
403key_revoked.
429rate_limited (per-minute bucket), quota_exceeded (daily quota; resets 00:00 UTC), too_many_failures (failed key attempts from your address). Header: Retry-After.
503no_release (Retry-After 60), feed_stale (storms / obs, Retry-After 600), shutting_down (Retry-After 5), overloaded (the service is busy, Retry-After 2). Header: Retry-After.

Answer: data

Inside the envelope (CompareData).

FieldTypeMeaning
data.latnumber
data.lonnumber
data.metricstring
data.unitstring
data.basestring | nullThe model whose run sets the valid times (/point's default here).
data.step_hconst 6
data.validarray of string (date-time)
data.modelsarray of object
data.models[].idstring
data.models[].namestring
data.models[].initstring | null (date-time)
data.models[].age_hnumber | null
data.models[].freshnessstring
data.models[].valuesarray of number | null
data.spreadarray of number | null
data.agreementarray of string | null
data.thresholdsobject
data.thresholds.hinumber
data.thresholds.mdnumber
data.verdictobject
data.verdict.kindstringOne of single, agree, early, diverge.
data.verdict.daysnumber | null
data.verdict.withinstring
data.verdict.textstring
data.excludedarray of object
data.excluded[].idstring
data.excluded[].reasonconst "old"
data.excluded[].age_hnumber | null
data.deferredarray of stringFresh models the default cap left out (models=all compares them).
data.regional_not_comparedarray of stringRegional models covering the position (never compared).

GET /v1/auto

The model the website picks for a layer in an area

Give exactly one of region, basin, or lat + lon (the smallest region holding the position, else the world). point_model is the tiled model a point forecast rides there (/point's default). model may have no point data (HRRR, NAM, ICON-EU, EWAM, RTOFS). Weight 1.

Authentication: the X-Api-Key header.

Parameters

NameRequiredTypeDefaultDescription
layeryesstring: wind, gust, swell, swell1, pres, cloud, precip, temp, vis, sst, current
regionnostring, pattern ^[a-z0-9_]{1,16}$A region name from /catalog.
basinnostring, pattern ^[a-z0-9_]{1,16}$A basin id from /catalog.
latnostring, pattern ^-?\d{1,3}(\.\d{1,6})?$Latitude −90..90 (with lon).
lonnostring, pattern ^-?\d{1,3}(\.\d{1,6})?$Longitude −180..360 (with lat).
validnostring, pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?Z$The valid time wanted (UTC), within now − 24 h .. now + 16 days. Default now.

Responses

StatusMeaning
200The pick.
400missing_param, invalid_param, unknown_param, unknown_model, model_without_points, unknown_scope, key_in_query.
401missing_key, invalid_key.
403key_revoked.
404no_data: no point data at this position (land, or no wind, pressure or waves); auto: no model serves the layer there. Metered.
429rate_limited (per-minute bucket), quota_exceeded (daily quota; resets 00:00 UTC), too_many_failures (failed key attempts from your address). Header: Retry-After.
503no_release (Retry-After 60), feed_stale (storms / obs, Retry-After 600), shutting_down (Retry-After 5), overloaded (the service is busy, Retry-After 2). Header: Retry-After.

Answer: data

Inside the envelope (AutoData).

FieldTypeMeaning
data.layerstring
data.scopeobject
data.scope.typestringOne of world, basin, region.
data.scope.idstring | null
data.scope.titlestring
data.validstring (date-time)
data.modelstring
data.whyobject
data.why.rulestringOne of currents, world_freshest, finest_regional, preferred_global.
data.why.kmnumber | nullEffective grid spacing over the region, km.
data.why.altstring | nullThe global model a regional pick beat.
data.why.alt_kmnumber | null
data.runobject
data.run.initstring | null (date-time)
data.run.endsstring | null (date-time)
data.run.freshnessstring
data.point_modelstring | null

GET /v1/storms

Active tropical cyclones — guidance, not an official warning

NHC advisories and ECMWF open-data tracks with canonical keys (kt, radius34_nm, classification; quadrant radii r34_nm / r50_nm / r64_nm [NE, SE, SW, NW] where given). 503 feed_stale when the feed is older than 12 h (an empty list would read "no storms"). Weight 1.

Authentication: the X-Api-Key header.

Parameters

NameRequiredTypeDefaultDescription
basinnostring, pattern ^[A-Z]{2}$The storm's basin (AL, EP, CP, WP, IO, SH …).
membersnostring: 0, 101: include the ECMWF ensemble (ensemble, members: [lon, lat, tau_h] polylines).

Responses

StatusMeaning
200The storms.
400missing_param, invalid_param, unknown_param, unknown_model, model_without_points, unknown_scope, key_in_query.
401missing_key, invalid_key.
403key_revoked.
429rate_limited (per-minute bucket), quota_exceeded (daily quota; resets 00:00 UTC), too_many_failures (failed key attempts from your address). Header: Retry-After.
503no_release (Retry-After 60), feed_stale (storms / obs, Retry-After 600), shutting_down (Retry-After 5), overloaded (the service is busy, Retry-After 2). Header: Retry-After.

Answer: data

Inside the envelope (StormsData).

FieldTypeMeaning
data.generated_atstring (date-time)
data.countinteger
data.attributionstring | nullThe feed's own credit line.
data.stormsarray of object
data.storms[].idstring
data.storms[].namestring
data.storms[].basinstring
data.storms[].sourcestringOne of NHC, ECMWF.
data.storms[].classificationstring
data.storms[].categoryinteger
data.storms[].latnumber
data.storms[].lonnumber
data.storms[].intensity_ktnumber | null
data.storms[].pressure_hpanumber | null
data.storms[].trackarray of object
data.storms[].forecastarray of object
data.storms[].conearray of array of number
data.storms[].model_tracksarray of object

GET /v1/obs

Latest buoy and shore observations — US federal stations only

Give exactly one of id, bbox, or lat + lon (with radius_nm). Only stations a US federal agency owns (NDBC, NOS, NWS, GLERL, USACE, FAA, NPS, NOAA programs) are served. Sorted by distance around a position, else by id. An unknown and a non-federal id both answer 404 not_found. Weight 1.

Authentication: the X-Api-Key header.

Parameters

NameRequiredTypeDefaultDescription
idnostring, pattern ^[A-Za-z0-9]{3,8}$A station id (case-insensitive).
bboxnostringw,s,e,n in degrees, at most 60° × 60°; w > e crosses the dateline.
latnostring, pattern ^-?\d{1,3}(\.\d{1,6})?$
lonnostring, pattern ^-?\d{1,3}(\.\d{1,6})?$
radius_nmnointeger, 1–30060Nautical miles around lat + lon.
limitnointeger, 1–20050

Responses

StatusMeaning
200The stations.
400missing_param, invalid_param, unknown_param, unknown_model, model_without_points, unknown_scope, key_in_query.
401missing_key, invalid_key.
403key_revoked.
404not_found: an unknown or a non-federal station id.
429rate_limited (per-minute bucket), quota_exceeded (daily quota; resets 00:00 UTC), too_many_failures (failed key attempts from your address). Header: Retry-After.
503no_release (Retry-After 60), feed_stale (storms / obs, Retry-After 600), shutting_down (Retry-After 5), overloaded (the service is busy, Retry-After 2). Header: Retry-After.

Answer: data

Inside the envelope (ObsData).

FieldTypeMeaning
data.generated_atstring | null (date-time)
data.feed_stalebooleanThe feed is older than 1 h.
data.countinteger
data.stationsarray of object
data.stations[].idstring
data.stations[].namestring
data.stations[].ownerstring
data.stations[].typestring
data.stations[].latnumber
data.stations[].lonnumber
data.stations[].timestring (date-time)
data.stations[].age_mininteger | null
data.stations[].wdirnumber | null
data.stations[].wspd_ktnumber | nullAs measured at the anemometer (4–5 m on buoys), not a 10 m wind.
data.stations[].gust_ktnumber | null
data.stations[].wvht_mnumber | null
data.stations[].dpd_snumber | null
data.stations[].pres_hpanumber | null
data.stations[].atmp_cnumber | null
data.stations[].wtmp_cnumber | null
data.stations[].distance_nmnumber

GET /v1/openapi.yaml

This document (no key needed, not metered)

Authentication: none (no key needed).

Responses

StatusMeaning
200OpenAPI 3.1. Type application/yaml.

The envelope

Every answer is this object; the endpoint's own answer is in data. Show notice and the attribution entries with the data (API terms §4-5).

FieldTypeMeaning
apiconst "predictsea/v1"
genstring | nullThe forecast release served (YYYYMMDDTHHMMSS-xxxxxx).
initstring | null (date-time)Issue time of the oldest input the data uses (a model's run, a feed's generated_at).
age_hnumber | nullHours since init, 1 decimal.
noticestringNot-for-navigation notice: show it with the data.
attributionarray of Attribution
attribution[].providerstring | nullFor example ECMWF, NOAA/NCEP, DWD, ECCC/MSC, NOAA/NHC, NOAA/NDBC.
attribution[].modelsarray of string
attribution[].creditstring
attribution[].licensestring | null
attribution[].license_urlstring | null
attribution[].terms_urlstring | null
attribution[].source_urlstring | null
attribution[].copyrightstring | null
attribution[].noticestring | null
attribution[].disclaimerstring | null
attribution[].notestring
modificationstring | nullHow Predict Sea modifies the model output (absent for obs).
dataobject
metaMeta
meta.request_idstring
meta.generated_atstring (date-time)
meta.weightinteger
meta.quotaQuota | null
meta.quota.per_minuteinteger
meta.quota.day_limitintegerWeight units per UTC day.
meta.quota.day_usedinteger
meta.quota.day_remaininginteger
meta.quota.resets_atstring (date-time)

Errors

An error answer carries error in place of data, with the notice and meta:

FieldTypeMeaning
errorobject
error.statusinteger
error.codestringOne of the codes below.
error.paramstring
error.messagestring
CodeStatus
missing_param400
invalid_param400
unknown_param400
unknown_model400
model_without_points400
unknown_scope400
key_in_query400
missing_key401
invalid_key401
key_revoked403
not_found404
no_data404
method_not_allowed—
rate_limited429
quota_exceeded429
too_many_failures429
no_release503
feed_stale503
shutting_down503
overloaded503
internal—

A status of “—”: no operation lists that code (it can answer any request); the guide's error table gives every code with its status. On 429 and 503, wait Retry-After seconds.

The raw specification: https://api.predictsea.com/v1/openapi.yaml.