Build with the WeatherCore API.
A small, predictable JSON API: current conditions, a 16-day daily and hourly forecast, historical daily weather and derived alerts. Every endpoint is a GET that takes a latitude and longitude.
Authentication
Create a key in your dashboard and send it as a bearer token on every request.
Authorization: Bearer wc_live_xxxxxxxxxxxxxxxxxxxxxxxx
- Keys start with
wc_live_. We store only a hash, so a lost key can't be recovered; revoke it and create a new one. - Keep keys on your server. The API allows browser calls from any origin, but a key in front-end code is visible to every visitor.
- A missing, wrong or revoked key returns
401 unauthenticated.
Quickstart
Fetch a 7-day forecast for New York City.
curl -G "https://weathercore-api.weathercore.workers.dev/v1/forecast/daily" \
-H "Authorization: Bearer $WEATHERCORE_KEY" \
--data-urlencode "lat=40.7128" \
--data-urlencode "lon=-74.006" \
--data-urlencode "days=7"const res = await fetch(
"https://weathercore-api.weathercore.workers.dev/v1/forecast/daily?lat=40.7128&lon=-74.006&days=7",
{ headers: { Authorization: `Bearer ${process.env.WEATHERCORE_KEY}` } }
);
if (!res.ok) throw new Error((await res.json()).error.message);
const { forecast } = await res.json();
console.log(forecast[0].high, forecast[0].description);import os, requests
r = requests.get(
"https://weathercore-api.weathercore.workers.dev/v1/forecast/daily",
params={"lat": 40.7128, "lon": -74.006, "days": 7},
headers={"Authorization": f"Bearer {os.environ['WEATHERCORE_KEY']}"},
timeout=10,
)
r.raise_for_status()
print(r.json()["forecast"][0])
Conventions
- Units. Metric by default (°C, km/h, mm). Pass
units=imperialfor °F, mph and inches. Theunitsobject in each response tells you what you got. - Coordinates. Rounded to two decimals (about 1 km). The response echoes the grid point actually used in
latandlon. - Time. Dates and times in forecasts are local to the location, whose IANA name is in
timezone./v1/currentalso returnsobservedAtas a UTC ISO timestamp. - Nulls. A value the model doesn't provide is
null, never omitted.
/v1/current
Observed-style conditions for a coordinate right now, including the real UTC instant of the observation.
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat | number | required | Latitude, -90 to 90. |
| lon | number | required | Longitude, -180 to 180. |
| units | string | optional | metric (default) or imperial. |
{
"lat": 40.71,
"lon": -74.01,
"timezone": "America/New_York",
"observedAt": "2026-10-15T18:00:00.000Z",
"observedAtLocal": "2026-10-15T14:00",
"temp": 16.8,
"feelsLike": 15.2,
"humidity": 58,
"precipAmount": 0,
"cloudCover": 88,
"pressure": 1016.4,
"windSpeed": 14.1,
"windDirection": 250,
"windGusts": 27.4,
"isDay": true,
"code": 3,
"condition": "overcast",
"description": "Overcast",
"units": {
"temperature": "°C",
"wind": "km/h",
"precipitation": "mm"
}
}
pressure is mean sea-level pressure in hPa, windDirection is degrees (0–360, direction the wind comes from), cloudCover and humidity are percentages.
/v1/forecast/daily
Up to 16 days of daily highs, lows, precipitation, wind, UV and sun times.
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat | number | required | Latitude, -90 to 90. |
| lon | number | required | Longitude, -180 to 180. |
| units | string | optional | metric (default) or imperial. |
| days | integer | optional | 1–16. Default 16. Out-of-range values are clamped. |
{
"lat": 40.71,
"lon": -74.01,
"timezone": "America/New_York",
"elevation": 10,
"days": 2,
"units": {
"temperature": "°C",
"wind": "km/h",
"precipitation": "mm"
},
"forecast": [
{
"date": "2026-10-15",
"code": 3,
"condition": "overcast",
"description": "Overcast",
"high": 17.4,
"low": 11.2,
"precipChance": 20,
"precipAmount": 0,
"windMax": 18.5,
"gustMax": 34.2,
"uvIndexMax": 3.6,
"sunrise": "2026-10-15T07:06",
"sunset": "2026-10-15T18:00"
},
{
"date": "2026-10-16",
"code": 61,
"condition": "rain",
"description": "Slight rain",
"high": 15.1,
"low": 10.4,
"precipChance": 65,
"precipAmount": 4.2,
"windMax": 22.7,
"gustMax": 41.0,
"uvIndexMax": 2.1,
"sunrise": "2026-10-16T07:07",
"sunset": "2026-10-16T17:58"
}
]
}
precipChance can be null late in the window when the underlying model does not publish it. sunrise/sunset are local times at the location. Sample truncated to 2 of N days.
/v1/forecast/hourly
Hour-by-hour forecast starting from the current hour.
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat | number | required | Latitude, -90 to 90. |
| lon | number | required | Longitude, -180 to 180. |
| units | string | optional | metric (default) or imperial. |
| hours | integer | optional | 1–384 (16 days). Default 48. Out-of-range values are clamped. |
{
"lat": 40.71,
"lon": -74.01,
"timezone": "America/New_York",
"hours": 2,
"units": {
"temperature": "°C",
"wind": "km/h",
"precipitation": "mm"
},
"hourly": [
{
"time": "2026-10-15T14:00",
"hour": 14,
"temp": 16.8,
"feelsLike": 15.2,
"humidity": 58,
"precipChance": 10,
"precipAmount": 0,
"code": 3,
"condition": "overcast",
"description": "Overcast",
"windSpeed": 14.1,
"windGusts": 27.4
},
{
"time": "2026-10-15T15:00",
"hour": 15,
"temp": 17.1,
"feelsLike": 15.6,
"humidity": 56,
"precipChance": 10,
"precipAmount": 0,
"code": 2,
"condition": "partly_cloudy",
"description": "Partly cloudy",
"windSpeed": 15.0,
"windGusts": 28.8
}
]
}
time is local to the location; hour is its 0–23 hour. Sample truncated to 2 of N hours.
/v1/history
Daily observations from the ERA5 reanalysis archive for any date range up to 366 days per request.
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat | number | required | Latitude, -90 to 90. |
| lon | number | required | Longitude, -180 to 180. |
| units | string | optional | metric (default) or imperial. |
| start | date | optional | YYYY-MM-DD. Default: 6 days before end. |
| end | date | optional | YYYY-MM-DD. Default: yesterday (UTC). |
{
"lat": 40.71,
"lon": -74.01,
"timezone": "America/New_York",
"start": "2026-10-08",
"end": "2026-10-09",
"units": {
"temperature": "°C",
"wind": "km/h",
"precipitation": "mm"
},
"history": [
{
"date": "2026-10-08",
"code": 2,
"condition": "partly_cloudy",
"description": "Partly cloudy",
"high": 19.6,
"low": 12.3,
"precipAmount": 0,
"windMax": 16.4
},
{
"date": "2026-10-09",
"code": 63,
"condition": "rain",
"description": "Moderate rain",
"high": 16.2,
"low": 11.8,
"precipAmount": 9.7,
"windMax": 24.9
}
]
}
start must be on or before end, and the range can be at most 366 days. Sample truncated to 2 days.
/v1/alerts
Threshold-based heads-ups computed from the next 16 days of forecast. These are not official government warnings, and the response says so.
| Parameter | Type | Required | Description |
|---|---|---|---|
| lat | number | required | Latitude, -90 to 90. |
| lon | number | required | Longitude, -180 to 180. |
{
"lat": 40.71,
"lon": -74.01,
"timezone": "America/New_York",
"alerts": [
{
"id": "wind-2026-10-19",
"type": "wind",
"severity": "watch",
"date": "2026-10-19",
"title": "Strong wind gusts",
"detail": "Gusts up to 56 km/h.",
"value": 56,
"threshold": 50
}
],
"derived": true,
"disclaimer": "Generated automatically from forecast thresholds (heavy rain, heat, frost, wind, UV, thunderstorms). These are not official government warnings."
}
Thresholds are metric and this endpoint takes no units parameter. alerts is an empty array when nothing crosses a threshold.
| type | watch | warning | Based on |
|---|---|---|---|
| heavy_rain | ≥ 25 mm | ≥ 50 mm | Daily precipitation total |
| heat | ≥ 35 °C | ≥ 38 °C | Daily high |
| frost | ≤ 0 °C | ≤ -10 °C | Daily low |
| wind | ≥ 50 km/h | ≥ 70 km/h | Daily max gust |
| uv | ≥ 8 | ≥ 11 | Daily max UV index |
| thunderstorm | code 95 | code 96+ | Weather code (96+ means hail) |
Condition codes
Each period carries a numeric WMO code, a stable condition key for your icons and logic, and a human description.
| condition | WMO code | Meaning |
|---|---|---|
| clear | 0 | Clear sky |
| mostly_clear | 1 | Mainly clear |
| partly_cloudy | 2 | Partly cloudy |
| overcast | 3 | Overcast |
| fog | 45, 48 | Fog, depositing rime fog |
| drizzle | 51, 53, 55 | Light to dense drizzle |
| freezing_drizzle | 56, 57 | Freezing drizzle |
| rain | 61, 63, 65 | Slight to heavy rain |
| freezing_rain | 66, 67 | Freezing rain |
| snow | 71, 73, 75, 77 | Snow and snow grains |
| showers | 80, 81, 82 | Rain showers |
| snow_showers | 85, 86 | Snow showers |
| thunderstorm | 95 | Thunderstorm |
| thunderstorm_hail | 96, 99 | Thunderstorm with hail |
| unknown | — | Code not recognized |
Rate limits
Quotas are per account per UTC day and reset at 00:00 UTC. Every authenticated call counts, including ones that return an error.
| Plan | Daily allowance |
|---|---|
| Developer | 1,000 calls / day |
| Startup | 100,000 calls / day |
| Business | 1,000,000 calls / day |
| Licensed accounts | Not metered |
Metered responses include these headers:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Calls allowed today. |
| X-RateLimit-Remaining | Calls left today. |
| X-RateLimit-Reset | Unix time (seconds) when the counter resets. |
| Retry-After | Seconds to wait. Sent on 429 and 502 responses. |
Errors
Errors use a normal HTTP status and a consistent body.
{
"error": {
"code": "invalid-argument",
"message": "`lat` (-90 to 90) and `lon` (-180 to 180) are required query parameters."
}
}
| Status | code | When |
|---|---|---|
| 400 | invalid-argument | A parameter is missing or out of range. The message says which. |
| 401 | unauthenticated | Missing, invalid or revoked API key. |
| 429 | resource-exhausted | Daily quota reached. Check Retry-After. |
| 502 | provider-unavailable | Upstream weather data is unreachable or rate-limited. Retry after 30 s. |
| 500 | internal | Unexpected error on our side. Safe to retry. |
{
"error": {
"code": "unauthenticated",
"message": "Missing, invalid, or revoked API key."
}
}
{
"error": {
"code": "resource-exhausted",
"message": "Daily quota of 1,000 calls reached for your plan. It resets at 00:00 UTC; upgrade your plan for a higher limit."
}
}
{
"error": {
"code": "provider-unavailable",
"message": "The weather data provider is unreachable. Please retry shortly."
}
}
Try it
Call the live API from your browser and see the exact response. Your key is sent only to the API and is never stored.
No key yet? Create one in the dashboard.