1 Tomorrow's weather, in one sentence
About twenty minutes · the gentlest one on the page
Why start here: this exercise teaches the single move that the five
others reuse — reaching into an answer and pulling one value out of it. Nothing
else about it is new, and that is the point.
What you want: you type /weather and the bot answers
"Tomorrow in Sion, 12 to 25 degrees, no rain."
you /weather
the bot ☁️ Tomorrow in Sion · 12.0 to 25.4 °C · 0 mm of rain
The nodes: Switch (one extra branch) →
HTTP Request → Code → Telegram. The pattern, with
nothing added to it.
The address:
open-meteo.com · no key needed
https://api.open-meteo.com/v1/forecast?latitude=46.2331&longitude=7.3606
&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weather_code
&timezone=Europe/Zurich&forecast_days=1
The coordinates are Sion's. Put your own in; a right click on any map gives them to you.
The trap: the answer is not a list of days, it is an object holding
lists. Even for a single day, temperature_2m_max arrives as a list, so you
have to write daily.temperature_2m_max[0]. The [0] means "the
first item of the list", and lists are counted from zero. Without it the field turns up
empty and you hunt for the mistake somewhere else for twenty minutes. Second detail:
weather_code is a number rather than a text (0 = clear sky, 3 =
overcast, 61 = light rain). You need a small lookup table in the Code
node.
What you learn: how to walk down a JSON answer and pull the right value
out of it. It is the basic move, and it comes back five more times.
2 Pollen, the same move on another service
About fifteen minutes · shorter than the first one, on purpose
Why it comes second: same publisher as exercise 1, different host,
different shape of answer. Changing service is first of all changing shape, and it costs
nothing to learn that on an easy one.
What you want: /pollen and the bot reports the four pollens
that spoil spring in Valais.
The address:
air-quality-api.open-meteo.com · no key needed
https://air-quality-api.open-meteo.com/v1/air-quality?latitude=46.2331
&longitude=7.3606¤t=grass_pollen,birch_pollen,alder_pollen,ragweed_pollen
&timezone=Europe/Zurich
The trap: here you ask for current and not
daily, and the values arrive on their own, without a list around them. So
current.grass_pollen reads straight, with no [0]. And one more
thing we checked: outside Europe the fields still exist but hold null, which
is JSON's way of saying "there is nothing here". The forecast model only covers the
continent, and nothing in the answer says so.
What you learn: an empty field is not automatically your fault. Before
you go and fix your node, ask whether the service has the data at all.
3 The Rhône, and the word you must not write
About thirty minutes · the most profitable lesson on the page
Why it matters: this is where you find out that a call that succeeds and
a call that asks the right question are two separate things. Everything after this
exercise gets easier to debug.
What you want: /river and the bot gives the measured flow in
cubic metres per second. Useful in Valais when it has rained for three days.
The source: the measurements come from the Swiss Federal Office for the
Environment. They are republished by api.existenz.ch, run by a Swiss
association. Station 2011 is the Rhône at Sion.
api.existenz.ch · no key needed
https://api.existenz.ch/apiv1/hydro/latest?locations=2011
¶meters=flow,temperature&app=YOUR_APP&version=1.0
The app parameter expects the name of your bot. No key, no check: it is a
courtesy that lets whoever pays for the hosting see who is calling. Put a readable name in
rather than leaving it as it stands.
The trap, and it is a pretty one: the parameter is called
flow. Write discharge, the word a technical dictionary hands you
when you look up river flow, and the service still answers 200 — the status code
meaning the call itself went through — carrying "payload": [], an empty
list. No error, no hint, nothing. We called both to be sure: flow returns the
value, discharge returns an empty list.
The second trap: the answer is a flat list of objects shaped
{timestamp, loc, par, val}. You ask for two parameters and you may well get
one back: station 2011 publishes the flow but not the water temperature. So look for the
item whose par field equals flow. Never take
payload[0] on the assumption that the order will follow your request.
What you learn: a 200 does not prove you asked the right question. That
lesson pays for itself far beyond rivers.
4 The next train, with a parameter that comes from you
About forty minutes · the first one where typed text leaves your server
Why it is different: in the first three exercises the address never
changes. Here it depends on what the person typed, and everything difficult about the
exercise follows from that one fact.
What you want: /train Sion Lausanne and the bot answers with
the departure time, the platform and any delay.
you /train Sion Lausanne
the bot 🚆 11:39 · platform 3 · 1 min late · 2 h 06 on the way
transport.opendata.ch · no key needed
https://transport.opendata.ch/v1/connections?from=Sion&to=Lausanne&limit=1
The nodes: before the HTTP Request, you have to cut the
command text apart to isolate the two stations. Either a Code node, or an
expression written straight into the field, along the lines of
{{ $json.message.text.split(' ')[1] }}. An expression is how n8n lets you
compute a value on the spot instead of typing a fixed one; the double braces are what mark
it as one.
The trap: station names contain spaces, hyphens and accented letters. A
name like Genève-Aéroport, pasted straight into the address, falls apart, because a web
address only accepts a narrow set of characters and the rest have to be escaped. Rather
than patching the text by hand, switch on Send Query Parameters in the
HTTP Request node and give from and to as two
separate parameters: n8n encodes them correctly for you. Tested with Genève-Aéroport, it
goes through.
What you learn: how to get text written by a human safely into an outside
call. Done carelessly, this is the door that injections walk through — an injection being
what happens when something typed by a stranger gets treated as part of your instruction
instead of as plain data. It is the same caution as the apostrophes in the bot's SQL.
5 Aircraft overhead, and a real token
An evening, honestly · the hardest exercise here
Fair warning: this one is not a coffee-break exercise. Two calls instead
of one, a credential that expires, and a rule change in March 2026 that has left most of
the tutorials on the web quietly wrong. Start it when you have a clear evening.
What you want: /planes and the bot counts the aircraft in a
square of sky around you. The service is OpenSky Network, an association based in
Switzerland.
opensky-network.org · limited anonymous access
https://opensky-network.org/api/states/all?lamin=46.0&lomin=7.0
&lamax=46.5&lomax=7.6
With no account the call still works, on a daily budget. A response header called
x-rate-limit-remaining tells you what is left of it — a header being an
extra line of information the server sends alongside the data itself. For a personal bot that
answers when asked, that budget is enough.
The beginner trap: when the square of sky is empty, the
states field holds null instead of an empty list. Any node that
counts a length stops right there. Checked on the same day at the same minute: over
Valais, null; over Zurich, 47 aircraft. The difference was not in the code.
The serious trap, the one that makes the exercise: since March 2026, a
username and password placed on the call do nothing whatsoever. They do not even raise an
error, they are simply ignored, and you drop back into the anonymous quota without
noticing. For a real quota you go through OAuth2: you ask an authentication address for a
token by sending it a client identifier and a client secret, then you present that token
in the Authorization header of every call. A token is a long string that
stands for "this caller is allowed", valid for a limited time only — which means your
workflow has to know how to ask for a fresh one.
The nodes: two HTTP Request nodes instead of one. The first
fetches the token with a POST, the verb used when you send something rather than merely
ask for it; the second uses the token. Plenty of tutorials online predate the change, and
that is why they no longer work, rather than you going about it badly.
What you learn: the difference between a key you paste once and a token
you renew. That two-step shape is how most professional services are entered.
6 MCP, or how to stop writing one node per service
An hour to wire, longer to think through · hard in a different way
Why it closes the page: the five exercises before it add one service at a
time. This one adds a whole family at once, and it changes what your bot is for.
What you want: to stop redoing the previous five exercises for every new
idea. MCP, short for Model Context Protocol, is a convention by which a server publishes a
list of tools, each described in words a language model can read, so that the model itself
works out when to use them. n8n knows how to connect to such a server, and the
documentation explains the settings without ever saying what the thing is good for.
The node: MCP Client Tool. It does not sit in the main line
of the workflow; it hangs underneath an Agent node, as one more tool the agent
can reach for. An Agent node is the node that hands the conversation to a language model
and lets that model choose among the tools attached to it. You fill in the server address
under Endpoint, you pick the authentication (none, bearer token, header, or
OAuth2, depending on the server), and the agent discovers by itself what it can do.
The trap: the tool descriptions are sent to the model on every turn of
the conversation. A server publishing thirty tools means thirty descriptions on every
single message, and a bill that follows. The node offers Tools to Include and
Tools to Exclude so that you expose only what you need. Use them on your first
try, not after your first invoice.
The confusion to avoid: n8n has two nodes with neighbouring names.
MCP Client Tool fetches tools from elsewhere for your bot.
MCP Server Trigger does the reverse: it exposes the workflows sitting in your
n8n to an outside agent. This exercise is the client. The server is the step after, and it
is the more entertaining one, because that is where your task bot becomes a tool other
assistants can call.
What you learn: where the ceiling of one-node-per-service sits, and what
lies above it. This is the point where your bot stops being an exercise and becomes a
piece of something larger.