Overview
The response holds up to four blocks: the conditions now, a 60 minute rain forecast, 240 hourly rows, and 10 daily rows.
Build a request
Select at least one data block.
curl "https://graysky.net/free/v1/forecast/42.35,-71.05?dataSets=current,daily"
const response = await fetch('https://graysky.net/free/v1/forecast/42.35,-71.05?dataSets=current,daily');
const forecast = await response.json();
console.log(forecast.current);
import requests
forecast = requests.get("https://graysky.net/free/v1/forecast/42.35,-71.05?dataSets=current,daily").json()
print(forecast["current"])
Endpoint
https://graysky.net/free/v1/forecast/{LATITUDE},{LONGITUDE}
$ curl "https://graysky.net/free/v1/forecast/42.35,-71.05?dataSets=current,hourly,daily"
The coordinate cell
The service moves your position to a 0.05 degree cell. A cell is about
5.5 km across. The response header X-Gc-Grid
shows the cell. Example: the point 42.3601,-71.0589 becomes the cell
42.35,-71.05.
Query parameters
| Name | Default | Description |
|---|---|---|
dataSets |
current,daily |
A comma-set of blocks: current, minutely, hourly, and daily. See Data blocks. An unknown member gets HTTP 400. |
units |
si |
si or imperial. Imperial units add fields. Imperial units do not replace the SI fields. See Units. |
lang |
en |
The API answers in English only. A different value gets HTTP 400. |
The service ignores an unknown query parameter. A link with
?utm_source= works. The service resolves
the time zone from the coordinate and returns it in
location.timezone. Do not send a
timezone parameter.
Data blocks
The dataSets parameter selects the blocks.
Each block is one part of the response. See
Response format for the shared field rules.
| Block | Fields |
|---|---|
current |
validAt, conditionCode, conditionLabel, temperatureC, feelsLikeC, heatIndexC, dewPointC, humidityPercent, cloudCoverPercent, visibilityKm, uvIndex, uvCategory, pressureMb, precipRateMmHr, precipType, windSpeedMs, windGustMs, windDirectionDeg, windDirectionCardinal, summary |
minutely |
61 rows of validAt, precipRateMmHr, and precipChancePercent, plus coverage and anchorAgeMinutes. Radar coverage is the United States and Europe. Outside that area, data[] is empty and coverage says so. |
hourly |
The current set without pressureMb, plus precipMm and precipChancePercent, in 240 hourly rows. |
daily |
temperatureMaxC/MinC, feelsLikeMaxC/MinC, dewPointMaxC/MinC, humidityPercent, cloudCoverPercent, visibilityKm, uvIndex, uvIndexPeakTime, precipMm, precipChancePercent, precipType, wind fields, sunriseAt, sunsetAt, solarNoonAt, dawn and dusk times, moonriseAt, moonsetAt, moonPhase, narrative, in 10 daily rows. |
Two gaps to know. pressureMb is in the
current block only. Precipitation is one
precipMm amount plus a
precipType value; there are no separate
rain, shower, and snowfall fields.
Response format
The response is one JSON object with location,
meta, and the blocks you requested. These
rules hold everywhere:
- SI on the wire. Values are metric with a unit suffix:
temperatureC,windSpeedMs,precipMm. The imperial option adds companion fields. It removes nothing. See Units. - RFC 3339 timestamps. Each time field is an RFC 3339 string with an explicit offset, not a Unix epoch.
- Percentages are 0-100.
humidityPercent,cloudCoverPercent,precipChancePercent. - Wind comes paired.
windDirectionDeg(0-360) andwindDirectionCardinal(for exampleSW). - Conditions are an enum.
conditionCodeis a fixed string with a humanconditionLabelbeside it. See condition codes. - Nullable, not absent. A field with no data is
null, not dropped. A decoder can rely on the shape.
location
| Field | Type | Notes |
|---|---|---|
latitude | number | The latitude of the cell that answered. |
longitude | number | The longitude of the cell that answered. |
timezone | string | The IANA time zone name, for example America/New_York. |
utcOffsetSeconds | integer | The offset from UTC in seconds. It includes DST. |
meta
| Field | Type | Notes |
|---|---|---|
units | string | Echoes the request's units value. The wire is always SI. This is a display hint. |
generatedAt | string | The RFC 3339 time of the response build. |
expiresAt | string | A client-cache hint. It matches the Cache-Control lifetime. |
modelSources | string[] | The internal model identifiers in this response. |
attribution | string | The credit string. |
Condition codes
conditionCode is one value from a fixed set.
New values are additive. Treat an unknown value as
cloudy. The current set:
clear-day, clear-night,
mostly-clear-day, mostly-clear-night,
partly-cloudy-day, partly-cloudy-night,
mostly-cloudy-day, mostly-cloudy-night,
cloudy, rain,
sleet, snow,
wind, fog,
hail, thunderstorm,
scattered-thunderstorms, isolated-thunderstorms,
tornado, limited.
limited marks a degraded entry. The
validAt is valid, but the numeric fields are
null, because every model source ran out of
coverage for that lead. Show "data unavailable", not a zero.
The machine-readable contract is
/dev/openapi.yaml
(OpenAPI 3.1).
Units
| Value | Behavior |
|---|---|
si (default) |
SI fields only: temperatureC, windSpeedMs, precipMm, and so on. |
imperial |
The SI fields plus imperial companions on the same entry. meta.units echoes imperial. |
A companion appears only when the SI source field is present. A
null SI value makes a
null imperial value. The API never invents
a number.
SI to imperial companion map
| SI field | Imperial companion | Conversion |
|---|---|---|
temperatureC | temperatureF | °C → °F |
feelsLikeC | feelsLikeF | °C → °F |
dewPointC | dewPointF | °C → °F |
heatIndexC | heatIndexF | °C → °F |
temperatureMaxC / temperatureMinC | temperatureMaxF / temperatureMinF | °C → °F (daily) |
windSpeedMs / windGustMs | windSpeedMph / windGustMph | m/s × 2.23694 |
precipMm | precipIn | mm ÷ 25.4 |
precipRateMmHr | precipRateInHr | mm/hr ÷ 25.4 |
visibilityKm | visibilityMi | km × 0.621371 |
pressureMb | pressureInHg | mb × 0.02953 |
Response headers
| Header | Meaning |
|---|---|
X-Gc-Surface | Always free-v1 on this endpoint. |
X-Gc-Grid | The 0.05 degree cell that answered. |
X-Gc-Budget | How much of this hour's shared budget is used, in steps of 10 percent. |
X-Attribution | The credit line. Show it or link to graysky.net. |
Cache-Control | How long the answer stays fresh. Keep each answer for at least that long. |
Link | Points to the rules on this page (rel="terms-of-service"). |
The service sends
Access-Control-Allow-Origin: *. A browser
page can call the API directly.
Errors
Each error is an RFC 9457
Problem Details object with
Content-Type: application/problem+json:
{
"type": "https://graysky.net/dev#bad-coordinate",
"title": "Bad Request",
"status": 400,
"detail": "The path needs latitude,longitude in degrees. Example: /free/v1/forecast/42.35,-71.05",
"traceId": "8f1c2a7d9e3b4c01-BOS"
}
| Field | Notes |
|---|---|
type | A URI for the problem type. It points at a row in the table below. |
title | A short name for the status. |
status | The HTTP status code, repeated in the body. |
detail | What went wrong in this request. |
traceId | The correlation id. Quote it when you report a problem. |
The type URI ends with one of these fragments:
| Status | type fragment | Cause |
|---|---|---|
400 | #bad-coordinate | The path is not a valid latitude,longitude pair. |
400 | #bad-parameter | An unknown dataSets member, a bad units value, or a timezone parameter. |
400 | #paid-only | The request asked for the alerts block or the airQuality block. The free API does not serve them. |
400 | #unsupported-language | The lang value is not en. |
404 | #no-coverage | GraySky has no data for this point now. |
503 | #budget-exhausted | The shared budget for this hour is empty. Wait for the Retry-After seconds. |
503 | #service-disabled | The free service is off. |
502 | #upstream-error | The data source sent an error. |
504 | #upstream-timeout | The data source did not answer in time. |
Good fits
- Fixed displays. A Raspberry Pi panel, a smart mirror, a desk widget, or a lamp that turns blue before rain.
- Home automation. A Home Assistant sensor. Skip the irrigation when today's
precipMmis above 2. Close the blinds whenuvIndexpasses 7. - A rain warning for one place. The
minutelyblock holds 61 per-minute rows. Use it to warn yourself 20 minutes before the rain starts. - Classroom projects. For students building web apps that work on the first try.
- Chat bots. A bot that posts one daily forecast for one town.
- Personal scripts. A shell prompt, a terminal weather command, or a morning cron job.
- Garden and sensor logs. Log the forecast rain total (
precipMm) for one yard. Or compare a home weather station's own readings with the forecast.
Poor fits
These uses empty the shared budget, or the rules do not permit them.
| Use | Why |
|---|---|
| Commercial work | The rules do not permit it. Email [email protected]. |
| A page that shows many cities | Each new cell is a new request to the origin. |
| An app with a location for each user | Each user adds a cell. The budget empties quickly. |
| Bulk collection of the grid | It empties the shared budget for everybody. |
| Severe-weather alarms | The free API does not serve the alerts block. Do not build a safety alarm on it. |
Limits and rules
This API needs no key and no account. It has rules.
- Use this API for non-commercial work only.
- Give credit to GraySky. Add a link to graysky.net.
- Poll each coordinate at most once every 10 minutes. A faster poll returns the same cached response.
- Send fewer than 10 requests each minute.
Caching
The service caches each answer at the edge. A cache hit does not use the shared budget. A repeat of a warm cell costs the service almost nothing.
The Cache-Control header shows how long an
answer stays fresh. Keep each answer for at least that time. An answer
with the minutely block stays fresh for
about 2 minutes. All other answers stay fresh for about 10 minutes.
Data sources
GraySky makes the forecast with a proprietary multi-model engine. The engine blends these inputs:
- Short-term models. Kilometer-scale numerical models over North America. They power the first 48 hours of the
hourlyblock and most of thecurrentblock. - Global models. Two independent global models make the 2 to 10 day outlook in the
dailyblock and the second half of thehourlyblock. - Live radar. Radar mosaics over the United States and Europe feed our nowcaster. The nowcaster makes the
minutelyblock. - Surface observations. Tens of thousands of stations feed our verification pipeline. The pipeline trains the calibration layer.
We add three layers on top:
- Bias calibration. We correct temperatures with per-city tables. We fit the tables each night against station observations. We recalibrate rain probabilities each night against radar truth.
- Model blending. Where two models disagree, we use the model with the better score for that location and that variable.
- Plain-language summaries. A deterministic rule set writes the summary fields from the numbers. No generative model writes them.
Refresh cadence
| Layer | Refresh |
|---|---|
| Minutely precipitation | Every 2 minutes during active precipitation. Every 10 minutes when clear. |
| Current conditions and hourly | Every 15 minutes. |
| Daily 10-day outlook | Every 6 hours. |
| Bias-calibration tables | Refit every night against the latest observations. |
Coverage
| Block | Coverage |
|---|---|
current | Worldwide |
minutely | United States and Europe |
hourly | Worldwide |
daily | Worldwide |
FAQ
Do I need an API key?
No. Send the request. There is no key, no account, and no sign-up.
Can I call the API from a browser page?
Yes. The service sends CORS headers. A page on any origin can call the API. The rules also apply to a page.
Is there an SDK?
No. The API is plain HTTP and JSON. Every language can call it. To get
a typed client, generate one from
/dev/openapi.yaml
with an OpenAPI 3.1 tool.
What is the coverage outside the United States?
The current, hourly,
and daily blocks are global. The
minutely block covers the United States and
Europe.
Can I use the data in a commercial product?
No. The free API is for non-commercial work only. For commercial use, email [email protected].
Can I republish the data?
Show the data to your users, with credit to GraySky. Do not mirror the API. Do not sell the raw JSON.
How do I contact you?
Email [email protected].
Include the traceId from the error response
when you report a problem.