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.
curl "https://coords.zone/api/v1/climate?lat=42.36&lng=-71.06"
{
"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. |
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
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.
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.
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.
We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
© 2026 coords.zone