FreeAPI.watch

Building a Weather App in 2026? Here's the Free API Stack I'd Use

 ·  weather

Open-Meteo for forecasts, Nominatim for location lookup, Open-Meteo Air Quality for pollution data. Code snippets and gotchas included.

The free API stack for a weather app in 2026: Open-Meteo for forecasts (10,000 calls/day, no key), Nominatim for address-to-coordinates (1 req/sec, no key), and Open-Meteo Air Quality for pollution data (10,000 calls/day, no key, same API design). Total cost: $0.

I'll walk through the integration, the response shapes, and the specific gotchas that will bite you if you don't know about them.

Step 1: Geocode the User's Location

Before you can get weather, you need coordinates. If your app accepts a city name or address, Nominatim converts it to lat/lng:

async function geocode(query) {
  const url = new URL("https://nominatim.openstreetmap.org/search");
  url.searchParams.set("q", query);
  url.searchParams.set("format", "json");
  url.searchParams.set("limit", "1");

  const res = await fetch(url, {
    headers: {
      "User-Agent": "MyWeatherApp/1.0 (contact@example.com)"
    }
  });
  const results = await res.json();
  if (!results.length) return null;
  return { lat: parseFloat(results[0].lat), lon: parseFloat(results[0].lon) };
}

Cache this result. A city name maps to the same coordinates every time — there's no reason to hit Nominatim again for 'London' if you already have its coordinates.

See /nominatim for live status and /tutorial/nominatim for the full tutorial including rate limit handling.

Step 2: Fetch the Forecast from Open-Meteo

Open-Meteo's response has two top-level keys you'll use: `current` (current conditions) and `hourly` (hourly forecast for the next 7 days). Request only what you need — each parameter adds response size.

async function getForecast(lat, lon) {
  const url = new URL("https://api.open-meteo.com/v1/forecast");
  url.searchParams.set("latitude", lat);
  url.searchParams.set("longitude", lon);
  // Current conditions
  url.searchParams.set("current", [
    "temperature_2m",
    "apparent_temperature",
    "precipitation",
    "weathercode",
    "windspeed_10m"
  ].join(","));
  // Hourly for next 24 hours (index 0-23)
  url.searchParams.set("hourly", "temperature_2m,precipitation_probability,weathercode");
  url.searchParams.set("forecast_days", "2");
  url.searchParams.set("timezone", "auto"); // infer from coordinates

  const res = await fetch(url);
  const data = await res.json();

  return {
    current: data.current,
    hourly: {
      times: data.hourly.time.slice(0, 24),
      temp: data.hourly.temperature_2m.slice(0, 24),
      precip_prob: data.hourly.precipitation_probability.slice(0, 24),
      weathercode: data.hourly.weathercode.slice(0, 24)
    }
  };
}

The `weathercode` follows the WMO standard (0 = clear sky, 61/63/65 = rain by intensity, 71/73/75 = snow). Open-Meteo's docs at https://open-meteo.com/en/docs include the full WMO code table.

See /open-meteo for live status.

Step 3: Add Air Quality (Optional)

Open-Meteo's Air Quality API uses identical parameter syntax but a different base URL:

async function getAirQuality(lat, lon) {
  const url = new URL("https://air-quality-api.open-meteo.com/v1/air-quality");
  url.searchParams.set("latitude", lat);
  url.searchParams.set("longitude", lon);
  url.searchParams.set("current", "pm10,pm2_5,european_aqi");

  const res = await fetch(url);
  const data = await res.json();
  return data.current; // { pm10, pm2_5, european_aqi, ... }
}

The `european_aqi` value maps to the EU Air Quality Index (1–5 scale: Good, Fair, Moderate, Poor, Very Poor). The same 10,000 calls/day limit applies but it's a separate quota from the weather API. See /open-meteo-air-quality for current status.

When the Free Stack Breaks Down

The free stack isn't suitable for: commercial applications with >1,000 DAU (you'll hit Open-Meteo's free tier), hyperlocal precipitation nowcasting (none of the free APIs match Dark Sky's sub-km resolution), or severe weather alerting for production use (NOAA alerts are great for the US but not global).

The natural paid upgrade path: Open-Meteo commercial plan ($10–$50/month) for volume, OpenCage ($50/month) if geocoding volume exceeds what Nominatim can handle comfortably, and Tomorrow.io ($19/month) if you need hyperlocal features.

For the full weather API comparison, see /category/weather and /compare/open-meteo-vs-openweathermap.

Frequently Asked Questions

Can a free stack handle production traffic?

Open-Meteo's 10,000 calls/day free limit translates to roughly one call every 8.6 seconds if used continuously — fine for a personal app or small user base. At 1,000 daily active users each requesting a forecast once per session, you'd hit the limit. At that scale, Open-Meteo's commercial plan ($10–$50/month depending on volume) is the right move. The Nominatim rate limit (1 req/sec) is the more likely bottleneck — you'd want to cache geocoding results aggressively.

What's the biggest hidden cost?

Geocoding. Converting a user's typed address to lat/lng coordinates requires a geocoding call on every new location search. If you cache aggressively (store the lat/lng after first lookup, don't re-geocode the same address), you can stay well within Nominatim's 1 req/sec limit. The hidden cost emerges when users search for many different locations — a city-explorer app, for example, would hit geocoding limits far sooner than forecast limits.

When should I migrate to paid?

Consider paid when: (a) you hit Open-Meteo's 10,000 calls/day limit and caching isn't reducing it further; (b) you need sub-1km precision for weather (paid hyperlocal APIs like Tomorrow.io); (c) you need historical weather data beyond what Open-Meteo's free tier covers; (d) your geocoding volume exceeds Nominatim's comfortable rate limit and caching isn't sufficient. Open-Meteo's paid plans are reasonable ($10–$50/month); OpenCage geocoding is $50/month for 10,000 calls/day.

← All articles