Community. Driven. Weather. Data. | Always Ad-Free | Developer FriendlyChecking statusDiscord
Become a supporter
Sign in
API

Observations

Measured surface conditions at a point with every field credited to its own station, plus the stations themselves, their latest readings and their recent history.


Base URL: https://api.wxalerts.org

What real instruments are reporting right now: airfield observations, buoys and coastal stations, and the backyard stations of CWOP and amateur radio. Nothing here is a model. Every number says which station it came from, how far away that station is, and how old the reading was.

GET /conditions?lat=33.215&lon=-97.133&units=us
GET /conditions?q=norman+ok
GET /stations?bbox=-97.9,32.4,-96.3,33.4&units=us
GET /stations/metar:KDTO
GET /stations/metar:KDTO/history?hours=6

No key needed. Browsers block calls from other websites (CORS allows only wxalerts.org), so call it from a server, script or app. The answers are cached for 60 seconds; see rate limits.

For a screen that wants six flat numbers and a forecast beside them, use GET /weather. Its current block is /conditions reduced to one value per field and one station name.

Where the readings come from

Every station has an id with its network in front of it, and a source naming the feed it arrives on.

source id looks like what it is trust_tier
metar metar:KDTO ASOS and AWOS at airfields, via the Aviation Weather Center. Reports hourly, more often when the weather changes 1
ndbc ndbc:42095, ndbc:LONF1 NDBC buoys, C-MAN and coastal stations, NOS tide gauges and partner platforms. The only source of sea state 1
aprs cwop:GW4373, aprs:KC5CAM-13 CWOP and amateur radio weather stations off APRS-IS, mostly every five minutes. cwop: ids are CWOP registrations, aprs: ids are callsigns 4

trust_tier runs from 1 (a maintained instrument) to 4 (somebody’s console). Tier 2 is for mesonets and tier 3 for stations hosted here directly (source pws); neither is ingesting yet, so you will only see 1 and 4 today. The station owner’s guide covers how a CWOP station is positioned and weighted from the other side.

Two more labels describe the platform rather than the feed:

  • station_type: land, coastal, buoy, platform, usv, dart, tao or other. A METAR can come from an oil rig.
  • domain: land or marine. Whether the station may speak for conditions ashore. A buoy is marine; a C-MAN tower on a pier is land, because the air it measures is the air on the beach.

Conditions at a point

GET /conditions?lat=33.215&lon=-97.133&units=us
parameter default
lat, lon the point. Or q (a place name, up to 120 characters) or location_id
units auto see Units
max_age_minutes 120 5 to 1440. Ignore stations whose last report is older
limit 12 1 to 50. How many of the nearest stations to rank. More fills more fields, from further away

No point at all is 422; a q or location_id that matches nothing is 404.

{
  "server_time": "2026-11-09T19:07:14Z",
  "location": { "lat": 33.215, "lon": -97.133, "name": null, "display_name": null, "state": null },
  "units": {
    "system": "us", "source": "explicit", "country": null,
    "fields": {
      "temperature": { "unit": "F", "label": "°F", "description": "Air temperature." },
      "pressure_altimeter": { "unit": "inHg", "label": "inHg", "description": "Altimeter setting, as reported in a METAR." }
    }
  },
  "fields": {
    "temperature": { "value": 69.0, "unit": "F", "label": "°F", "station_id": "aprs:KC5CAM-13",
                     "source": "aprs", "distance_m": 2485, "age_seconds": 74, "derived": false },
    "dewpoint": { "value": 58.8, "unit": "F", "label": "°F", "station_id": "aprs:KC5CAM-13",
                  "source": "aprs", "distance_m": 2485, "age_seconds": 74, "derived": true },
    "pressure_altimeter": { "value": 30.2, "unit": "inHg", "label": "inHg", "station_id": "metar:KDTO",
                            "source": "metar", "distance_m": 6210, "age_seconds": 854, "derived": false },
    "visibility": { "value": 10.0, "unit": "mi", "label": "mi", "station_id": "metar:KDTO",
                    "source": "metar", "distance_m": 6210, "age_seconds": 854, "derived": false },
    "cloud": { "value": [{ "cover": "BKN", "base_ft_agl": 3900.0 }], "unit": null, "label": null,
               "station_id": "metar:KDTO", "source": "metar", "distance_m": 6210, "age_seconds": 854,
               "derived": false }
  },
  "marine": {},
  "stations": [
    { "station_id": "aprs:KC5CAM-13", "source": "aprs", "station_type": "land", "domain": "land",
      "trust_tier": 4, "name": null, "lat": 33.2363, "lon": -97.1412,
      "distance_m": 2485, "age_seconds": 74, "observed_at": "2026-11-09T19:06:00Z",
      "raw": "KC5CAM-13>APRS,TCPIP*,qAC,AMBCWOP-2:@091906z3314.18N/09708.47W_181/002g003t069r000p000P000h70b10230L216AmbientCWOP.com" },
    { "station_id": "metar:KDTO", "source": "metar", "station_type": "land", "domain": "land",
      "trust_tier": 1, "name": "Denton Muni, TX", "lat": 33.2088, "lon": -97.1992,
      "distance_m": 6210, "age_seconds": 854, "observed_at": "2026-11-09T18:53:00Z",
      "raw": "METAR KDTO 091853Z AUTO 17003KT 10SM BKN039 19/13 A3020 RMK AO2 SLP221 T01940128" }
  ],
  "candidates_considered": 12
}

Every field names its own station

A point’s conditions are assembled one field at a time. The nearest stations are ranked once, and each field takes the best-ranked station that reports it. In the example the temperature, dew point and rain came from a backyard station 2.5 km away, while visibility, the altimeter setting and the cloud layers came from the airfield 6 km away, because the backyard station has no sensor for them. A station missing a sensor costs that one field, not the whole answer.

Stations are never averaged. Two stations disagreeing is a fact about the weather; their mean is a number nobody observed.

The ranking is one line:

score = distance × tier penalty × (1 + age in seconds / 7200)

Lowest wins. The tier penalty is 1.0 for tier 1, 1.3 for tier 2, 1.6 for tier 3 and 2.0 for tier 4, so a CWOP station has to be more than twice as close as an airfield to win a field, and a reading two hours old counts as twice as far away as a fresh one. In the Florida Keys that puts a C-MAN tower 32 km away ahead of a CWOP station 10 km away for temperature and wind.

Each entry in fields carries:

key
value in the response’s unit system
unit, label machine code (F, hPa) and display form (°F)
station_id, source who measured it
distance_m from the requested point to that station
age_seconds how old the reading was when served
derived computed rather than measured. A METAR carries no humidity and a CWOP packet carries no dew point, so the missing half of each pair is derived from the other (relative_humidity on metar, dewpoint on aprs)

A key that is absent had no station reporting it. stations lists only the stations that supplied at least one field, with their name, position, raw report and how far and how old they were. candidates_considered is how many stations were ranked: 0 means nothing has reported nearby within max_age_minutes, which is a different answer from a point where every field happens to be missing.

Readings that are held back

A broken backyard thermometer is caught by its neighbors. For temperature and dewpoint, a reading more than 16.7 °C (about 30 °F) from the median of at least three land stations within 50 miles is refused, and the field falls through to the next station. The bound is loose on purpose: sea breezes, outflow and valley inversions are real and stay. When a derived value fails, the measurement it came from goes with it (a CWOP dew point fails, so its humidity is withheld too). Wind and rain are never checked this way, because one storm really can put them at one station and not the next.

Buoys and offshore platforms (domain: marine) never answer air temperature, moisture, wind, cloud or present weather for a point, because air over open water is not the air ashore. Their pressure counts, and they are the main source of marine.

The fields

field
temperature, dewpoint, relative_humidity
wind_direction degrees true, the direction it blows from. A calm METAR reports 0 with a speed of 0
wind_speed, wind_gust sustained wind and peak gust
pressure_sea_level reduced to sea level. On a CWOP station the owner’s console did the reduction, with an elevation they typed in
pressure_altimeter the altimeter setting in a METAR
pressure_station at the station, not reduced
pressure_tendency change over the last three hours; negative is falling
visibility prevailing
precip_1h, precip_24h, precip_since_local_midnight accumulations over each window
snow_depth
solar_irradiance downwelling, W/m² in every system
cloud METAR sky layers, lowest first: [{ "cover": "BKN", "base_ft_agl": 3900.0 }]. Bases are always feet above ground, whatever units says
wx_string present weather in METAR code: "-RA BR", "VCSH"
flight_category VFR, MVFR, IFR or LIFR

Three pressures, never one: they are different measurements, and a station can report all three. /weather picks one for you.

cloud, wx_string and flight_category carry null for unit and label; they are text, not quantities.

Sea state

"marine": {
  "water_temperature": { "value": 83.7, "unit": "F", "label": "°F", "station_id": "ndbc:TBYF1",
                         "source": "ndbc", "station_type": "coastal", "distance_m": 21126,
                         "age_seconds": 4035, "derived": false },
  "water_level": { "value": 0.3, "unit": "ft", "label": "ft", "station_id": "ndbc:TBYF1",
                   "source": "ndbc", "station_type": "coastal", "distance_m": 21126,
                   "age_seconds": 4035, "derived": false }
}

marine is its own block, chosen the same way, because the station that answers it is often much further away and the distance is the point. It is {} inland. Each entry adds station_type, so you can tell a buoy 30 km out from a gauge on the pier.

field
water_temperature near the waterline, about 0.6 m down on a buoy hull. Not a skin or satellite temperature
wave_height significant wave height: the mean of the highest third, about half the largest wave present
wave_period_dominant, wave_period_average seconds. In a mixed sea the two differ
wave_direction degrees true, the direction the dominant waves come from
water_level above the station’s datum

Stations in a viewport

GET /stations?bbox=-97.9,32.4,-96.3,33.4&units=us
GET /stations?bbox=-130,20,-90,50&sources=metar&detail=minimal
GET /stations?bbox=-83,24,-79,27&reports_sea=true

For a map. Returns every station in the box that has reported within max_age_minutes, airfields and buoys (tier 1) first, then the most recent.

parameter default
bbox required west,south,east,north in degrees. At most 30° a side, or up to 180° when sources=metar alone. Does not cross the antimeridian
sources all comma separated: metar, aprs, ndbc. An unknown name matches nothing rather than failing
domain both land or marine
reports_sea false only stations measuring the water (sea temperature or wave height)
detail full minimal for position and one reading per station
max_age_minutes 120 5 to 1440
limit 400 1 to 2000
units auto see Units

A malformed, inverted or oversized bbox is 422 with a message saying which.

{
  "server_time": "2026-11-09T19:10:02Z",
  "count": 3,
  "truncated": true,
  "units": { "system": "us", "source": "explicit", "country": null, "fields": { "...": "..." } },
  "stations": [
    {
      "station_id": "metar:KLUD", "source": "metar", "network": "ASOS/AWOS",
      "name": "Decatur Muni, TX", "lat": 33.2568, "lon": -97.5826, "elevation_m": 319.0,
      "trust_tier": 1, "station_type": "land", "domain": "land",
      "observed_at": "2026-11-09T18:55:00Z", "age_seconds": 927,
      "temperature": 66.2, "dewpoint": 53.6, "relative_humidity": 63.8,
      "wind_direction": 190.0, "wind_speed": 4.6, "wind_gust": null,
      "pressure_sea_level": null, "precip_1h": null,
      "water_temperature": null, "wave_height": null, "wave_period_dominant": null, "wave_direction": null,
      "raw": "METAR KLUD 091855Z AUTO 19004KT 10SM FEW029 FEW036 FEW100 19/12 A3020 RMK AO2",
      "withheld": []
    }
  ]
}

truncated: true means the box holds more reporting stations than limit returned. Zoom in rather than treating the page as all of them.

withheld names any field refused against the station’s neighbors in this viewport (the same check as above), with that field set to null. The station stays on the map so its owner can find it. A viewport with fewer than three peers around a station gets no check.

network is the roster’s name for the program (ASOS/AWOS, CWOP, APRSWXNET, NOS/CO-OPS, IOOS Partners and so on), finer than source. A platform that reaches us through two feeds is drawn once.

detail=minimal

Over a continent the full rows are most of a megabyte, nearly all of it raw reports nobody will click. minimal keeps what a client needs to place and color a point:

{ "station_id": "metar:KLUD", "source": "metar", "temperature": 66.2,
  "lat": 33.2568, "lon": -97.5826, "age_seconds": 927 }

With reports_sea=true or domain=marine, the minimal row swaps the air temperature for the water:

{ "station_id": "ndbc:41122", "source": "ndbc", "station_type": "buoy",
  "water_temperature": 82.9, "wave_height": 1.3,
  "lat": 26.001, "lon": -80.096, "age_seconds": 2427 }

One station

GET /stations/metar:KDTO?units=us

Who the station is, where, and its latest reading. Unknown ids are 404.

{
  "server_time": "2026-11-09T19:09:12Z",
  "units": { "system": "us", "source": "explicit", "country": null, "fields": { "...": "..." } },
  "station": {
    "station_id": "metar:KDTO", "source": "metar", "network": "ASOS/AWOS",
    "name": "Denton Muni, TX", "lat": 33.2088, "lon": -97.1992, "elevation_m": 199.0,
    "trust_tier": 1, "position_source": "awc",
    "first_seen_at": "2026-08-31T13:55:55Z", "last_seen_at": "2026-11-09T19:09:11Z",
    "observed_at": "2026-11-09T18:53:00Z", "age_seconds": 992,
    "raw": "METAR KDTO 091853Z AUTO 17003KT 10SM BKN039 19/13 A3020 RMK AO2 SLP221 T01940128",
    "latest": {
      "temperature": 66.9, "dewpoint": 55.0, "relative_humidity": 65.6,
      "wind_direction": 170.0, "wind_speed": 3.5,
      "pressure_sea_level": 30.18, "pressure_altimeter": 30.2, "visibility": 10.0,
      "cloud": [{ "cover": "BKN", "base_ft_agl": 3900.0 }], "flight_category": "VFR"
    }
  }
}

latest holds only what this station reports. An absent key means the station has no such sensor, which is more useful than a row of dashes. Its field names are the ones in the fields and sea state.

position_source says where the coordinates came from: a roster (awc, madis, ndbc) or the station’s own APRS beacon (aprs), which is self-reported. A station can be on the roster before it has sent anything; then observed_at, age_seconds and raw are null and latest is {}.

One station’s history

GET /stations/metar:KDTO/history?hours=6&units=us
parameter default
hours 24 1 to 168
limit 500 1 to 2000
units auto see Units
{
  "server_time": "2026-11-09T19:09:41Z",
  "station_id": "metar:KDTO",
  "hours": 3,
  "count": 3,
  "units": { "system": "us", "source": "explicit", "country": null, "fields": { "...": "..." } },
  "samples": [
    { "observed_at": "2026-11-09T16:53:00Z", "temperature": 61.0, "dewpoint": 55.9,
      "relative_humidity": 83.5, "wind_direction": 0.0, "wind_speed": 0.0,
      "pressure_sea_level": 30.24, "pressure_altimeter": 30.25, "visibility": 10.0,
      "cloud": [{ "cover": "OVC", "base_ft_agl": 4500.0 }], "flight_category": "VFR" },
    "..."
  ]
}

Samples run oldest first, and each carries observed_at plus only the fields that reading had. A known station with nothing in the window returns 200 with count: 0; an id no station has is 404, as on the detail route.

When the window holds more than limit samples, the newest limit are kept. A CWOP station reports every five minutes, so a week of it is around 2,000 rows and the default limit of 500 covers about the last two days. If count equals limit, the start of the window was cut; ask for a higher limit to reach it.

Units

GET /conditions?lat=33.2&lon=-97.1&units=metric
value temperature speed pressure precipitation visibility wave height, water level
si K m/s Pa kg/m² m m
metric °C km/h hPa mm km m
us °F mph inHg in mi ft
uk °C mph hPa mm mi m
auto chosen from the caller’s country

Humidity is %, directions deg, wave periods s and solar irradiance W/m² in every system. With auto, a country that reads Fahrenheit gets us, Britain gets uk and everywhere else metric; a caller who cannot be placed gets us.

Every response’s units block names the system, how it was chosen (source: explicit, geo or default), the country when it was chosen from one, and the unit, label and a one-line description for every field the endpoint can return. The vocabulary is the forecast’s own, so a temperature here and a temperature on /forecast render through the same code.

May we use Microsoft Clarity to see how pages are used (clicks, scrolling and replays of a visit, with anything you type hidden), plus a random ID linking that to crash reports? Only to find bugs and confusing screens. Never advertising, never sold.