How to get first and last frost dates for a coordinate

Last updated

Call https://coords.zone/api/v1/climate with lat and lng query parameters. It returns the average last spring frost and first autumn frost for that point as a day-of-year number — for latitude 42.36, longitude −71.06 (Boston, Massachusetts) the last spring frost is Apr 5 and the first autumn frost is Nov 9, a growing season of about 218 days. No API key is required to try it.

The request

curl "https://coords.zone/api/v1/climate?lat=42.36&lng=-71.06"

The response

{
  "lat": 42.36,
  "lng": -71.06,
  "min_temp": -19.3,
  "min_temp_f": -2.7,
  "max_temp": 23.2,
  "max_temp_f": 73.8,
  "frost_last_doy": 95,
  "frost_first_doy": 313
}
Field Type Description
frost_last_doy integer Last spring frost as a day of year, 1–365 on a non-leap numbering: 1 is Jan 1, 60 is Mar 1, 365 is Dec 31. Resolve it against a non-leap year. The value is a 30-year average, so it names no year of its own.
frost_first_doy integer First autumn frost as a day of year, on the same numbering.
min_temp number Average annual extreme minimum temperature, °C. This is the value the USDA zone is derived from.
max_temp number Average annual extreme maximum temperature, °C.
min_temp_f, max_temp_f number The same two temperatures in °F.

Calculating the growing season

The frost-free window is the gap between the two day-of-year values, which is why they are exposed as integers — subtracting them needs no date parsing at all:

const res = await fetch(
  "https://coords.zone/api/v1/climate?lat=42.36&lng=-71.06"
);
const { frost_last_doy, frost_first_doy } = await res.json();

const growingSeasonDays = frost_first_doy - frost_last_doy;

console.log(growingSeasonDays); // 218

In Python:

import requests

c = requests.get(
    "https://coords.zone/api/v1/climate",
    params={"lat": 42.36, "lng": -71.06},
).json()

print(c["frost_first_doy"] - c["frost_last_doy"])  # 218

These are averages, not a forecast

Every value here is a 30-year climatological average, not a prediction for the current year. "Last frost Apr 5" means that in a typical year the last freezing night falls around 5 April at that location — roughly half of years will have a later one. Gardeners traditionally add a safety margin of one to two weeks before planting frost-tender crops, and this API deliberately does not pick that margin for you.

Averaging is also why the day-of-year values are whole days: a frost_last_doy of 95 is the rounded mean of many individual years' last-frost days, not a claim that frost arrives on 5 April. For the same reason the API publishes the day number and not a resolved calendar date: turning 95 into a date means choosing how to count leap years, and at this precision that choice would be noise dressed up as detail. Count from 1 January in a non-leap year if you need one.

Coordinates with no frost

In climates that never reliably freeze, there is no meaningful frost date, and the frost fields come back as null rather than as an invented date. Latitude 1.35, longitude 103.82 (Singapore) returns:

{
  "lat": 1.35,
  "lng": 103.82,
  "min_temp": 23.3,
  "min_temp_f": 73.9,
  "max_temp": 27.3,
  "max_temp_f": 81.1,
  "frost_last_doy": null,
  "frost_first_doy": null
}

Treat null as "frost-free" rather than as an error, and guard the growing-season subtraction above accordingly.

Where the data comes from

Frost dates are derived from ERA5-Land, the highest-resolution reanalysis in ECMWF's ERA5 family, over decades of record. Coverage is global and land-based, so the same call works for any land coordinate on Earth. Treat the result as a regional average rather than a reading for one garden: elevation, slope, and urban heat can shift real frost dates by days between neighbouring sites.

Next

coords.zone

© 2026 coords.zone