GuidesWEATHER APIS
Free weather APIs tested from the browser
We tested 11 free weather APIs from a browser on 26 October 2026: 10 send CORS headers and work with fetch() directly, only 7Timer needs a proxy, and 6 need no API key. Open-Meteo is the default pick: no key, current weather plus a 7-day forecast in one request, free up to 10,000 calls a day for non-commercial use. For US forecasts, api.weather.gov is free for any purpose.
Every weather API we tested
We ran each API on 26 October 2026 with curl and an Origin: https://example.com header from a German datacenter IP and a US residential IP, then in Chromium 147.0.7727.15 from http://127.0.0.1:8001 and https://example.com. To check another endpoint, use the CORS header checker or our 70-API list of free public APIs.
| API | API key | CORS from the browser | Free limit | Coverage and notes |
|---|---|---|---|---|
| Open-Meteo | None | Yes, ACAO * | 10,000 calls/day, 5,000/hour, 600/minute, non-commercial only | Global forecast; CC BY 4.0 attribution |
| NWS api.weather.gov | None | Yes, ACAO * | Not published; a retry usually works within 5 seconds | US only; points lookup, then forecast URL |
| MET Norway Locationforecast 2.0 | None | Yes, ACAO * | Over 20 requests/second per app counts as heavy use | Global; identify your app; CC BY 4.0 |
| Bright Sky | None | Yes, ACAO * | Free for all purposes, no quota published | German DWD data, sparser outside Germany |
| wttr.in (format=j1) | None | Yes, ACAO * | No quota published | JSON labelled text/plain, about 40 KB per city |
| 7Timer | None | No, needs a proxy | No quota published | GFS model, updated 4 times a day |
| OpenWeatherMap | Free key (appid) | Yes, ACAO * | 60 calls/minute, 1,000,000 calls/month | One Call 3.0 is a separate subscription |
| WeatherAPI.com | Free key | Yes, ACAO * | 100,000 calls/month | Key goes in the URL as key= |
| Tomorrow.io | Free key | Yes, echoes your Origin | Per second, hour and day; shown in X-RateLimit headers | Free plan: 5-day forecast, 1 location |
| Visual Crossing | Free key | Yes, ACAO * | 1,000 records/day, 30,000 records/month | Commercial use allowed on the free plan |
| Pirate Weather | Free key in the URL path | Yes on a plain GET; preflight gets 403 | 10,000 calls/month | Dark Sky style API |
Open-Meteo: the default with no key
Open-Meteo (https://api.open-meteo.com/v1/forecast) takes no API key, sends Access-Control-Allow-Origin: *, and returns current conditions plus a 7-day forecast in one call when you pass current, daily, forecast_days=7 and timezone=auto. Call it directly from fetch().
Its terms allow non-commercial use only, with less than 10,000 calls per day, 5,000 per hour and 600 per minute, counted per IP address and per application. Commercial products need a paid plan. The data is licensed under CC BY 4.0, so credit Open-Meteo in your app.
The forecast takes latitude and longitude, not place names. Open-Meteo also runs a geocoding API and an air-quality API, both with Access-Control-Allow-Origin: *; our free geocoding API guide shows how to turn a city name into coordinates.
const params = new URLSearchParams({
latitude: '52.37',
longitude: '4.89',
current: 'temperature_2m,wind_speed_10m',
daily: 'temperature_2m_max,temperature_2m_min,precipitation_sum',
timezone: 'auto',
forecast_days: '7',
});
const response = await fetch(`https://api.open-meteo.com/v1/forecast?${params}`);
const body = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`Open-Meteo failed: ${body?.reason ?? response.status}`);
}
console.log('Now:', body.current.temperature_2m, body.current_units.temperature_2m);
console.table(
body.daily.time.map((day, i) => ({
day,
max: body.daily.temperature_2m_max[i],
min: body.daily.temperature_2m_min[i],
rainMm: body.daily.precipitation_sum[i],
})),
);US forecasts from api.weather.gov
The National Weather Service API at https://api.weather.gov covers the US only, needs no key, and sends Access-Control-Allow-Origin: * with application/geo+json responses. A forecast takes two requests: GET /points/{lat},{lon} returns properties.forecast, a gridpoint URL like https://api.weather.gov/gridpoints/TOP/32,81/forecast, and a GET on that URL returns 14 periods, day and night for 7 days. Outside the US, /points answers 404 with "Unable to provide data for requested point".
The docs ask for a User-Agent that identifies your app, such as (myweatherapp.com, contact@myweatherapp.com), and curl with an empty User-Agent got 403 Access Denied. Chrome does not let fetch() replace that header: Chromium 147 silently dropped our custom User-Agent, sent its own, and still got 200.
Call it directly, not through a proxy. The rate limit is not public, and the docs say proxies are more likely to reach it than requests straight from clients. When you hit it, retry after the limit clears, typically within 5 seconds.
async function getGeoJson(url) {
const response = await fetch(url, { headers: { Accept: 'application/geo+json' } });
const body = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`${url} failed: ${body?.detail ?? response.status}`);
}
return body;
}
const point = await getGeoJson('https://api.weather.gov/points/38.8977,-77.0365');
const forecast = await getGeoJson(point.properties.forecast);
console.table(
forecast.properties.periods.map(({ name, temperature, temperatureUnit, shortForecast }) => ({
name,
temperature: `${temperature} ${temperatureUnit}`,
shortForecast,
})),
);MET Norway, Bright Sky and wttr.in
- MET Norway Locationforecast 2.0 (
api.met.no/weatherapi/locationforecast/2.0/compact): no key,Access-Control-Allow-Origin: *, 39,836 bytes for one location, CC BY 4.0. The terms ask for an identifyingUser-Agent, and accept theOriginorRefererthat browsers send when cross-origin JavaScript can't set one. Don't add a customUser-Agentheader: the preflight for it returned 204 without CORS headers, so the browser blocks the call. More than 20 requests per second for your whole app counts as heavy use; cache responses and useIf-Modified-Since. - Bright Sky (
api.brightsky.dev): no key,Access-Control-Allow-Origin: *, German weather service (DWD) data under the DWD terms of use. The public instance is free for all purposes with no published quota;/current_weatherreturned 1,623 bytes. - wttr.in (
wttr.in/Amsterdam?format=j1): no key,Access-Control-Allow-Origin: *, no published quota. It returned 39,553 bytes of JSON labelledContent-Type: text/plain, whichresponse.json()still parses.
7Timer needs a proxy
7Timer (https://www.7timer.info/bin/api.pl) runs on the NOAA GFS model, updates 4 times a day and needs no key; the civil product with output=json gives 3-hourly steps. It sends no CORS headers, and Chromium 147 blocked it with: Access to fetch at '...' from origin 'http://127.0.0.1:8001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Prefix the URL with https://proxy.cors.dev/ and the same request returns 200 with 15,177 bytes of JSON. The free CORS proxy needs no key or signup and handles GET and HEAD for text responses up to 1 MiB, with a 10-second deadline and 600 requests per minute per client IP.
const proxy = 'https://proxy.cors.dev/';
const url = 'https://www.7timer.info/bin/api.pl?lon=4.89&lat=52.37&product=civillight&output=json';
const response = await fetch(proxy + url, { credentials: 'omit' });
if (!response.ok) {
throw new Error(`7Timer via proxy failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
}
const body = await response.json();
console.table(
body.dataseries.map(({ date, weather, temp2m }) => ({
date,
weather,
max: temp2m.max,
min: temp2m.min,
})),
);Weather APIs with a free key
- OpenWeatherMap: key in
appid,Access-Control-Allow-Origin: *even on a 401. The free plan gives 60 calls per minute and 1,000,000 per month for current weather, the 3-hour 5-day forecast, air pollution, maps and geocoding. One Call API 3.0 is a separate subscription: 1,000 free calls per day, then pay per call, and you fill out a billing form to subscribe. - WeatherAPI.com: key in
key=,Access-Control-Allow-Origin: *, 100,000 calls per month free. - Tomorrow.io: key in
apikey=, echoes yourOrigin. The free plan covers a 5-day forecast, 24 hours of history, 1 location and 1 alert; its per-second, hourly and daily limits show up only inX-RateLimitresponse headers. - Visual Crossing: key in
key=,Access-Control-Allow-Origin: *, 1,000 records per day and 30,000 per month, commercial use allowed. - Pirate Weather: a Dark Sky style API with the key in the URL path and 10,000 free calls per month. A plain
GETgetsAccess-Control-Allow-Origin: *, but its preflight returned 403 without CORS headers, so add no custom headers.
A key in frontend code is visible to every visitor, who can copy it and spend your quota. All five already send CORS headers, so call them directly; the cors.dev free tier refuses URLs with key=, apikey= and similar parameters (HTTP 400, X-Cors-Error: credential_in_url).
Good questions.
Which free weather API works without an API key?
Six of the 11 we tested: Open-Meteo, api.weather.gov, MET Norway, Bright Sky, wttr.in and 7Timer. All but 7Timer send Access-Control-Allow-Origin: *, and Open-Meteo is the easiest default at up to 10,000 calls per day for non-commercial apps.
Is Open-Meteo free for commercial use?
No. The free API (10,000 calls per day, 5,000 per hour, 600 per minute) is non-commercial only. For free commercial use without a key, api.weather.gov (US only) is free for any purpose, Bright Sky is free for all purposes under the DWD terms, and MET Norway publishes under CC BY 4.0, which allows commercial use with credit.
Do I need to set a User-Agent for api.weather.gov in the browser?
No. Chrome does not let fetch() override User-Agent: Chromium 147 dropped our custom value, sent its own, and got 200. Send an identifying User-Agent when you call the API from a server.
Is the OpenWeatherMap One Call API free?
One Call API 3.0 includes 1,000 free calls per day, but it is a separate subscription that asks for billing details and charges per call above that. The standard free plan gives 1,000,000 calls per month at up to 60 per minute without One Call.
How do I turn a city name into coordinates for a weather API?
Open-Meteo's geocoding API turns a city name into latitude and longitude with no key and sends Access-Control-Allow-Origin: *. Our free geocoding API guide compares 11 geocoders with code.
Can I show local weather without asking for the user's location?
Yes, approximately: look up the visitor's IP address with a free IP geolocation API and pass the coordinates to Open-Meteo or api.weather.gov. The exact position needs the browser's geolocation permission.