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.
- 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.
- 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>
<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
/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 |
---|---|---|
id | integer | Unique identifier for the observation record. |
station_code | string | Identifier of the reporting station. |
temperature | number (decimal) | Temperature in Celsius. |
six_hour_high | number (decimal) | Highest temperature in the previous 6 hours (Celsius). |
twentyfour_hour_high | number (decimal) | Highest temperature in the previous 24 hours (Celsius). |
cli_high | number (decimal) | Climate day high temperature (calendar day, Celsius). |
dsm_high | number (decimal) | Midnight-to-midnight high temperature (Celsius). |
precision_level | integer | Indicator of data precision/source type. |
observation_time | string (datetime) | Timestamp of the observation (UTC, e.g., YYYY-MM-DD HH:MM:SS ). |
dew_point | number (decimal) | Dew point temperature (Celsius). |
wind_direction | string | Wind direction (e.g., degrees "180", "VRB"). |
wind_speed | number (decimal) | Wind speed (knots). |
wind_gust | number (decimal) | Wind gust (knots), if any. |
lowest_probable_f | integer | The calculated minimum probable temperature in Fahrenheit, based on the precision of the source Celsius reading. Null if source temperature is null. |
highest_probable_f | integer | The 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}")