API Documentation

Introduction

Welcome to the Wethr.net Weather Data API! This API provides programmatic access to historical weather observation data collected from various stations. To use this API, you need an API key associated with your Wethr.net account.

Base URL: https://wethr.net (The specific endpoint path is listed below)


Authentication

All requests to the Wethr.net API must be authenticated using an API key associated with your account.

  1. Obtaining an API Key: Log in to your Wethr.net account and navigate to the My Account page. You can generate and manage your API keys in the "API Keys" section. Please copy your key immediately upon generation, as it will not be shown again.
  2. Using the API Key: Include your API key in the HTTP request header. Two methods are supported:
    • Bearer Token (Recommended):

      Authorization: Bearer <YOUR_API_KEY>
    • Custom Header:

      X-API-Key: <YOUR_API_KEY>
    Replace <YOUR_API_KEY> with the actual key you generated.

Failure to provide a valid, enabled API key will result in a 401 Unauthorized error.


Rate Limiting

To ensure fair usage and server stability, API requests are rate-limited based on the subscription tier associated with your API key. Limits are applied per API key.

Tier Requests per Minute Requests per Day
Professional 60 5,000
Enterprise 300 50,000

Note: Daily limits reset based on a fixed time window (e.g., UTC midnight). Per-minute limits use a sliding window where possible.

If you exceed the rate limit for your key's tier, the API will respond with an HTTP 429 Too Many Requests error. You may receive a Retry-After header indicating when you can make another request.

(Rate limiting implementation is based on the design; confirm specific reset times and window types if critical).


Endpoints

Get Weather Observations

GET /api/v1/observations.php

Retrieves a list of weather observations for a specific station within a defined time range.

Parameters (Query String)

Parameter Type Required Description Example
station_code string Yes The unique identifier for the weather station (3-10 alphanumeric characters). KDTO
start_time string Yes The beginning of the observation time range (inclusive). ISO 8601 format recommended (UTC 'Z' preferred). 2025-04-18T00:00:00Z
end_time string Yes The end of the observation time range (inclusive). ISO 8601 format recommended. Must be after start_time. 2025-04-18T06:00:00Z

Request Headers

Header Required Description Example
Authorization Yes (or X-API-Key) Contains the API key as a Bearer token. Bearer <YOUR_API_KEY>
X-API-Key Yes (or Authorization) Contains the API key directly. <YOUR_API_KEY>
Accept No (recommended) Informs the server the client expects JSON. application/json

Successful Response (200 OK)

Returns a JSON array containing observation objects matching the query. If no observations match, returns an empty array [].

  • Content-Type: application/json
  • Body: Array<Observation>

Example Success Body:

[
    {
        "id": 12345,
        "station_code": "KDTO",
        "temperature": 25.50,
        "six_hour_high": 26.00,
        "twentyfour_hour_high": 28.00,
        "cli_high": 27.50,
        "dsm_high": 27.80,
        "precision_level": 1,
        "observation_time": "2025-04-18T05:53:00",
        "dew_point": 15.00,
        "wind_direction": "180",
        "wind_speed": 10.00,
        "wind_gust": 15.00,
        "lowest_probable_f": 78,
        "highest_probable_f": 78
    },
    {
        "id": 12346,
        "station_code": "KDTO",
        "temperature": 25.70,
        "six_hour_high": null,
        "twentyfour_hour_high": 28.00,
        "cli_high": null,
        "dsm_high": null,
        "precision_level": 1,
        "observation_time": "2025-04-18T06:00:00",
        "dew_point": 15.10,
        "wind_direction": "170",
        "wind_speed": 11.00,
        "wind_gust": null,
        "lowest_probable_f": 79,
        "highest_probable_f": 79
    }
]

Error Responses

  • 400 Bad Request: Problem with the request parameters.

    Example Body:

    {
        "error": "Missing required parameters.",
        "details": [
            "start_time is required."
        ]
    }
  • 401 Unauthorized: API key is missing, invalid, or disabled.

    Example Body:

    {
        "error": "Invalid or disabled API key."
    }
  • 429 Too Many Requests: Rate limit exceeded for the API key's tier.

    Example Body:

    {
        "error": "Rate limit exceeded for tier: Professional. Limit: 60 req/min."
    }

    May include a Retry-After header indicating seconds to wait.

  • 500 Internal Server Error: An unexpected error occurred on the server.

    Example Body:

    {
        "error": "An internal server error occurred. Please contact support if the issue persists."
    }

Data Models

Observation Object

Represents a single weather observation record returned by the API.

Field Type Description
idintegerUnique identifier for the observation record.
station_codestringIdentifier of the reporting station.
temperaturenumber (decimal)Temperature in Celsius.
six_hour_highnumber (decimal)Highest temperature in the previous 6 hours (Celsius).
twentyfour_hour_highnumber (decimal)Highest temperature in the previous 24 hours (Celsius).
cli_highnumber (decimal)Climate day high temperature (calendar day, Celsius).
dsm_highnumber (decimal)Midnight-to-midnight high temperature (Celsius).
precision_levelintegerIndicator of data precision/source type.
observation_timestring (datetime)Timestamp of the observation (UTC, e.g., YYYY-MM-DD HH:MM:SS).
dew_pointnumber (decimal)Dew point temperature (Celsius).
wind_directionstringWind direction (e.g., degrees "180", "VRB").
wind_speednumber (decimal)Wind speed (knots).
wind_gustnumber (decimal)Wind gust (knots), if any.
lowest_probable_fintegerThe calculated minimum probable temperature in Fahrenheit, based on the precision of the source Celsius reading. Null if source temperature is null.
highest_probable_fintegerThe calculated maximum probable temperature in Fahrenheit, based on the precision of the source Celsius reading. Null if source temperature is null.

Note: Unless specified otherwise, temperature units are in Celsius and wind speed units are in knots. Returned fields match the database columns explicitly selected in the query, with the addition of calculated Fahrenheit values.


Client Examples

Replace <YOUR_API_KEY> with your actual API key and adjust parameters as needed.

cURL

# Using Authorization Header
curl -G "https://wethr.net/api/v1/observations.php" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  --data-urlencode "station_code=KDTO" \
  --data-urlencode "start_time=2025-04-18T00:00:00Z" \
  --data-urlencode "end_time=2025-04-18T06:00:00Z"

# Using X-API-Key Header
curl -G "https://wethr.net/api/v1/observations.php" \
  -H "X-API-Key: <YOUR_API_KEY>" \
  --data-urlencode "station_code=KDTO" \
  --data-urlencode "start_time=2025-04-18T00:00:00Z" \
  --data-urlencode "end_time=2025-04-18T06:00:00Z"

JavaScript (fetch)

const apiKey = '<YOUR_API_KEY>';
const station = 'KDTO';
const startTime = '2025-04-18T00:00:00Z';
const endTime = '2025-04-18T06:00:00Z';

const params = new URLSearchParams({
    station_code: station,
    start_time: startTime,
    end_time: endTime
});

// Ensure the path is correct for your setup
const url = `/api/v1/observations.php?${params.toString()}`;

fetch(url, {
    method: 'GET',
    headers: {
        'Authorization': `Bearer ${apiKey}`,
        // Or: 'X-API-Key': apiKey,
        'Accept': 'application/json'
    }
})
.then(response => {
    if (!response.ok) {
        console.error(`Error: ${response.status} ${response.statusText}`);
        // Attempt to parse error body for more info
        return response.json().catch(() => response.text()).then(errData => Promise.reject(errData));
    }
    return response.json();
})
.then(data => {
    console.log("Observations:", data);
    // Process the array of observation objects
})
.catch(error => {
    console.error("API Request Failed:", error);
    // error might be the parsed JSON error body or a network error
});

PHP (cURL)

<?php

$apiKey = '<YOUR_API_KEY>';
$station = 'KDTO';
$startTime = '2025-04-18T00:00:00Z';
$endTime = '2025-04-18T06:00:00Z';

$queryParams = http_build_query([
    'station_code' => $station,
    'start_time' => $startTime,
    'end_time' => $endTime
]);

$url = "https://wethr.net/api/v1/observations.php?" . $queryParams; // Use your domain

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15); // Increased timeout slightly
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . $apiKey,
    // Or: "X-API-Key: " . $apiKey,
    "Accept: application/json",
    "User-Agent: WethrNet-PHPScript/1.0" // Good practice
]);
// Verify SSL Peer and Host in production
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// May need: curl_setopt($ch, CURLOPT_CAINFO, '/path/to/your/cacert.pem');

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErrorNum = curl_errno($ch);
$curlErrorMsg = curl_error($ch);

curl_close($ch);

if ($curlErrorNum > 0) {
    echo "cURL Error ({$curlErrorNum}): [{$curlErrorMsg}]";
} elseif ($httpCode >= 400) {
    echo "HTTP Error: [{$httpCode}]\n";
    echo "Response Body: [" . $response . "]\n";
    $errorData = json_decode($response, true);
    if ($errorData && isset($errorData['error'])) {
         echo "API Error Message: [" . $errorData['error'] . "]\n";
         if (isset($errorData['details'])) print_r($errorData['details']);
    }
} else {
    echo "Success! HTTP Code: [{$httpCode}]\n";
    $observations = json_decode($response, true);
    if (json_last_error() === JSON_ERROR_NONE) {
        echo "Decoded Data (" . count($observations) . " observations found):\n";
        print_r($observations);
    } else {
        echo "Failed to decode JSON response. Raw response:\n" . $response;
    }
}

?>

Python (requests)

import requests
import json

api_key = '<YOUR_API_KEY>'
# Ensure the URL path is correct
api_url = 'https://wethr.net/api/v1/observations.php'

params = {
    'station_code': 'KDTO',
    'start_time': '2025-04-18T00:00:00Z',
    'end_time': '2025-04-18T06:00:00Z'
}

headers = {
    'Authorization': f'Bearer {api_key}',
    # Or: 'X-API-Key': api_key,
    'Accept': 'application/json',
    'User-Agent': 'WethrNet-PythonScript/1.0' # Good practice
}

try:
    # Consider SSL verification needs (verify=True is default and recommended)
    response = requests.get(
        api_url,
        headers=headers,
        params=params,
        timeout=15 # Slightly longer timeout
        # verify=True # Or path to cert bundle if needed
    )
    response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)

    observations = response.json()
    print(f"Success! Found {len(observations)} observations.")
    # Pretty print the first observation if available
    if observations:
        print("\nFirst Observation:")
        print(json.dumps(observations[0], indent=2))

except requests.exceptions.Timeout:
     print("Request timed out.")
except requests.exceptions.ConnectionError as e:
     print(f"Connection error: {e}")
except requests.exceptions.HTTPError as e:
    # Handle HTTP errors (4xx, 5xx) raised by raise_for_status()
    print(f"HTTP Error: {e.response.status_code} {e.response.reason}")
    try:
        # Attempt to show JSON error body from server
        error_details = e.response.json()
        print("Server Error Details:")
        print(json.dumps(error_details, indent=2))
    except json.JSONDecodeError:
        # If response wasn't JSON, show text
        print(f"Server Response Body: {e.response.text}")
except requests.exceptions.RequestException as e:
    # Catch other requests exceptions
    print(f"An unexpected request error occurred: {e}")

Top