Using the `openweathermap` node, how do I get a FORECAST rather than what has happened?

I have to ask here because if I don't and just go and search the docs, I won't find an answer.

So I'm asking.

I use openweathermap (node) to get what IS happening.

But am becoming curious if there is a way to get a forecast instead/also/too?

Ok, as said: If I didn't ask here....
But there is a Problem.
The docs say it does a 5 day forecast but doesn't expand on how you get it.

Someone.
Please.

I just use an HTTP request node

[{"id":"de07b57e0a202043","type":"group","z":"800e3b2905246688","name":"Openweathermap call. API Key in global.openweathermapid","style":{"label":true,"fill":"#e3f3d3","fill-opacity":"0.57"},"nodes":["5070330ada0e245c","45f938dc6664d888","c540067eb3ccc002","b13fdc024d7cc1c1"],"x":34,"y":279,"w":632,"h":82},{"id":"5070330ada0e245c","type":"function","z":"800e3b2905246688","g":"de07b57e0a202043","name":"Setup","func":"const owmid = global.get(\"openweathermapid\") || \"NoAPIkey\"\n\nmsg.url = \"https://api.openweathermap.org/data/3.0/onecall?\"\nmsg.url += \"lat=\" + msg.payload.lat + \"&lon=\" + msg.payload.long + \"&units=metric\"\nmsg.url += \"&appid=\" + owmid\nreturn msg;\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":250,"y":320,"wires":[["45f938dc6664d888"]]},{"id":"45f938dc6664d888","type":"http request","z":"800e3b2905246688","g":"de07b57e0a202043","name":"Openweathermap","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":410,"y":320,"wires":[["b13fdc024d7cc1c1"]]},{"id":"c540067eb3ccc002","type":"inject","z":"800e3b2905246688","g":"de07b57e0a202043","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"location\":\"Shey Gompa\",\"lat\":29.35249,\"long\":82.96538}","payloadType":"json","x":130,"y":320,"wires":[["5070330ada0e245c"]]},{"id":"b13fdc024d7cc1c1","type":"debug","z":"800e3b2905246688","g":"de07b57e0a202043","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":570,"y":320,"wires":[]}]

Yeah, but I was stumbling on how to change it from WHAT'S HAPPENING NOW to a FORECAST.

Seems I overlooked the option in the node to give either current weather for and 5 day forecast.
(Me needs glasses) :wink:

Got that working but now have the fun of parsing the huge payload I get to create the forecast of what's going to happen the rest of today.

The http request returns

(You can tell it you don't want some of the data with eg exclude=minutely)

I expect the contents of the arrays are the same as for the app

Hmmmm.....

No, somewhat different to what I get back.

(short bit)

{"dt":1756004400,"main":{"temp":17.24,"feels_like":16.43,"temp_min":17.24,"temp_max":17.33,"pressure":1020,"sea_level":1020,"grnd_level":1012,"humidity":54,"temp_kf":-0.09},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"clouds":{"all":25},"wind":{"speed":3.6,"deg":216,"gust":5.6},"visibility":10000,"pop":0,"sys":{"pod":"d"},"dt_txt":"2025-08-24 03:00:00"}

Seems I may try your offered way.
Hang on while I look.

:wink:

I'm falling over at/with the setup node.

So I need to put my key in there?
(That's not fixing it. Seems there is some difference with the level of access, or something.)
As putting in my key in the first line, now I get code 401....

{"cod":401,"message":"Please note that using One Call 3.0 requires a separate subscription to the One Call by Call plan. Learn more here https://openweathermap.org/price. If you have a valid subscription to the One Call by Call plan, but still receive this error, then please see https://openweathermap.org/faq#error401 for more info."}

When I sign in to openweathermap it says I have the free plan which does not offer hourly or daily forecasts, and I can't find a URL to access this service. I think this must be out of date.

Apart from that I have an API key (which sits in a global context variable so sharing the flow does not compromise it) which clearly does include hourly and daily forecasts.

It seems that I (also?) have a subscription to their "base plan" for OneCall v3


Presumably I gave them a credit card number to access this.
I do take care not to exceed the free limit of 3000 calls per day

Sorry, don't worry.

Not proud of it, but me and Mr Chat GPT are making good headway with it.

Slightly more complex set of queries from OWM's OneCall. Including demo's of the air quality index and reverse geocode lookup.

[{"id":"815941740bc0e964","type":"group","z":"d915a360.b8766","name":"Open Weather Map OneCall API. API Key and lat/lon in global.weather_apis","style":{"label":true,"fill":"#e3f3d3","fill-opacity":"0.29","color":"#000000"},"nodes":["3817c8b2947d01f6","0ac196566feba620","166c313a.49d2ef","a58a05c.aade6f8","e844d1e8.f8585","dbd12e73.be8cb","3bbcfa11.167be6","53c45f68.00321","8e6b9e728b78b1f0","83a5a47c39e68b98","421a1883e4eba346","287017c287456989"],"x":34,"y":1259,"w":1252,"h":282},{"id":"3817c8b2947d01f6","type":"change","z":"d915a360.b8766","g":"815941740bc0e964","name":"","rules":[{"t":"set","p":"url","pt":"msg","to":"(\t    $keys := $globalContext(\"weather_apis\", \"file\");\t    \"http://api.openweathermap.org/data/2.5/air_pollution?lat=\" & $keys.lat & \"&lon=\" & $keys.lon & \"&appid=\" & $keys.keys.owm\t)\t","tot":"jsonata"},{"t":"set","p":"headers","pt":"msg","to":"{\"content-type\":\"application/json\",\"Accept\":\"application/json\",\"User-Agent\":\"node-red\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":155,"y":1360,"wires":[["287017c287456989"]],"l":false,"info":"https://developer.climacell.co/v3/reference#get-realtime\n\nhttps://developer.climacell.co/v3/docs/present\n\nhttps://developer.climacell.co/v3/widgets"},{"id":"0ac196566feba620","type":"inject","z":"d915a360.b8766","g":"815941740bc0e964","name":"","props":[{"p":"payload","v":"","vt":"date"},{"p":"topic","v":"","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":95,"y":1360,"wires":[["3817c8b2947d01f6"]],"l":false},{"id":"166c313a.49d2ef","type":"change","z":"d915a360.b8766","g":"815941740bc0e964","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"(\t   /* Exclude = current,minutely,hourly,daily */\t   \t   $keys := $globalContext(\"weather_apis\", \"file\");\t   \t   {\t       \"appid\": $keys.keys.owm,\t       \"lat\": $keys.lat,\t       \"lon\": $keys.lon,\t       \"exclude\": \"current,minutely,daily\"    \t   }\t)","tot":"jsonata"},{"t":"set","p":"url","pt":"msg","to":"https://api.openweathermap.org/data/3.0/onecall","tot":"str"},{"t":"set","p":"headers","pt":"msg","to":"{\"content-type\":\"application/json\",\"Accept\":\"application/json\",\"User-Agent\":\"node-red\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":155,"y":1300,"wires":[["e844d1e8.f8585"]],"l":false,"info":"https://developer.climacell.co/v3/reference#get-realtime\n\nhttps://developer.climacell.co/v3/docs/present\n\nhttps://developer.climacell.co/v3/widgets"},{"id":"a58a05c.aade6f8","type":"inject","z":"d915a360.b8766","g":"815941740bc0e964","name":"","props":[{"p":"payload","v":"","vt":"date"},{"p":"topic","v":"","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":95,"y":1300,"wires":[["166c313a.49d2ef"]],"l":false},{"id":"e844d1e8.f8585","type":"http request","z":"d915a360.b8766","g":"815941740bc0e964","name":"","method":"GET","ret":"obj","paytoqs":true,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":215,"y":1300,"wires":[["dbd12e73.be8cb"]],"l":false},{"id":"dbd12e73.be8cb","type":"change","z":"d915a360.b8766","g":"815941740bc0e964","name":"","rules":[{"t":"set","p":"weather_forecast_owm","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":1300,"wires":[["3bbcfa11.167be6"]]},{"id":"3bbcfa11.167be6","type":"debug","z":"d915a360.b8766","g":"815941740bc0e964","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":575,"y":1300,"wires":[],"l":false},{"id":"53c45f68.00321","type":"comment","z":"d915a360.b8766","g":"815941740bc0e964","name":"Open Weather Map [OWM] (Onecall Hourly)","info":"","x":810,"y":1300,"wires":[]},{"id":"8e6b9e728b78b1f0","type":"comment","z":"d915a360.b8766","g":"815941740bc0e964","name":"OpenWeatherMap's OneCall API https://openweathermap.org/api/one-call-3 - free tier gives 1000 calls per day. Icons: https://flows.nodered.org/flow/d7af9f5d6f6923324466c58ea4f0655f","info":"https://openweathermap.org/api/one-call-3","x":660,"y":1500,"wires":[]},{"id":"83a5a47c39e68b98","type":"debug","z":"d915a360.b8766","g":"815941740bc0e964","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.list","targetType":"msg","statusVal":"","statusType":"auto","x":575,"y":1360,"wires":[],"l":false},{"id":"421a1883e4eba346","type":"comment","z":"d915a360.b8766","g":"815941740bc0e964","name":"OWM Air Quality API","info":"https://openweathermap.org/api/air-pollution\n\nHere is a description of Air Quality Index levels:\n\n| Qualitative name | Index | Pollutant concentration in μg/m<sup>3</sup> |\n| --- | --- | --- |\n|  | SO<sub>2</sub> | NO<sub>2</sub> | PM<sub>10</sub> | PM<sub>2.5</sub> | O<sub>3</sub> | CO |\n| Good | 1 | \\[0; 20) | \\[0; 40) | \\[0; 20) | \\[0; 10) | \\[0; 60) | \\[0; 4400) |\n| Fair | 2 | \\[20; 80) | \\[40; 70) | \\[20; 50) | \\[10; 25) | \\[60; 100) | \\[4400; 9400) |\n| Moderate | 3 | \\[80; 250) | \\[70; 150) | \\[50; 100) | \\[25; 50) | \\[100; 140) | \\[9400-12400) |\n| Poor | 4 | \\[250; 350) | \\[150; 200) | \\[100; 200) | \\[50; 75) | \\[140; 180) | \\[12400; 15400) |\n| Very Poor | 5 | ⩾350 | ⩾200 | ⩾200 | ⩾75 | ⩾180 | ⩾15400 |","x":730,"y":1360,"wires":[]},{"id":"287017c287456989","type":"http request","z":"d915a360.b8766","g":"815941740bc0e964","name":"Current Air Quality (Home)","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":320,"y":1360,"wires":[["83a5a47c39e68b98"]]}]

Thanks, but again, I have problems using it because of.......

(I'm not exactly sure)

Run as is I get the error 401.

I have an API KEY for openweather, but it seems it doesn't include what you are using.

Using the Openweather node, and sticking this node after it,

[{"id":"b0be7cdb11639687","type":"function","z":"e43e3902.57c4e8","name":"function 60","func":"function degToCompass(deg) {\n    const directions = [\"Northerly\", \"North North Easterly\", \"North Easterly\", \"East North Easterly\", \"Easterly\",\n                        \"East South Easterly\", \"South Easterly\", \"South South Easterly\",\n                        \"Southerly\", \"South South Westerly\", \"South Westerly\", \"West South Westerly\", \"Westerly\",\n                        \"West North Westerly\", \"North Westerly\", \"North North Westerly\"];\n    const index = Math.round(deg / 22.5) % 16;\n    return directions[index];\n}\n\nfunction getTrend(values, labels = { up: \"increasing\", down: \"decreasing\", flat: \"stable\" }) {\n    let delta = values[values.length - 1] - values[0];\n    if (Math.abs(delta) < 0.5) {\n        return labels.flat;\n    } else if (delta > 0) {\n        return labels.up;\n    } else {\n        return labels.down;\n    }\n}\n\nfunction getWindDirectionChange(startDeg, endDeg) {\n    let diff = (endDeg - startDeg + 360) % 360;\n    if (diff === 0) {\n        return \"same\";\n    } else if (diff > 0 && diff <= 180) {\n        return \"veering\";\n    } else {\n        return \"backing\";\n    }\n}\n\nfunction formatTemp(val) {\n    const rounded = Math.round(val * 10) / 10;\n    return Number.isInteger(rounded) ? `${Math.round(rounded)}°` : `${rounded.toFixed(1)}°`;\n}\n\nfunction formatWindSpeed(ms) {\n    let knots = Math.round(ms * 1.94384);\n    return `${knots} knots`;\n}\n\nfunction describeCloudCover(percent) {\n    if (percent <= 10) return \"clear\";\n    if (percent <= 30) return \"mostly clear\";\n    if (percent <= 60) return \"partly cloudy\";\n    if (percent <= 80) return \"mostly cloudy\";\n    return \"overcast\";\n}\n\nlet forecasts = msg.payload;\nlet temps = [];\nlet windSpeeds = [];\nlet cloudCovers = [];\n\n// Find forecast closest to now\nlet now = Math.floor(Date.now() / 1000);\nlet closest = forecasts[0];\n\nfor (let i = 1; i < 6 && i < forecasts.length; i++) {\n    if (Math.abs(forecasts[i].dt - now) < Math.abs(closest.dt - now)) {\n        closest = forecasts[i];\n    }\n}\n\nlet nowTemp = closest.main.temp;\n\n// Collect values for trend analysis\nfor (let i = 0; i < 6 && i < forecasts.length; i++) {\n    let forecast = forecasts[i];\n    temps.push(forecast.main.temp);\n    windSpeeds.push(forecast.wind.speed);\n    cloudCovers.push(forecast.clouds.all);\n}\n\n// Calculate trends\nlet tempTrend = getTrend(temps);\nlet windTrend = getTrend(windSpeeds);\nlet cloudTrend = getTrend(cloudCovers);\n\n// Wind info\nlet windStartSpeed = formatWindSpeed(windSpeeds[0]);\nlet windEndSpeed = formatWindSpeed(windSpeeds[windSpeeds.length - 1]);\nlet windStartDeg = forecasts[0].wind.deg;\nlet windEndDeg = forecasts[forecasts.length - 1].wind.deg;\nlet windStartDir = degToCompass(windStartDeg);\nlet windEndDir = degToCompass(windEndDeg);\n\n// Determine wind direction change\nlet windDirectionChange = getWindDirectionChange(windStartDeg, windEndDeg);\n\n// Build wind summary\nlet windSummary;\nif (windStartDir === windEndDir) {\n    windSummary = `Winds ${windStartDir} at ${windStartSpeed} ${windTrend} to ${windEndSpeed}.`;\n} else {\n    let changeWord = windDirectionChange === \"veering\" ? \"veering\" : \"backing\";\n    windSummary = `Winds ${windStartDir} at ${windStartSpeed} ${changeWord} to ${windEndDir} at ${windEndSpeed}.`;\n}\n\n// Cloud info\nlet cloudStartLabel = describeCloudCover(cloudCovers[0]);\nlet cloudEndLabel = describeCloudCover(cloudCovers[cloudCovers.length - 1]);\n\n// Final summary\nlet summary =\n    `Today's forecast for Ashfield. ` +\n    `temperature now ${formatTemp(nowTemp)} and ${tempTrend} to ${formatTemp(temps[temps.length - 1])}.\\n` +\n    windSummary + `\\n` +\n    `Cloud coverage ${cloudStartLabel} ${cloudTrend} to ${cloudEndLabel}`;\n\nmsg.payload = summary;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2740,"y":3830,"wires":[["a817c55d52c646bc"]]}]

I get a forecast for today.

Maybe not the best, but it seems to work.

I'll maybe have to make one for tomorrow also. I'll see if needs must.

That is, of course, indicating that your call is unauthorised.

I think that, as jbudd indicated, you need to sign up with a credit-card for the service.

You still get plenty of free calls so it shouldn't cost you anything. 1,000 calls per day is a crazy amount if you are just getting forecasts for even a few locations. There is little point in calling more than about once an hour anyway.

See my post from earlier in the other weather api thread if you want an updated version of this along with examples from several other weather API's.

I have a free subscription and it works to my needs.

I'm not exactly wanting to share my CC details. (Personal)

Anyway, I've kinda solved it anyway....

Thanks all the same.