If you have ever built anything that needed weather data, you have probably bounced off an API paywall. OpenWeatherMap caps the free tier at 60 calls a minute and 1,000 a day, then meters the rest. WeatherAPI does the same dance. Tomorrow.io, Visual Crossing, AccuWeather — all of them want a credit card before you can pull a serious sample. For a side project or a working prototype, that is a real friction point.
Open-Meteo is the exception. It is a non-profit weather service backed by national meteorological agencies, it serves forecasts and historical climate data through a clean REST API, and it does not ask for an API key for non-commercial use. No signup, no quota meter, no credit card. You just hit the endpoint and get JSON back. For a lot of teams that single fact reframes what is possible.
This post walks through why bulk weather collection is harder than the docs make it look, what Open-Meteo gives you that the paid APIs do not, and how to pull it for hundreds of locations through our hosted scraper without writing any integration code yourself.
Even on a generous free API, going from "I can call the endpoint once" to "I have a clean dataset across 500 cities every hour" is its own engineering problem. Open-Meteo solves the API key issue, but the rest of the work is still real:
Geocoding before you can query: The forecast endpoint takes latitude and longitude, not city names. If your input is a list of cities — and it usually is — you need a geocoder in front. Open-Meteo runs a separate geocoding service at geocoding-api.open-meteo.com, but you still have to wire it together, handle ambiguous city names, and decide what to do when the top match is the wrong country.
Hourly data is fat. A single 7-day forecast for one city with three variables returns 168 hourly records. Multiply by 500 cities and you are at 84,000 rows per pull, before you have even thought about historical archives. You need streaming output, not a single in-memory blob.
Variable selection is finicky. Open-Meteo exposes over 60 hourly variables — temperature, wind, precipitation, humidity, pressure, soil moisture, UV index, evapotranspiration, snow depth, and on. Misspell a variable name and the API silently drops it from the response. You only notice when your downstream pipeline complains about a missing column.
Historical and forecast are separate endpoints. Forecasts come from /v1/forecast, historical comes from /v1/archive, and the input shapes are not identical. If you want both for the same set of cities, you are now maintaining two integration paths.
Schema drift across responses. The hourly object is parallel arrays, not a list of records. hourly.time[i] lines up with hourly.temperature_2m[i] — but if a variable is unsupported for a location, the array can be missing entirely. Code that assumes "every variable always exists" will throw on the first edge case.
We run an Open-Meteo scraper on Apify that collapses all of the above into a single managed call. You pass in a city name (or a lat,lon string), the action you want, and a list of variables. The scraper geocodes if needed, picks the right endpoint, requests the variables, flattens the parallel arrays into clean per-hour records, and streams them out. Pricing is pay-per-result — one cent per hourly record returned — so you only pay for data you actually use.
{
"action": "forecast",
"location": "London",
"forecastDays": 7,
"variables": ["temperature_2m", "windspeed_10m", "precipitation"]
}
{
"location": "London",
"latitude": 51.51,
"longitude": -0.13,
"time": "2026-05-03T00:00",
"temperature_2m": 14.3,
"windspeed_10m": 10.1,
"precipitation": 0,
"scraped_at": "2026-05-03T18:07:39Z",
"source": "open-meteo.com"
}
{
"action": "historical",
"location": "52.52,13.41",
"startDate": "2024-01-01",
"endDate": "2024-01-07",
"variables": ["temperature_2m", "precipitation"]
}
You can pass coordinates directly to skip the geocoding step, or let the scraper resolve a city name for you. The output shape is the same either way — one row per hour, with every variable as its own column. Drop it into a Pandas DataFrame, push it to BigQuery, write it to a Postgres table. The hard part is over.
| Feature | Open-Meteo | OpenWeatherMap | WeatherAPI |
|---|---|---|---|
| API key required | No | Yes | Yes |
| Free tier limit | ~10,000 calls/day | 1,000 calls/day | 1M calls/month |
| Forecast horizon | 16 days hourly | 5 days / 3-hour | 14 days hourly |
| Historical archive | Back to 1940 | Paid tier only | Paid tier only |
| Soil / agricultural variables | Yes | No | No |
For most non-commercial workloads, Open-Meteo is strictly the better data source. The historical archive going back to 1940 alone is worth the switch — everyone else paywalls anything older than yesterday.
Open-Meteo does not require proxies for normal usage. But if you are scraping a wider weather workflow that pulls from multiple sources at once — say, station data from regional services or webcams from outdoor venues — you will hit IP-level rate limits eventually. We use Oxylabs residential proxies for the backstops on workloads where any single source might block. Disclosure: that is an affiliate link.
The Open-Meteo scraper is live on Apify. Pay-per-result at one cent per hourly record. No subscription, no minimum, no API key on your end. If your workload is small enough to live on Open-Meteo's direct free tier, do that — that is what it is there for. If you need geocoding, normalised output, or to run it as part of a larger Apify workflow, the hosted scraper is the easier path.