API Documentation

  • ✨ API Version 2 — Expanded station support (30 stations), metric units for international locations, Wethr Low calculations, and explicit logic parameters. Base URL: https://wethr.net/api/v2/

    Introduction

    The Wethr.net API v2 provides programmatic access to weather observation data and model forecasts. It includes support for 30 weather stations across the US and international markets, with native metric units for non-US stations.

    Base URL: https://wethr.net/api/v2/


    What's New in v2

    • Push API BETA: Stream real-time observations, DSM/CLI releases, and temperature extreme alerts
    • Model Accuracy API NEW DEV+: Per-model forecast accuracy metrics (MAE, bias, RMSE) across 45 stations with filtering by model, run time, and time window. Requires Developer or Enterprise tier.
    • Precipitation API BETA: New endpoint for monthly precipitation totals with real-time ASOS data
    • NWS Forecasts API BETA: New endpoint for NWS hourly temperature forecasts with version history
    • Expanded Station Support: 30 stations including London, Buenos Aires, Toronto, and Seoul
    • Metric Units: International stations (EGLC, SAEZ, CYYZ, RKSI, NZWN, SBGR, LFPG, LTAC, VILK, EDDM, LLBG, RJTT, ZSPD, VHHH, WSSS) return temperatures in Celsius
    • Wethr Low: The wethr_high mode now also returns wethr_low
    • Improved Wethr High/Low Accuracy (NWS mode): NWS logic incorporates high-frequency observations to capture extremes between standard METAR reporting intervals. WU/Polymarket logic uses only standard METAR observations.
    • Required Logic Parameter: Explicit logic parameter required for wethr_high mode (nws or wu)
    • Observation Type Filter: Filter latest mode by dsm_high, dsm_low, cli_high, cli_low
    • Units Indicator: All responses include a units field

    Authentication

    All requests require an API key. Include it in the HTTP header:

    Authorization: Bearer <YOUR_API_KEY>

    Or use the custom header:

    X-API-Key: <YOUR_API_KEY>

    Generate API keys in your Account Settings.


    Rate Limiting

    TierRequests/MinuteRequests/Day
    Professional605,000
    Developer30050,000
    Enterprise30050,000

    Supported Stations

    US Stations (Fahrenheit)

    CodeLocationTimezone
    KMDWChicago MidwayAmerica/Chicago
    KPHLPhiladelphiaAmerica/New_York
    KMIAMiamiAmerica/New_York
    KLAXLos AngelesAmerica/Los_Angeles
    KBKFAurora (Buckley SFB)America/Denver
    KDENDenverAmerica/Denver
    KAUSAustinAmerica/Chicago
    KNYCNew York (Central Park)America/New_York
    KDFWDallas/Fort WorthAmerica/Chicago
    KMSYNew OrleansAmerica/Chicago
    KLGANew York LaGuardiaAmerica/New_York
    KDALDallas Love FieldAmerica/Chicago
    KHOUHouston HobbyAmerica/Chicago
    KSEASeattleAmerica/Los_Angeles
    KSFOSan FranciscoAmerica/Los_Angeles
    KLASLas VegasAmerica/Los_Angeles
    KPHXPhoenixAmerica/Phoenix
    KSATSan AntonioAmerica/Chicago
    KDCAWashington D.C.America/New_York
    KCLTCharlotteAmerica/New_York
    KBOSBostonAmerica/New_York
    KBNANashvilleAmerica/Chicago
    KATLAtlantaAmerica/New_York
    KJAXJacksonvilleAmerica/New_York
    KOKCOklahoma CityAmerica/Chicago
    KDTWDetroitAmerica/Detroit
    KMSPMinneapolisAmerica/Chicago

    International Stations (Celsius) NEW

    CodeLocationTimezone
    EGLCLondon City AirportEurope/London
    SAEZBuenos Aires EzeizaAmerica/Argentina/Buenos_Aires
    CYYZToronto PearsonAmerica/Toronto
    RKSISeoul IncheonAsia/Seoul
    SBGRSão Paulo GuarulhosAmerica/Sao_Paulo
    LFPGParis Charles de GaulleEurope/Paris
    LTACEsenboğa International AirportEurope/Istanbul
    VILKChaudhary Charan Singh International AirportAsia/Kolkata
    EDDMMunich AirportEurope/Berlin
    LLBGBen Gurion International AirportAsia/Jerusalem
    RJTTTokyo Haneda AirportAsia/Tokyo
    ZSPDShanghai Pudong AirportAsia/Shanghai
    VHHHHong Kong International AirportAsia/Hong_Kong
    WSSSSingapore Changi AirportAsia/Singapore

    Error Responses

    • 400 Bad Request — Invalid or missing parameters
    • 401 Unauthorized — Invalid or missing API key
    • 403 Forbidden — Tier restriction (e.g., Model Accuracy API requires Developer or Enterprise)
    • 429 Too Many Requests — Rate limit exceeded
    • 500 Server Error — Internal server error
    {
        "error": "Missing required parameter: logic",
        "details": "The 'logic' parameter is required for wethr_high mode. Valid values: 'nws' or 'wu'."
    }

    Observations API

    GET /api/v2/observations.php

    Mode: Latest Observation

    Returns the most recent observation for a station.

    Parameters

    ParameterRequiredDescription
    station_codeYesStation identifier
    modeYesSet to latest
    observation_typeNoFilter: dsm_high, dsm_low, cli_high, cli_low

    Example Request

    GET /api/v2/observations.php?station_code=KMDW&mode=latest
    GET /api/v2/observations.php?station_code=KMDW&mode=latest&observation_type=cli_high

    Example Response

    {
        "station_code": "KMDW",
        "observation_time": "2025-06-15 18:53:00",
        "temperature": 26.7,
        "temperature_display": 80.1,
        "lowest_probable": 80,
        "highest_probable": 80,
        "units": "fahrenheit",
        "dsm_high": 28.3,
        "dsm_high_display": 83,
        "relative_humidity": 55.2
    }

    Mode: Wethr High/Low Calculation

    Calculates the current trading-day high and low temperatures based on multiple observation sources.

    • NWS logic: Uses CLI/DSM reports, 6-hour highs/lows, and all available observations to capture extremes between standard reporting intervals. Time window follows Standard Time year-round.
    • WU logic: Uses only standard METAR observations. Time window follows Local Time year-round. See Precision Validation for US Stations for details on how observation accuracy is ensured.

    Parameters

    ParameterRequiredDescription
    station_codeYesStation identifier
    modeYesSet to wethr_high
    logic Yes nws — NWS/Kalshi: Standard Time year-round, uses CLI/DSM/6hr data
    wu — Weather Underground: Local Time year-round, uses exact temperature

    Example Request

    GET /api/v2/observations.php?station_code=KMDW&mode=wethr_high&logic=nws

    Example Response

    {
        "station_code": "KMDW",
        "date": "2025-06-15",
        "wethr_high": 83,
        "wethr_low": 68,
        "time_of_high_utc": "2025-06-15 20:53:00",
        "time_of_low_utc": "2025-06-15 10:53:00",
        "calculation_logic": "nws",
        "units": "fahrenheit"
    }

    WU Logic: Precision Validation for US Stations

    METAR observations report temperature in two ways: the body of the report contains a whole-degree Celsius value (e.g., 26/08), while the remarks section may contain a "T-group" with tenths-of-a-degree precision (e.g., T02560078 = 25.6°C). The difference matters because 26°C rounds to 79°F, while 25.6°C rounds to 78°F — a 1°F discrepancy from a single observation that can affect the Wethr High.

    US ASOS stations (ICAO codes beginning with K) nearly always produce T-groups. However, when a new METAR is first published, the body temperature may be available before the complete remarks section has propagated through all data sources. During this brief window, the observation may appear to have a rounded whole-degree temperature when a more precise value is forthcoming.

    To prevent this from affecting the Wethr High/Low calculation, the API applies the following validation when using logic=wu:

    • If a K-station observation has tenths-of-a-degree precision (T-group present), it is used immediately — the temperature is exact and unambiguous.
    • If a K-station observation has only whole-degree precision (no T-group), the API requires confirmation from a secondary data source before including it in the Wethr High/Low calculation. This ensures the T-group has had time to propagate and that the rounded value is not premature.

    This validation applies only to WU logic. NWS logic is unaffected because it uses temperature ranges (lowest_probable / highest_probable) rather than a single rounded value, which inherently accounts for rounding uncertainty.


    Mode: History

    Retrieves observations over a time range (max 24 hours). Default mode when mode is omitted.

    Parameters

    ParameterRequiredDescription
    station_codeYesStation identifier
    start_timeYesStart of range (ISO 8601 UTC)
    end_timeYesEnd of range (ISO 8601 UTC)

    Example Request

    GET /api/v2/observations.php?station_code=KMDW&start_time=2025-06-15T00:00:00Z&end_time=2025-06-15T12:00:00Z

    Observation Data Model

    FieldTypeDescription
    idintegerUnique record identifier
    station_codestringStation identifier
    observation_timedatetimeUTC timestamp
    temperaturedecimalTemperature in Celsius (raw)
    temperature_displaydecimalTemperature in station's native units
    lowest_probableintegerMin probable temp (native units)
    highest_probableintegerMax probable temp (native units)
    unitsstringfahrenheit or celsius
    precision_levelintegerData precision (1 = exact)
    dew_pointdecimalDew point (Celsius)
    relative_humiditydecimalRelative humidity (%)
    wind_directionstringWind direction
    wind_speeddecimalWind speed (knots)
    wind_gustdecimalWind gust (knots)
    visibilitydecimalVisibility (statute miles)
    six_hour_highdecimal6-hour high (Celsius)
    six_hour_lowdecimal6-hour low (Celsius)
    cli_high / cli_lowdecimalCLI report values (US only)
    dsm_high / dsm_lowdecimalDSM report values (US only)
    *_displayintegerValues in station's native units
    *_fintegerValues in Fahrenheit (backward compat)

    Client Examples

    cURL — Wethr High with NWS Logic

    curl -G "https://wethr.net/api/v2/observations.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KMDW" \
      --data-urlencode "mode=wethr_high" \
      --data-urlencode "logic=nws"

    cURL — Latest CLI High

    curl -G "https://wethr.net/api/v2/observations.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KMDW" \
      --data-urlencode "mode=latest" \
      --data-urlencode "observation_type=cli_high"

    JavaScript — Wethr High/Low

    const params = new URLSearchParams({
        station_code: 'KMDW',
        mode: 'wethr_high',
        logic: 'nws'
    });
    
    fetch(`/api/v2/observations.php?${params}`, {
        headers: { 'Authorization': `Bearer ${apiKey}` }
    })
    .then(res => res.json())
    .then(data => {
        console.log(`High: ${data.wethr_high}° / Low: ${data.wethr_low}°`);
    });

    Python — Latest Observation

    import requests
    
    response = requests.get(
        'https://wethr.net/api/v2/observations.php',
        params={'station_code': 'KMDW', 'mode': 'latest'},
        headers={'Authorization': f'Bearer {api_key}'}
    )
    data = response.json()
    print(f"Current: {data['temperature_display']}°{data['units'][0].upper()}")

    Forecasts API

    GET /api/v2/forecasts.php

    Retrieves model forecast data for a location within a time range.

    Parameters

    ParameterRequiredDescription
    location_nameYesLocation identifier (e.g., KMDW)
    start_valid_timeYesStart of forecast range (ISO 8601)
    end_valid_timeYesEnd of forecast range (ISO 8601)
    model No Filter by model: ARPEGE, ECMWF-IFS, GEFS, GEM-GDPS, GEM-HRDPS, GFS, GFS-MOS, HRRR, ICON, JMA, LAV-MOS, NAM, NAM-MOS, NAM4KM, NBM, NBS-MOS, RAP, UKMO

    Example Request

    GET /api/v2/forecasts.php?location_name=KMDW&start_valid_time=2025-06-15T18:00:00Z&end_valid_time=2025-06-15T20:00:00Z&model=HRRR

    Forecast Data Model

    FieldTypeDescription
    idintegerUnique record identifier
    modelstringForecast model name
    location_namestringLocation identifier
    latitudedecimalLatitude
    longitudedecimalLongitude
    run_timedatetimeModel run time (UTC)
    valid_timedatetimeForecast valid time (UTC)
    forecast_hourintegerLead time in hours
    temperature_kdecimalTemperature (Kelvin)
    temperature_fdecimalTemperature (Fahrenheit)
    temperature_cdecimalTemperature (Celsius)

    Precipitation API BETA

    GET /api/v2/precipitation.php
    ⚠️ Beta Notice: This API is currently in beta testing. While functional, you may encounter bugs or unexpected behavior. Data accuracy is not guaranteed during this phase. Please report any issues to help us improve the service.

    Returns current month precipitation totals for a station, combining official CLI (Climate Report) data with live ASOS observations. All date/time calculations use Local Standard Time (no DST adjustment) year-round, matching NWS climate reporting standards.

    Endpoint & Parameters

    ParameterRequiredDescription
    station_codeYesStation identifier (see supported stations below)

    Note: This endpoint always returns data for the current month. Historical month data is not available via this API.

    Supported Stations

    KNYC, KLAX, KMDW, KSFO, KAUS, KMIA, KDFW, KHOU, KDEN, KBKF, KSEA, KPHL, KLAS, KPHX, KSAT, KDCA, KCLT, KBOS, KBNA, KATL, KJAX, KOKC, KDTW, KMSP, KMSY

    Example Request

    GET /api/v2/precipitation.php?station_code=KAUS

    Example Response

    {
        "station_code": "KAUS",
        "station_name": "Austin",
        "timezone": "America/Chicago",
        "timezone_offset_hours": -6,
        "today_local_date": "2025-06-15",
        "month": 6,
        "year": 2025,
        "official_mtd": 1.45,
        "today_precip": 0.127,
        "total_mtd": 1.58,
        "has_trace": false,
        "last_trace_time": null,
        "cli_date": "2025-06-14",
        "cli_issued_at": "2025-06-15 05:15:00",
        "today_hourly_max": {
            "2025-06-15 08": 0.05,
            "2025-06-15 09": 0.077
        },
        "timestamp": "2025-06-15T21:30:00Z",
        "units": "inches"
    }

    Response Fields

    FieldTypeDescription
    station_codestringStation identifier
    station_namestringHuman-readable station name
    timezonestringPHP timezone identifier
    timezone_offset_hoursintegerFixed UTC offset for Local Standard Time (e.g., -6 for CST)
    today_local_datestringCurrent date in station's Local Standard Time (YYYY-MM-DD)
    monthintegerCurrent month (1-12)
    yearintegerCurrent year
    official_mtddecimalOfficial month-to-date total from CLI reports (through yesterday)
    today_precipdecimalToday's precipitation calculated from live ASOS observations
    total_mtddecimalCombined total: official_mtd + today_precip
    has_tracebooleanWhether trace precipitation (P0000) was reported today
    last_trace_timestring|nullTime of last trace report (Local Standard Time)
    cli_datestring|nullDate of the most recent CLI report used
    cli_issued_atstring|nullTimestamp when the CLI report was issued
    today_hourly_maxobjectHourly precipitation breakdown for today (hour → max precip)
    timestampstringAPI response timestamp (UTC, ISO 8601)
    unitsstringAlways inches

    Understanding the Data

    • Official (CLI): The official_mtd value comes from the NWS Daily Climate Report (CLI), typically issued around 5 AM local time. This is the "banked" official total through the previous day.
    • Live (Today): The today_precip value is calculated in real-time from 5-minute ASOS observations since midnight Local Standard Time.
    • Trace: When has_trace is true, precipitation was detected but was too small to measure (<0.01"). Trace amounts are not included in the totals.
    • Hourly Max: The today_hourly_max object shows the maximum cumulative reading for each hour, useful for detailed analysis.

    cURL Example

    curl -G "https://wethr.net/api/v2/precipitation.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KAUS"

    Python Example

    import requests
    
    response = requests.get(
        'https://wethr.net/api/v2/precipitation.php',
        params={'station_code': 'KAUS'},
        headers={'Authorization': f'Bearer {api_key}'}
    )
    data = response.json()
    
    print(f"Month-to-Date: {data['total_mtd']}\"")
    print(f"  Official (CLI): {data['official_mtd']}\"")
    print(f"  Today (Live):   {data['today_precip']}\"")
    if data['has_trace']:
        print(f"  Trace detected at {data['last_trace_time']}")

    NWS Forecasts API BETA

    GET /api/v2/nws_forecasts.php
    ⚠️ Beta Notice: This API is currently in beta testing. While functional, you may encounter bugs or unexpected behavior. Please report any issues to help us improve the service.

    Returns NWS hourly temperature forecasts for a station. Forecasts are sourced from the National Weather Service API and stored with version tracking as they update throughout the day.

    ⚠️ Critical: Local Standard Time Convention
    All dates and hours in this API use Local Standard Time year-round (no Daylight Saving Time adjustment). This means:
    • During DST months, the "day" boundaries are shifted by 1 hour compared to civil time
    • Hour 0 always represents midnight Standard Time, not midnight local civil time
    • This convention matches NWS climate reporting and Kalshi temperature market resolution

    If you are using a different resolution source that uses civil time (with DST), these forecasts may not align correctly with your day boundaries.

    Endpoint & Parameters

    ParameterRequiredDescription
    station_codeYesStation identifier
    date No Forecast date in YYYY-MM-DD format. Defaults to today. Can request up to 2 days in the future or 365 days (1 year) in the past.
    mode No latest (default) — Returns only the most recent forecast version for the requested date
    history — Returns all forecast versions for the requested date (useful to see how the forecast evolved)

    Example Requests

    # Today's latest forecast
    GET /api/v2/nws_forecasts.php?station_code=KAUS
    
    # Tomorrow's latest forecast
    GET /api/v2/nws_forecasts.php?station_code=KAUS&date=2025-06-16
    
    # Today's forecast history (all versions)
    GET /api/v2/nws_forecasts.php?station_code=KAUS&date=2025-06-15&mode=history

    Example Response (mode=latest)

    {
        "station_code": "KAUS",
        "station_name": "Austin",
        "timezone": "America/Chicago",
        "timezone_offset_hours": -6,
        "forecast_date": "2025-06-15",
        "version": 12,
        "hourly_temps": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, 94, 95, 95, 94, 92, 89, 85, 82, 79, 77, 75],
        "high": 95,
        "low": 75,
        "timestamp": "2025-06-15T21:30:00Z",
        "units": "fahrenheit",
        "time_convention": "local_standard_time"
    }

    Example Response (mode=history)

    {
        "station_code": "KAUS",
        "station_name": "Austin",
        "timezone": "America/Chicago",
        "timezone_offset_hours": -6,
        "forecast_date": "2025-06-15",
        "total_versions": 3,
        "forecasts": [
            {
                "version": 1,
                "hourly_temps": [null, null, null, null, null, null, 71, 74, 78, 82, ...],
                "high": 94,
                "low": 71
            },
            {
                "version": 2,
                "hourly_temps": [null, null, null, null, null, null, null, null, null, null, null, null, 91, 93, ...],
                "high": 95,
                "low": 77
            }
        ],
        "timestamp": "2025-06-15T21:30:00Z",
        "units": "fahrenheit",
        "time_convention": "local_standard_time"
    }

    Response Fields

    FieldTypeDescription
    station_codestringStation identifier
    station_namestringHuman-readable station name
    timezonestringPHP timezone identifier
    timezone_offset_hoursintegerFixed UTC offset for Local Standard Time
    forecast_datestringForecast date (YYYY-MM-DD) in Local Standard Time
    versionintegerForecast version number (latest mode only)
    hourly_tempsarrayArray of 25 temperatures (indices 0-24, where 24 = midnight next day). Values are null for hours that had already passed when the forecast was issued.
    highinteger|nullForecasted high temperature, calculated as the maximum of all non-null values in hourly_temps
    lowinteger|nullForecasted low temperature, calculated as the minimum of all non-null values in hourly_temps
    total_versionsintegerNumber of forecast versions available (history mode only)
    forecastsarrayArray of all forecast versions (history mode only)
    unitsstringAlways fahrenheit
    time_conventionstringAlways local_standard_time

    Understanding the Time Convention

    The hourly_temps array contains 25 values representing hours 0 through 24:

    • Index 0: Midnight (00:00) Local Standard Time
    • Index 12: Noon (12:00) Local Standard Time
    • Index 24: Midnight (00:00) next day — included for continuity

    Null Values for Past Hours

    The NWS API only provides forecasts for future hours. Hours that have already occurred when the forecast was fetched will have null values. For example, if a forecast is fetched at 2:00 PM, indices 0-13 will typically be null while indices 14-24 will contain forecasted temperatures.

    When using mode=history, earlier forecast versions will have more non-null values since they were fetched when more hours were still in the future.

    Example during Daylight Saving Time

    In Austin (CST/CDT), during summer when CDT is in effect:

    • Index 0 represents midnight Central Standard Time (which is 1:00 AM CDT civil time)
    • The "day" in this API runs from 1:00 AM to 1:00 AM civil time during DST months

    cURL Example

    curl -G "https://wethr.net/api/v2/nws_forecasts.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KAUS" \
      --data-urlencode "date=2025-06-15"

    Python Example

    import requests
    
    response = requests.get(
        'https://wethr.net/api/v2/nws_forecasts.php',
        params={'station_code': 'KAUS', 'mode': 'latest'},
        headers={'Authorization': f'Bearer {api_key}'}
    )
    data = response.json()
    
    print(f"Forecast for {data['forecast_date']} (version {data['version']})")
    print(f"High: {data['high']}°F / Low: {data['low']}°F")
    print(f"Hourly: {data['hourly_temps']}")

    Push API BETA

    ⚠️ Beta Product: The Push API is currently in beta. While we strive to maintain high availability and data quality, this service may undergo changes without notice. Additionally, weather data is sourced from upstream providers (NOAA, NWS, Synoptic) and station sensors that may occasionally experience outages or report erroneous values. We strongly recommend implementing your own data validation logic to detect and handle anomalies such as unrealistic temperature spikes, sensor malfunctions, or missing data. Wethr.net is not liable for trading decisions or other actions based on data received through this API.

    Overview

    The Push API delivers real-time weather data as events occur. Instead of polling the REST API, clients maintain a persistent connection and receive updates instantly — including new observations, DSM/CLI releases, and temperature extreme alerts.

    Key Benefits:
    • Real-time updates with sub-second latency
    • Efficient — no polling overhead
    • Automatic reconnection with event replay
    • Temperature alerts for new highs/lows as they occur

    Base URL: https://wethr.net:3443/api/v2/stream


    Authentication & Rate Limits

    The Push API uses API key authentication. Include your key as a query parameter:

    GET https://wethr.net:3443/api/v2/stream?stations=KAUS,KMDW&api_key=YOUR_API_KEY
    TierMax StationsConnections per User
    Professional51
    DeveloperUnlimited1
    EnterpriseUnlimited1

    Requirements:

    • HTTPS only
    • One connection per user account
    • API key must be enabled in your account

    Connecting

    Parameters

    ParameterRequiredDescription
    stationsYesComma-separated station codes (e.g., KAUS,KMDW,KLAX)
    api_keyYesYour API key
    last_event_idNoResume from a specific event ID (for reconnection)

    Connection Lifecycle

    1. Client connects with station list and API key
    2. Server validates credentials and station limits
    3. Server sends connected event with subscribed stations
    4. Server streams events as they occur
    5. Server sends periodic heartbeat events (every 30s)
    6. On disconnect, client can reconnect with last_event_id to replay missed events

    Event Types

    EventDescriptionFrequency
    connectedConnection establishedOnce on connect
    heartbeatKeep-alive signalEvery 30 seconds
    observationNew weather observation (METAR/HF-METAR/SPECI)Every 1-5 minutes per station
    dsmDaily Summary Message releasedVaries by station
    cliClimate Report releasedVaries by station
    new_highNew temperature high detectedAs conditions change
    new_lowNew temperature low detectedAs conditions change

    Payload Reference

    observation

    Sent when a new METAR, HF-METAR, or SPECI observation is received.

    {
        "station_code": "KAUS",
        "event": "observation",
        "timezone": "America/Chicago",
        "local_date": "2026-02-08",
        "product": "HF-METAR",
        "observation_time_utc": "2026-02-08T18:55:00Z",
        "temperature_celsius": 24,
        "temperature_fahrenheit": 75.2,
        "dew_point_celsius": 12,
        "dew_point_fahrenheit": 53.6,
        "relative_humidity": 47,
        "wind_direction": "200",
        "wind_speed_mph": 14,
        "wind_speed_kmh": 22,
        "wind_gust_mph": null,
        "wind_gust_kmh": null,
        "visibility_miles": 10,
        "altimeter_inhg": 30.12,
        "wethr_high": {
            "nws": {"value_f": 75, "value_c": 24, "time_utc": "2026-02-08T18:44:00Z"},
            "wu": {"value_f": 73, "value_c": 23, "time_utc": "2026-02-08T17:53:00Z"}
        },
        "wethr_low": {
            "nws": {"value_f": 42, "value_c": 6, "time_utc": "2026-02-08T12:53:00Z"},
            "wu": {"value_f": 42, "value_c": 6, "time_utc": "2026-02-08T12:53:00Z"}
        },
        "high_valid_through_utc": "2026-02-08T18:00:00Z",
        "low_valid_through_utc": "2026-02-08T12:00:00Z",
        "anomaly": false,
        "suspect_temperature": null,
        "id": "49500",
        "timestamp": "2026-02-08T18:56:15Z"
    }

    Note on Wethr High/Low:

    • nws: Takes into account all observations, CLI/DSM data, Standard Time window
    • wu: METAR observations only, Local Time window

    Valid Through (HVT/LVT):

    high_valid_through_utc and low_valid_through_utc indicate the time through which the high/low values are considered "locked in" based on official reports (DSM/CLI) and confirmed 6-hour extremes. A null value means the extreme is still based on preliminary observations and may change.

    Data Quality Flags:

    • anomaly and suspect_temperature help identify potentially erroneous data. See Data Quality Flags for details.

    dsm

    Sent when the Daily Summary Message is released. Timing varies by station.

    {
        "station_code": "KAUS",
        "event": "dsm",
        "for_date": "2026-02-07",
        "high_f": 84,
        "high_c": 29,
        "high_time_utc": "2026-02-07T21:53:00Z",
        "low_f": 42,
        "low_c": 6,
        "low_time_utc": "2026-02-07T12:53:00Z",
        "anomaly": false,
        "id": "49123",
        "timestamp": "2026-02-08T06:15:00Z"
    }

    cli

    Sent when the Climate Report is released. Timing varies by station.

    {
        "station_code": "KAUS",
        "event": "cli",
        "for_date": "2026-02-07",
        "high_f": 84,
        "high_c": 29,
        "low_f": 42,
        "low_c": 6,
        "anomaly": false,
        "id": "49456",
        "timestamp": "2026-02-08T14:30:00Z"
    }

    new_high / new_low

    Sent immediately when a new temperature extreme is detected.

    {
        "station_code": "KAUS",
        "event": "new_high",
        "logic": "nws",
        "value_f": 75,
        "value_c": 24,
        "prev_value_f": 73,
        "prev_value_c": 23,
        "observation_time_utc": "2026-02-08T18:44:00Z",
        "id": "49478",
        "timestamp": "2026-02-08T18:46:29Z"
    }

    The logic field indicates which calculation triggered the alert (nws or wu). You may receive separate alerts for each logic if both detect a new extreme.


    Data Quality Flags

    The Push API includes flags to help you identify potentially erroneous data. We strongly recommend checking these fields before using observation data for trading or other high-stakes decisions.

    FieldEvent(s)TypeDescription
    anomaly observation, dsm, cli boolean DSM/CLI high temperature is suspiciously elevated vs. nearby METAR observations (possible erroneous official report)
    suspect_temperature observation object | null METAR temperature failed plausibility checks. When present, temperature_celsius / temperature_fahrenheit are null. The rejected values and reason are preserved in this object. The implausible reading is excluded from all calculations — it will not affect wethr_high, wethr_low, or trigger new_high / new_low alerts.

    suspect_temperature Object

    FieldTypeDescription
    raw_celsiusnumberThe rejected temperature in Celsius
    raw_fahrenheitnumberThe rejected temperature in Fahrenheit
    reasonstringMachine-readable reason code (currently: out_of_range)
    messagestringHuman-readable description

    Example: Suspect Temperature Event

    {
        "station_code": "KLAX",
        "event": "observation",
        "product": "METAR",
        "temperature_celsius": null,
        "temperature_fahrenheit": null,
        "suspect_temperature": {
            "raw_celsius": -61.0,
            "raw_fahrenheit": -77.8,
            "reason": "out_of_range",
            "message": "Temperature outside plausible range (-60°C to 60°C)"
        },
        "wethr_high": {
            "nws": {"value_f": 68, "value_c": 20, "time_utc": "2026-02-08T21:53:00Z"},
            "wu": {"value_f": 68, "value_c": 20, "time_utc": "2026-02-08T21:53:00Z"}
        },
        "anomaly": false,
        "id": "49501",
        "timestamp": "2026-02-08T22:56:15Z"
    }

    In this example, KLAX reported −61°C which is clearly erroneous. The temperature was rejected and preserved in suspect_temperature for transparency. The wethr_high and wethr_low values are unaffected, and no new_low alert was triggered.


    Client Examples

    cURL — Test Connection

    curl -N "https://wethr.net:3443/api/v2/stream?stations=KAUS,KMDW&api_key=YOUR_API_KEY"

    JavaScript (Browser)

    const apiKey = 'YOUR_API_KEY';
    const stations = ['KAUS', 'KMDW', 'KLAX'];
    
    const url = `https://wethr.net:3443/api/v2/stream?stations=${stations.join(',')}&api_key=${apiKey}`;
    const eventSource = new EventSource(url);
    
    eventSource.addEventListener('observation', (e) => {
        const data = JSON.parse(e.data);
        
        // Check for suspect temperature
        if (data.suspect_temperature) {
            console.warn(`⚠️ ${data.station_code}: Suspect temp rejected (${data.suspect_temperature.raw_fahrenheit}°F - ${data.suspect_temperature.reason})`);
            return;
        }
        
        console.log(`${data.station_code}: ${data.temperature_fahrenheit}°F`);
        console.log(`  Wethr High (NWS): ${data.wethr_high.nws.value_f}°F`);
    });
    
    eventSource.addEventListener('new_high', (e) => {
        const data = JSON.parse(e.data);
        console.log(`🔥 NEW HIGH at ${data.station_code}: ${data.value_f}°F (was ${data.prev_value_f}°F)`);
    });
    
    eventSource.addEventListener('new_low', (e) => {
        const data = JSON.parse(e.data);
        console.log(`❄️ NEW LOW at ${data.station_code}: ${data.value_f}°F (was ${data.prev_value_f}°F)`);
    });
    
    eventSource.onerror = (e) => {
        console.error('Connection error:', e);
    };

    Python

    import sseclient
    import requests
    import json
    
    api_key = 'YOUR_API_KEY'
    stations = 'KAUS,KMDW,KLAX'
    url = f'https://wethr.net:3443/api/v2/stream?stations={stations}&api_key={api_key}'
    
    response = requests.get(url, stream=True)
    client = sseclient.SSEClient(response)
    
    for event in client.events():
        if event.event == 'observation':
            data = json.loads(event.data)
            
            # Check for suspect temperature
            if data.get('suspect_temperature'):
                st = data['suspect_temperature']
                print(f"⚠️ {data['station_code']}: Suspect temp rejected ({st['raw_fahrenheit']}°F - {st['reason']})")
                continue
            
            print(f"{data['station_code']}: {data['temperature_fahrenheit']}°F")
            print(f"  Wethr High (NWS): {data['wethr_high']['nws']['value_f']}°F")
        
        elif event.event == 'new_high':
            data = json.loads(event.data)
            print(f"🔥 NEW HIGH at {data['station_code']}: {data['value_f']}°F")

    Node.js

    const EventSource = require('eventsource');
    
    const apiKey = 'YOUR_API_KEY';
    const stations = 'KAUS,KMDW,KLAX';
    const url = `https://wethr.net:3443/api/v2/stream?stations=${stations}&api_key=${apiKey}`;
    
    const es = new EventSource(url);
    
    es.addEventListener('observation', (e) => {
        const data = JSON.parse(e.data);
        console.log(`${data.station_code}: ${data.temperature_fahrenheit}°F`);
    });
    
    es.addEventListener('new_high', (e) => {
        const data = JSON.parse(e.data);
        console.log(`NEW HIGH at ${data.station_code}: ${data.value_f}°F`);
    });
    
    es.onerror = (err) => {
        console.error('Error:', err);
    };

    Reconnection with Event Replay

    If your connection drops, you can resume from where you left off using the last_event_id parameter. Track the id field from each event and pass it on reconnect:

    let lastEventId = null;
    
    function connect() {
        let url = `https://wethr.net:3443/api/v2/stream?stations=KAUS&api_key=${apiKey}`;
        if (lastEventId) {
            url += `&last_event_id=${lastEventId}`;
        }
        
        const es = new EventSource(url);
        
        es.onmessage = (e) => {
            const data = JSON.parse(e.data);
            lastEventId = data.id;  // Track for reconnection
        };
        
        es.onerror = () => {
            es.close();
            setTimeout(connect, 5000);  // Reconnect after 5s
        };
    }
    
    connect();

    Model Accuracy API NEW DEV+

    GET /api/v2/model_accuracy.php
    🔒 Developer & Enterprise Only: This endpoint requires a Developer or Enterprise tier API key. Professional and Testing tier keys will receive a 403 Access Denied response.

    Returns per-model forecast accuracy metrics for a station, including MAE (Mean Absolute Error), bias, and RMSE across configurable time windows. Data is computed daily from verified high temperature observations compared against raw model output.

    All 45 stations (US and international) are supported. Data is updated once daily and cached server-side for performance.

    Endpoint & Parameters

    ParameterRequiredDescription
    station_code Yes ICAO station identifier (e.g., KORD, EGLC)
    window No Time window for accuracy metrics. Values: 7d (default), 14d, 30d, 90d
    model No Comma-separated model filter (case-sensitive). Example: GFS,HRRR,ECMWF-IFS. Omit for all models.
    run_time No Comma-separated run time filter. Example: 00z,12z. Omit for all run times.
    include No Comma-separated list of optional data sections to include: trends, daily_detail. See Optional Includes.

    Available Models: ARPEGE, ECMWF-IFS, GEFS, GEM-GDPS, GEM-HRDPS, GFS, GFS-MOS, HRRR, ICON, JMA, LAV-MOS, NAM, NAM-MOS, NAM4KM, NBM, NBS-MOS, NWS, RAP, UKMO

    Note: Not all models have data for all run times or all stations. The response will only include models and runs that have available data within the requested window.

    Example Requests

    # Default: all models, 7-day window, core metrics only
    GET /api/v2/model_accuracy.php?station_code=KORD
    
    # Specific models and window
    GET /api/v2/model_accuracy.php?station_code=KMIA&window=30d&model=GFS,HRRR,ECMWF-IFS
    
    # Filter by run time
    GET /api/v2/model_accuracy.php?station_code=KDTW&window=14d&model=GFS&run_time=12z,18z
    
    # Include trend data and daily detail
    GET /api/v2/model_accuracy.php?station_code=KAUS&window=7d&include=trends,daily_detail

    Example Response (Default — No Includes)

    {
        "station": "KDTW",
        "city": "Detroit, MI",
        "computed_at": "2026-03-15T02:15:23.286094",
        "window": "7d",
        "models": ["HRRR", "GFS", "ECMWF-IFS"],
        "days_available": 6,
        "ensemble": {
            "mae": 3.94,
            "bias": 3.94,
            "rmse": 4.77,
            "n": 6
        },
        "per_model": {
            "HRRR": {"mae": 1.79, "bias": 1.61, "rmse": 2.17, "n": 6},
            "GFS": {"mae": 3.32, "bias": 2.88, "rmse": 4.19, "n": 6},
            "ECMWF-IFS": {"mae": 3.83, "bias": 3.17, "rmse": 4.53, "n": 6}
        },
        "per_model_run": {
            "HRRR": {
                "13z": {"mae": 1.77, "bias": 1.17, "rmse": 2.37, "n": 6},
                "16z": {"mae": 1.14, "bias": 0.75, "rmse": 1.32, "n": 4},
                "18z": {"mae": 0.78, "bias": 0.23, "rmse": 0.82, "n": 4}
            },
            "GFS": {
                "06z": {"mae": 3.5, "bias": 3.5, "rmse": 4.41, "n": 6},
                "12z": {"mae": 3.87, "bias": 3.25, "rmse": 5.11, "n": 5},
                "18z": {"mae": 2.85, "bias": 2.85, "rmse": 3.32, "n": 2}
            },
            "ECMWF-IFS": {
                "06z": {"mae": 4.33, "bias": 4.0, "rmse": 5.4, "n": 6},
                "12z": {"mae": 3.71, "bias": 2.57, "rmse": 4.26, "n": 5}
            }
        }
    }

    Note: This example shows a model=HRRR,GFS,ECMWF-IFS filtered response. Without the model filter, all 19 tracked models would be returned.

    Response Fields

    Top-Level Fields

    FieldTypeDescription
    stationstringICAO station code
    citystringHuman-readable city name
    computed_atstringISO 8601 timestamp of when the accuracy data was last computed
    windowstringTime window requested (7d, 14d, 30d, 90d)
    modelsarrayList of model names included in this response (affected by model filter)
    days_availableintegerNumber of days with valid data in this window

    ensemble

    Aggregate accuracy metrics across all models for the requested window.

    FieldTypeDescription
    maedecimalMean Absolute Error (°F or °C) — average distance between forecast and actual, ignoring direction
    biasdecimalAverage signed error — positive = model ran cold (under-forecast), negative = model ran warm (over-forecast)
    rmsedecimalRoot Mean Square Error — like MAE but penalizes large misses more heavily
    nintegerNumber of valid forecast-observation pairs

    per_model

    Object keyed by model name. Each value contains the same mae, bias, rmse, n fields as the ensemble, but scoped to that specific model across all run times.

    per_model_run

    Object keyed by model name, then by run time label (e.g., 00z, 06z, 12z, 18z). Each run time entry contains mae, bias, rmse, n for that specific model + run time combination.

    Run time labels are in UTC (e.g., 12z = 12:00 UTC model initialization). Available run times vary by model — for example, HRRR may have 13z, 16z, 18z while GFS has 06z, 12z, 18z.


    Optional Includes

    By default, the response contains only the core metrics (per_model, per_model_run, ensemble) for the requested window. Use the include parameter to add heavier data sections. All optional includes respect the model filter — only data for requested models is returned.

    trends

    Rolling 7-day MAE and bias over time, keyed by model name. Each entry is an array of data points:

    {
        "trends": {
            "HRRR": [
                {"date": "2025-12-25", "mae_7d": 0.39, "bias_7d": -0.39, "n": 2},
                {"date": "2025-12-26", "mae_7d": 0.55, "bias_7d": -0.10, "n": 3},
                ...
            ],
            "GFS": [ ... ]
        }
    }

    Typically contains ~80 data points per model. Useful for charting model performance over time.

    daily_detail

    Last 30 days of actual vs. predicted high temperatures for each model, including which run time was used:

    {
        "daily_detail": [
            {
                "date": "2026-03-14",
                "actual_high": 41,
                "models": {
                    "HRRR": 41.5,
                    "GFS": 40.5,
                    "ECMWF-IFS": 39.7
                },
                "run_times": {
                    "HRRR": "18z",
                    "GFS": "18z",
                    "ECMWF-IFS": "12z"
                }
            },
            ...
        ]
    }

    Payload Size Reference

    Approximate response sizes for a typical station (19 models):

    RequestSize
    Default (no includes)~4 KB
    2 models filtered, no includes~400 bytes
    All includes, no model filter~120 KB

    Client Examples

    cURL — Default (7-Day, All Models)

    curl -G "https://wethr.net/api/v2/model_accuracy.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KORD"

    cURL — Filtered by Model and Run Time

    curl -G "https://wethr.net/api/v2/model_accuracy.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KDTW" \
      --data-urlencode "window=30d" \
      --data-urlencode "model=GFS,HRRR,ECMWF-IFS" \
      --data-urlencode "run_time=12z,18z"

    cURL — With Trend Data

    curl -G "https://wethr.net/api/v2/model_accuracy.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KAUS" \
      --data-urlencode "window=90d" \
      --data-urlencode "model=HRRR,NBM" \
      --data-urlencode "include=trends"

    Python — Compare Models Across Windows

    import requests
    
    api_key = 'YOUR_API_KEY'
    headers = {'Authorization': f'Bearer {api_key}'}
    
    for window in ['7d', '14d', '30d', '90d']:
        resp = requests.get(
            'https://wethr.net/api/v2/model_accuracy.php',
            params={
                'station_code': 'KORD',
                'window': window,
                'model': 'GFS,HRRR,ECMWF-IFS'
            },
            headers=headers
        )
        data = resp.json()
        print(f"\n--- {window} ({data['days_available']} days) ---")
        for model, stats in data['per_model'].items():
            print(f"  {model}: MAE={stats['mae']:.1f}° Bias={stats['bias']:+.1f}° (n={stats['n']})")

    JavaScript — Fetch with Daily Detail

    const params = new URLSearchParams({
        station_code: 'KMIA',
        window: '30d',
        model: 'HRRR,NBM,GFS',
        include: 'daily_detail'
    });
    
    fetch(`/api/v2/model_accuracy.php?${params}`, {
        headers: { 'Authorization': `Bearer ${apiKey}` }
    })
    .then(res => res.json())
    .then(data => {
        console.log(`Station: ${data.station} (${data.city})`);
        console.log(`Ensemble MAE: ${data.ensemble.mae}°F`);
    
        // Show each model's performance
        for (const [model, stats] of Object.entries(data.per_model)) {
            console.log(`  ${model}: MAE=${stats.mae}° Bias=${stats.bias > 0 ? '+' : ''}${stats.bias}°`);
        }
    
        // Show daily predictions vs actual
        data.daily_detail.forEach(day => {
            const preds = Object.entries(day.models)
                .map(([m, p]) => `${m}=${p}°`)
                .join(', ');
            console.log(`  ${day.date}: Actual=${day.actual_high}° | ${preds}`);
        });
    });
  • ⚠️ API Version 1 (Legacy) — This version is maintained for backward compatibility. New integrations should use API v2 for expanded features and station support. Base URL: https://wethr.net/api/v1/

    Introduction

    The Wethr.net API v1 provides access to weather observation data and model forecasts. This version supports 24 US stations with all temperatures in Fahrenheit.

    Base URL: https://wethr.net/api/v1/


    Authentication

    Include your API key in the request header:

    Authorization: Bearer <YOUR_API_KEY>

    Or:

    X-API-Key: <YOUR_API_KEY>

    Rate Limiting

    TierRequests/MinuteRequests/Day
    Professional605,000
    Developer30050,000
    Enterprise30050,000

    Error Responses

    • 400 Bad Request — Invalid parameters
    • 401 Unauthorized — Invalid API key
    • 429 Too Many Requests — Rate limit exceeded
    • 500 Server Error — Internal error

    Observations API

    GET /api/v1/observations.php

    Mode: Latest Observation

    Returns the most recent observation.

    GET /api/v1/observations.php?station_code=KMDW&mode=latest

    Example Response

    {
        "station_code": "KMDW",
        "temperature": 26.7,
        "observation_time": "2025-06-15T18:53:00Z",
        "lowest_probable_f": 80,
        "highest_probable_f": 80,
        "dsm_high_f": 83,
        "relative_humidity": 55.2
    }

    Mode: Wethr High

    Returns the calculated trading-day high. Logic is determined automatically by station type.

    GET /api/v1/observations.php?station_code=KMDW&mode=wethr_high

    Example Response

    {
        "station_code": "KMDW",
        "date": "2025-06-15",
        "wethr_high": 83,
        "time_of_high_utc": "2025-06-15 20:53:00",
        "calculation_logic": "standard"
    }

    Mode: History

    Retrieves observations over a time range (max 24 hours).

    GET /api/v1/observations.php?station_code=KMDW&start_time=2025-06-15T00:00:00Z&end_time=2025-06-15T12:00:00Z

    Observation Data Model

    FieldTypeDescription
    station_codestringStation identifier
    observation_timedatetimeUTC timestamp
    temperaturedecimalTemperature (Celsius)
    temperature_fdecimalTemperature (Fahrenheit)
    lowest_probable_fintegerMin probable temp (°F)
    highest_probable_fintegerMax probable temp (°F)
    dew_pointdecimalDew point (Celsius)
    relative_humiditydecimalRelative humidity (%)
    cli_high / cli_high_fdecimal/intCLI report high
    dsm_high / dsm_high_fdecimal/intDSM report high
    six_hour_highdecimal6-hour high (Celsius)
    wind_speeddecimalWind speed (knots)
    visibilitydecimalVisibility (miles)

    Forecasts API

    GET /api/v1/forecasts.php

    Parameters

    ParameterRequiredDescription
    location_nameYesLocation identifier
    start_valid_timeYesStart of range (ISO 8601)
    end_valid_timeYesEnd of range (ISO 8601)
    modelNoModel filter

    Available Models: ARPEGE, ECMWF-IFS, GEFS, GEM-GDPS, GEM-HRDPS, GFS, GFS-MOS, HRRR, ICON, JMA, LAV-MOS, NAM, NAM-MOS, NAM4KM, NBM, NBS-MOS, RAP, UKMO

    Example Request

    GET /api/v1/forecasts.php?location_name=KMDW&start_valid_time=2025-06-15T18:00:00Z&end_valid_time=2025-06-15T20:00:00Z&model=HRRR

    Forecast Data Model

    FieldTypeDescription
    modelstringModel name
    location_namestringLocation
    run_timedatetimeModel run time
    valid_timedatetimeForecast valid time
    forecast_hourintegerLead time (hours)
    temperature_kdecimalTemp (Kelvin)
    temperature_fdecimalTemp (Fahrenheit)
    temperature_cdecimalTemp (Celsius)

    Client Examples

    cURL — Latest Observation

    curl -G "https://wethr.net/api/v1/observations.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KMDW" \
      --data-urlencode "mode=latest"

    cURL — Wethr High

    curl -G "https://wethr.net/api/v1/observations.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KMDW" \
      --data-urlencode "mode=wethr_high"

    cURL — History

    curl -G "https://wethr.net/api/v1/observations.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "station_code=KMDW" \
      --data-urlencode "start_time=2025-06-15T00:00:00Z" \
      --data-urlencode "end_time=2025-06-15T12:00:00Z"

    cURL — Forecasts

    curl -G "https://wethr.net/api/v1/forecasts.php" \
      -H "Authorization: Bearer <YOUR_API_KEY>" \
      --data-urlencode "location_name=KMDW" \
      --data-urlencode "start_valid_time=2025-06-15T18:00:00Z" \
      --data-urlencode "end_valid_time=2025-06-15T20:00:00Z" \
      --data-urlencode "model=HRRR"
Top