_ ___ ___ ___ ___ ___ ___ /_\ | _ \_ _| | \ / _ \ / __/ __| / _ \| _/| | | |) | (_) | (__\__ \ /_/ \_\_| |___| |___/ \___/ \___|___/
Base URL http://localhost:3000 Auth none. No API key, no signup, no quota registration. Format JSON only (application/json; charset=utf-8) CORS Access-Control-Allow-Origin: * (browser calls work directly) Methods GET, HEAD, OPTIONS Upstream open-meteo.com (free, non-commercial-friendly, CC BY 4.0) Caching weather responses cached 10 min, geocoding cached 24 h Rate limit 60 requests / 60 s per IP (headers: X-RateLimit-*) Every endpoint answers with a top level"ok"boolean. On failure the body is{ "ok": false, "error": { "code": "...", "message": "..." } }and the HTTP status is 400 / 404 / 429 / 5xx.
GET /api/weather current conditions + daily forecast + hourly series ?city=Warsaw ?lat=52.2297&lon=21.0122 GET /api/forecast alias of /api/weather (identical payload) GET /api/current current conditions only (no daily / hourly arrays) ?city=Tokyo GET /api/geocode resolve a place name to coordinates ?q=Lisbon&count=5 GET /api/health service status, cache statistics, counters GET /api machine readable endpoint index
NAME TYPE DEFAULT APPLIES TO DESCRIPTION
--------------------------------------------------------------------------------------------
city string - weather,forecast, City name. Resolved through the geocoder;
current the best match is used. Alias: q, name.
lat number - weather,forecast, Latitude -90 .. 90. Alias: latitude.
current
lon number - weather,forecast, Longitude -180 .. 180. Alias: lng, longitude.
current
days integer 7 weather,forecast Forecast days, 1 .. 16.
hours integer 24 weather,forecast Hourly entries returned, 0 .. 48.
units string metric all weather calls "metric" (C, km/h, mm) or
"imperial" (F, mph, inch).
timezone string auto all weather calls IANA name, e.g. Europe/Warsaw, or "auto"
(resolved from the coordinates). Alias: tz.
lang string en city lookup Geocoder language, e.g. en, de, ru, pl.
q string - geocode Place name to search (required).
count integer 8 geocode Results returned, 1 .. 20.
Either city OR the pair lat + lon is required for weather calls.
If both are given, city wins.
{
"ok": true,
"location": {
"name": "Warsaw", "country": "Poland", "country_code": "PL", "admin1": "Mazovia",
"latitude": 52.23, "longitude": 21.01, "elevation": 113.0, "population": 1702139,
"timezone": "Europe/Warsaw", "timezone_abbreviation": "GMT+2", "utc_offset_seconds": 7200
},
"units": { "temperature": "C", "wind_speed": "km/h", "pressure": "hPa",
"precipitation": "mm", "humidity": "%" },
"current": {
"time": "2026-08-22T19:30", "is_day": 1,
"weather_code": 61, "condition": "RAIN", "description": "Slight rain",
"temperature": 18.4, "apparent_temperature": 17.9, "humidity": 71,
"pressure": 1013.2, "surface_pressure": 1000.8, "cloud_cover": 84,
"precipitation": 0.4, "rain": 0.4, "showers": 0.0, "snowfall": 0.0,
"wind_speed": 12.4, "wind_gusts": 28.1, "wind_direction": 215,
"wind_direction_cardinal": "SW",
"sunrise": "2026-08-22T05:42", "sunset": "2026-08-22T19:58", "uv_index_max": 5.1
},
"daily": [
{ "date": "2026-08-22", "weather_code": 61, "condition": "RAIN",
"description": "Slight rain", "temp_max": 21.3, "temp_min": 13.1,
"feels_max": 20.8, "feels_min": 12.4,
"sunrise": "2026-08-22T05:42", "sunset": "2026-08-22T19:58",
"uv_index_max": 5.1, "precipitation_sum": 3.2, "precipitation_probability": 70,
"wind_speed_max": 18.0, "wind_direction": 220, "wind_direction_cardinal": "SW" }
],
"hourly": [
{ "time": "2026-08-22T20:00", "temperature": 17.8, "weather_code": 61,
"condition": "RAIN", "is_day": 0, "precipitation_probability": 62 }
],
"meta": {
"source": "open-meteo.com",
"attribution": "Weather data by Open-Meteo.com (CC BY 4.0)",
"cached": false, "cache_age_seconds": 0,
"generated_at": "2026-08-22T17:30:11.402Z"
}
}
condition is a stable, human friendly string derived from the WMO weather_code. Use it to pick an icon, a colour or an ASCII scene: SUNNY code 0, daytime CLEAR code 0, night PARTLY 1, 2 CLOUDY (reserved / generic) OVERCAST 3 FOG 45, 48 DRIZZLE 51, 53, 55 RAIN 61, 63, 65, 80, 81, 82 FREEZING_RAIN 56, 57, 66, 67 SNOW 71, 73, 75, 77, 85, 86 STORM 95, 96, 99 HAIL (reserved, see STORM codes 96/99) description carries the exact WMO wording, e.g. "Violent rain showers".
# current weather + 7 day forecast by city curl -s "$BASE/api/weather?city=Warsaw" | jq '.current' # by coordinates, imperial units, 3 days curl -s "$BASE/api/weather?lat=52.2297&lon=21.0122&days=3&units=imperial" # just the current conditions curl -s "$BASE/api/current?city=Tokyo" | jq '.current.temperature' # geocoding curl -s "$BASE/api/geocode?q=Lisbon&count=3" | jq '.results[].name' # inspect rate limit headers curl -sD - -o /dev/null "$BASE/api/weather?city=Oslo" | grep -i ratelimit
const BASE = "http://localhost:3000";
async function getWeather(city) {
const res = await fetch(`${BASE}/api/weather?city=${encodeURIComponent(city)}&days=5`);
const data = await res.json();
if (!data.ok) throw new Error(data.error.message);
return data;
}
const wx = await getWeather("Warsaw");
console.log(wx.location.name, wx.current.temperature, wx.units.temperature);
console.log(wx.current.condition, "-", wx.current.description);
for (const day of wx.daily) {
console.log(day.date, day.condition, `${day.temp_min}..${day.temp_max}`);
}
// by coordinates
const local = await fetch(`${BASE}/api/weather?lat=48.8566&lon=2.3522`).then(r => r.json());
// CORS is open, so this also works straight from a static page:
// <script>fetch("BASE/api/current?city=Berlin").then(...)</script>
import requests
BASE = "http://localhost:3000"
def get_weather(city=None, lat=None, lon=None, days=7, units="metric"):
params = {"days": days, "units": units}
if city:
params["city"] = city
else:
params.update({"lat": lat, "lon": lon})
r = requests.get(f"{BASE}/api/weather", params=params, timeout=15)
r.raise_for_status()
data = r.json()
if not data["ok"]:
raise RuntimeError(data["error"]["message"])
return data
wx = get_weather(city="Warsaw")
cur = wx["current"]
print(f"{wx['location']['name']}: {cur['temperature']}{wx['units']['temperature']} "
f"({cur['condition']}, feels {cur['apparent_temperature']})")
for day in wx["daily"]:
print(day["date"], day["condition"], day["temp_min"], "..", day["temp_max"])
# coordinates + imperial
wx_us = get_weather(lat=40.7128, lon=-74.0060, units="imperial", days=3)
RATE LIMIT
60 requests per 60 seconds per IP address (configurable via RATE_LIMIT_MAX).
Every response carries:
X-RateLimit-Limit requests allowed in the window
X-RateLimit-Remaining requests left
X-RateLimit-Reset seconds until the window resets
Retry-After only on 429
CACHING
Weather responses are cached in memory for 10 minutes, keyed by
lat/lon (rounded to 2 decimals), days, units, timezone and hours.
Geocoding results are cached for 24 hours.
X-Cache: HIT | MISS tells you which path served your call, and
meta.cache_age_seconds reports how old the cached payload is.
This keeps Open-Meteo unstressed - please do not disable it.
ERROR CODES
400 missing_parameter neither city nor lat+lon supplied
400 bad_request parameter out of range or too long
404 not_found the city could not be geocoded
404 unknown_endpoint no such path under /api
429 rate_limited too many requests, see Retry-After
502 upstream_error Open-Meteo returned an error
504 upstream_timeout Open-Meteo did not answer in 12 s
500 internal_error unexpected server failure
FAIR USE
Free for personal and commercial projects. Keep client side caching on,
do not poll faster than once per 10 minutes for the same coordinates and
credit Open-Meteo (CC BY 4.0) where you show the data.
response will appear here ...