DEV Community

Alex Spinov
Alex Spinov

Posted on

Open-Meteo Has a Free Weather API — No Key, No Signup, Real Forecast Data

Most weather APIs hide behind registration walls, throttle free accounts heavily, or charge immediately for anything useful. Open-Meteo is different: completely free, no API key, no account, open-source, and it returns real hourly/daily forecasts.

One URL, no auth:

https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060&current=temperature_2m,wind_speed_10m
Enter fullscreen mode Exit fullscreen mode

Try it right now:

curl "https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060&current=temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code"
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "latitude": 40.710335,
  "longitude": -73.99235,
  "timezone": "GMT",
  "current": {
    "time": "2026-03-26T16:15",
    "temperature_2m": 18.0,
    "wind_speed_10m": 19.0,
    "relative_humidity_2m": 50,
    "weather_code": 0
  },
  "current_units": {
    "temperature_2m": "°C",
    "wind_speed_10m": "km/h",
    "relative_humidity_2m": "%",
    "weather_code": "wmo code"
  }
}
Enter fullscreen mode Exit fullscreen mode

No API key header. No rate limit response. No credit card.

How it works: latitude + longitude + variables

Every request needs two parameters: latitude and longitude. Everything else is optional — you specify which variables you want.

Current weather variables:

  • temperature_2m — air temperature at 2m height (°C or °F)
  • wind_speed_10m — wind speed at 10m (km/h, mph, m/s, or knots)
  • relative_humidity_2m — humidity (%)
  • precipitation — precipitation in the last hour (mm)
  • weather_code — WMO weather code (0=clear, 1-3=partly cloudy, 45=fog, 61-67=rain, 71-77=snow, etc.)
  • apparent_temperature — feels-like temperature
  • is_day — 1 if daytime, 0 if night

Hourly forecast variables (up to 16 days):

  • temperature_2m, precipitation_probability, wind_speed_10m
  • uv_index, visibility, soil_temperature_0cm

Python: current weather for any city

import requests

def get_weather(lat: float, lon: float) -> dict:
    url = "https://api.open-meteo.com/v1/forecast"
    params = {
        "latitude": lat,
        "longitude": lon,
        "current": "temperature_2m,apparent_temperature,wind_speed_10m,relative_humidity_2m,weather_code,precipitation",
        "wind_speed_unit": "mph",
        "temperature_unit": "fahrenheit"
    }
    response = requests.get(url, params=params)
    response.raise_for_status()
    return response.json()["current"]

# New York City
weather = get_weather(40.7128, -74.0060)
print(f"Temperature: {weather['temperature_2m']}°F (feels like {weather['apparent_temperature']}°F)")
print(f"Wind: {weather['wind_speed_10m']} mph")
print(f"Humidity: {weather['relative_humidity_2m']}%")
print(f"Precipitation: {weather['precipitation']} mm")
Enter fullscreen mode Exit fullscreen mode

Output:

Temperature: 64.4°F (feels like 61.2°F)
Wind: 11.8 mph
Humidity: 50%
Precipitation: 0.0 mm
Enter fullscreen mode Exit fullscreen mode

Get an hourly 3-day forecast

import requests
from datetime import datetime

def get_forecast(lat: float, lon: float, days: int = 3) -> list[dict]:
    url = "https://api.open-meteo.com/v1/forecast"
    params = {
        "latitude": lat,
        "longitude": lon,
        "hourly": "temperature_2m,precipitation_probability,wind_speed_10m",
        "forecast_days": days,
        "temperature_unit": "fahrenheit"
    }
    data = requests.get(url, params=params).json()
    hourly = data["hourly"]

    # Zip into list of dicts
    return [
        {
            "time": t,
            "temp_f": temp,
            "precip_pct": precip,
            "wind_mph": wind
        }
        for t, temp, precip, wind in zip(
            hourly["time"],
            hourly["temperature_2m"],
            hourly["precipitation_probability"],
            hourly["wind_speed_10m"]
        )
    ]

forecast = get_forecast(40.7128, -74.0060)
for hour in forecast[:6]:  # First 6 hours
    print(f"{hour['time']}: {hour['temp_f']}°F, {hour['precip_pct']}% rain chance, {hour['wind_mph']} mph")
Enter fullscreen mode Exit fullscreen mode

Output:

2026-03-26T00:00: 47.2°F, 5% rain chance, 6.3 mph
2026-03-26T01:00: 45.8°F, 4% rain chance, 5.8 mph
2026-03-26T02:00: 44.5°F, 3% rain chance, 5.1 mph
2026-03-26T03:00: 43.7°F, 3% rain chance, 4.9 mph
2026-03-26T04:00: 42.9°F, 2% rain chance, 5.2 mph
2026-03-26T05:00: 42.4°F, 2% rain chance, 5.5 mph
Enter fullscreen mode Exit fullscreen mode

Historical weather data (also free)

Open-Meteo has a separate endpoint for historical data, also free:

import requests

def get_historical(lat: float, lon: float, start: str, end: str) -> dict:
    url = "https://archive-api.open-meteo.com/v1/archive"
    params = {
        "latitude": lat,
        "longitude": lon,
        "start_date": start,
        "end_date": end,
        "daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max"
    }
    return requests.get(url, params=params).json()

# Get last week's data for London
history = get_historical(51.5074, -0.1278, "2026-03-19", "2026-03-25")
daily = history["daily"]
for date, max_t, min_t, precip in zip(
    daily["time"],
    daily["temperature_2m_max"],
    daily["temperature_2m_min"],
    daily["precipitation_sum"]
):
    print(f"{date}: {min_t}{max_t}°C, {precip}mm rain")
Enter fullscreen mode Exit fullscreen mode

The archive goes back to 1940 for most locations. No key. No account.

JavaScript: weather widget in the browser

async function getCurrentWeather(lat, lon) {
  const params = new URLSearchParams({
    latitude: lat,
    longitude: lon,
    current: 'temperature_2m,apparent_temperature,weather_code,wind_speed_10m',
    temperature_unit: 'fahrenheit'
  });

  const response = await fetch(
    `https://api.open-meteo.com/v1/forecast?${params}`
  );
  const data = await response.json();
  return data.current;
}

// Works directly from the browser — no CORS issues
getCurrentWeather(34.0522, -118.2437).then(weather => {
  console.log(`LA: ${weather.temperature_2m}°F, code: ${weather.weather_code}`);
});
Enter fullscreen mode Exit fullscreen mode

No CORS issues — Open-Meteo has Access-Control-Allow-Origin: *.

What the weather codes mean

The weather_code field uses WMO weather interpretation codes:

WMO_CODES = {
    0: "Clear sky",
    1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
    45: "Fog", 48: "Icy fog",
    51: "Light drizzle", 53: "Moderate drizzle", 55: "Heavy drizzle",
    61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain",
    71: "Slight snow", 73: "Moderate snow", 75: "Heavy snow",
    80: "Slight showers", 81: "Moderate showers", 82: "Heavy showers",
    95: "Thunderstorm",
}

def describe_weather(code: int) -> str:
    return WMO_CODES.get(code, f"Unknown ({code})")

print(describe_weather(0))   # Clear sky
print(describe_weather(63))  # Moderate rain
Enter fullscreen mode Exit fullscreen mode

Free vs. paid (commercial)

Open-Meteo is free for non-commercial use. The limits:

Tier Cost Rate limit Use case
Free $0 ~10,000 req/day Personal projects, apps, research
Commercial From $29/mo Higher limits + SLA Production apps

For most developer projects — dashboards, personal apps, learning — the free tier is more than enough.

What it doesn't do

  • No severe weather alerts (use NWS API for US alerts)
  • No minute-by-minute nowcasting (free tier updates hourly)
  • No air quality data (separate API at air-quality-api.open-meteo.com, also free)

Need weather data on autopilot? My Weather Forecast Scraper on Apify collects temperature, rain & wind forecasts on a schedule — no code. Email spinov001@gmail.com for custom pipelines.


More free API articles:

Top comments (0)