How does an app know your train is running late, right now?
Open any train-status app a few minutes before you leave for the station. It shows you the live position of your train, its current delay in minutes, and the platform number — updated to the second. That app was not built with a giant table of every train's position stored inside it. If it were, the moment the train moved, the app would be wrong. Instead, every time you open the screen, the app quietly sends a question across the internet to a railway computer system: "Where is train 12622 right now?" A second later, an answer comes back, and the app displays it. That question-and-answer exchange between two programs is what this chapter is about. The formal name for the rulebook that lets one program ask another program a question like this is an API — an Application Programming Interface — and by the end of this chapter you will be writing Python code that does exactly this: asking a real computer on the internet a question and getting back a real, live answer.
Two programs, one conversation
Think about what happens when you order food from a printed restaurant menu. You do not walk into the kitchen and start cooking. You tell the waiter what you want, using items from a fixed menu, and the waiter brings back a plate. You never see how the kitchen works — you only need to know what is on the menu and what you get back.
An API works the same way for two pieces of software. One program (your Python code) wants some data or wants a service performed — "give me today's weather for these coordinates," "give me the exchange rate for USD to INR," "give me the score of this cricket match." It does not need to know how the other program stores its data, what programming language it is written in, or how its database works. It only needs to know the "menu" — the fixed list of questions the other program agrees to answer, and the exact format for asking. That menu, plus the rules for using it, is the API. The program that answers the question is called the server; the program asking is called the client. When your Python script is the one requesting data, your script is the client.
This is different from a normal website. A website like a newspaper's homepage is built for a human to read in a browser — it comes back as HTML, full of layout, images, and text meant for eyes. An API endpoint is built for a program to read — it comes back as plain structured data, with no visual design at all. This is worth naming explicitly because it trips up a lot of beginners:
Common misconception: "An API is just a special kind of website." It is not. A website's job is to look good in a browser. An API's job is to hand over raw data in a predictable format so that any program, in any language, can use it. You can technically type an API's address into a browser and see something appear, but what appears is raw data (usually JSON, which you will meet shortly) — not a designed page. The browser is not being used as a browser here; it is just acting as the simplest possible client that can send a request and display whatever text comes back.
The address you send your question to: a URL
To ask an API a question, you need its address. That address is a URL (Uniform Resource Locator), and every API URL is built from the same four pieces, in the same order. Consider the exact URL we will use in this chapter — the address for a free, no-signup weather service called Open-Meteo:
Reading left to right: https:// is the protocol — the shared set of rules two computers use to talk over the internet (almost every API you will ever use runs over HTTPS, the secure version of HTTP). api.open-meteo.com is the host — literally, which computer on the internet you are talking to. /v1/forecast is the path, also called the endpoint — which specific "menu item" on that server you want; a weather service might have separate endpoints for current conditions, a 7-day forecast, and historical data, each with its own path. Finally, everything after the ? is the query string — a list of key=value pairs joined by &, which is how you attach the specific details of your question: which coordinates, and whether you want the current weather. Change the query string and you get a different answer from the exact same endpoint.
Making your first request with Python
Python does not talk to the internet on its own — you use a library. The standard one, and the one used throughout this chapter, is called requests. It is not part of core Python, so on your own machine you would install it once with pip install requests; most classroom and online Python environments already have it ready.
Here is the smallest complete request — asking Open-Meteo for Delhi's current weather:
import requests
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": 28.61,
"longitude": 77.21,
"current_weather": True
}
response = requests.get(url, params=params)
print(response)
Notice we did not build the long URL with the ? and & by hand. We gave requests.get the base URL and a separate Python dictionary, params, and it assembles the query string for us — this is both less error-prone and, importantly, handles a detail called URL-encoding automatically (turning spaces and special characters into the safe codes a URL is allowed to contain).
Run this, and you will see:
<Response [200]>
That 200 is an HTTP status code — a three-digit number the server sends back to tell you what happened to your request. 200 means "success, here is your data." You will meet the other common codes shortly. But notice what this printout is not: it is not the weather. It is a Response object — a Python object that wraps the server's reply and gives you tools to unpack it. This is the second common misconception worth naming precisely:
Common misconception: "The response IS the data." It is not — the response is a container that the data arrives inside. Watch what happens if you try to reach into it directly, the way you would with a dictionary:
temperature = response["current_weather"]["temperature"]
print(temperature)
TypeError: 'Response' object is not subscriptable
Python is telling you, correctly, that a Response object does not support square-bracket indexing like a dictionary does — because it isn't a dictionary. It is an object with its own attributes: response.status_code (the number you saw above), response.text (the raw reply as one long string of characters), and, most usefully, response.json() — a method that reads that raw text, checks that it is valid JSON, and hands you back an actual Python dictionary (or list) you can index into normally.
JSON: the format almost every API speaks
JSON (JavaScript Object Notation, though it has nothing Python- or JavaScript-specific about its rules) is a plain-text way of writing structured data using just objects (in curly braces, like Python dictionaries), arrays (in square brackets, like Python lists), strings, numbers, booleans, and null. It has become the near-universal language APIs use to hand data back and forth, because almost every programming language can read and write it. Here is roughly what Open-Meteo's reply for Delhi actually looks like as raw JSON text:
{
"latitude": 28.61,
"longitude": 77.21,
"generationtime_ms": 0.12,
"utc_offset_seconds": 0,
"timezone": "GMT",
"elevation": 216.0,
"current_weather": {
"temperature": 34.2,
"windspeed": 11.3,
"winddirection": 210,
"weathercode": 1,
"time": "2026-08-11T10:00"
}
}
Lay this next to a Python dictionary and the resemblance is not an accident — JSON's object syntax was deliberately designed to look almost identical to dictionary and list literals in mainstream languages. That is exactly why response.json() can convert it so directly: it parses this text and returns Python's native equivalent, a nested dictionary, with the inner current_weather value itself being another dictionary.
So the corrected version of our earlier code is:
data = response.json()
temperature = data["current_weather"]["temperature"]
print(f"Delhi temperature: {temperature}°C")
Delhi temperature: 34.2°C
Trace it: response.json() reads the reply body and returns a dictionary, which we store in data. data["current_weather"] reaches the inner dictionary shown above. ["temperature"] reaches the number inside that. The f-string then formats it into a sentence. Every step is indexing into an ordinary nested Python dictionary — the API-specific part of the work was only ever the single line that fetched it.
Fetching for more than one place: looping over an API call
A single request answers one question. To answer several — say, today's temperature in Delhi, Mumbai, and Chennai — you loop, changing the parameters each time and, this time, checking the status code defensively before trusting the reply:
cities = {
"Delhi": (28.61, 77.21),
"Mumbai": (19.07, 72.87),
"Chennai": (13.08, 80.27)
}
for city, (lat, lon) in cities.items():
params = {"latitude": lat, "longitude": lon, "current_weather": True}
response = requests.get(url, params=params)
if response.status_code == 200:
temp = response.json()["current_weather"]["temperature"]
print(f"{city}: {temp}°C")
else:
print(f"{city}: request failed with status {response.status_code}")
Two Python details make this loop work cleanly. First, cities.items() yields pairs like ("Delhi", (28.61, 77.21)), and the loop header for city, (lat, lon) in cities.items() unpacks each pair two levels deep in one line: city takes the string, and the inner tuple is simultaneously unpacked into lat and lon. Second, the if response.status_code == 200 check is not decoration — it is what separates a script that silently breaks the day a server has a bad minute from one that fails gracefully. With three valid Indian city coordinates and a working connection, this prints three lines, one per city, each with that city's live temperature.
Reading HTTP status codes
Every single HTTP response carries a status code, and treating it as optional information is how "working" scripts break in production. The codes fall into ranges you should recognise on sight:
- 200 OK — the request succeeded; the body contains what you asked for.
- 201 Created — succeeded, and as a result something new was created on the server (common after a POST request that submits data, rather than a GET that only reads it).
- 400 Bad Request — the server could not understand your request; usually a malformed parameter, such as sending text where a number was expected.
- 401 Unauthorized — you did not prove who you are; the request needs valid credentials that were missing or wrong.
- 403 Forbidden — the server understood exactly who you are, and is refusing anyway; you lack permission for this resource.
- 404 Not Found — the path in your URL does not correspond to any endpoint on this server; a very common typo bug.
- 429 Too Many Requests — you have been rate-limited; you are asking faster than the server allows.
- 500 Internal Server Error — the fault is on the server's side, not yours; something broke while it was processing your otherwise-valid request.
The pattern worth internalising: codes starting with 2 mean success, 4 means you (the client) made a mistake, and 5 means the server made a mistake. Reading the first digit alone already tells you where to start debugging.
A quirk worth knowing: booleans in query parameters
Look again at the params dictionary from our first example: "current_weather": True. That True is a genuine Python boolean, not the string "true". When requests builds the URL's query string, it converts every parameter value to text, and for a Python boolean, converting to text produces the capitalised words True or False — because that is exactly what str(True) gives you in Python. So the actual URL sent to the server ends in current_weather=True, capital T, even though you typed a lowercase word in your diagram or documentation. Open-Meteo happens to accept this specific parameter case-insensitively, so it works here — but many stricter APIs expect exactly the lowercase JSON-style words true and false, and will silently misinterpret or reject a capitalised True. The safe habit, once you notice this, is to pass the literal string "true" yourself in params whenever an API's documentation shows a lowercase boolean, rather than relying on how Python happens to stringify its own boolean type.
When an API needs to know who you are
Open-Meteo is unusual in asking for nothing beyond your query — no signup, no key. Most real-world APIs are not this open, because they want to track usage, enforce limits, or restrict data to paying or registered users. These APIs require an API key: a long, unique string issued to you when you register as a developer, which you must attach to every request as proof of who is asking. There are two common places a key travels. As a query parameter, appended just like any other parameter:
params = {"q": "search term", "api_key": "your_key_here"}
response = requests.get(url, params=params)
Or, more commonly for security-conscious APIs, inside an HTTP header — extra metadata sent alongside the URL, separate from the visible query string, so the key does not end up logged in browser history or server access logs the way a URL parameter can:
headers = {"Authorization": "Bearer your_key_here"}
response = requests.get(url, headers=headers)
If a key is missing, invalid, or expired, this is precisely the situation that produces the 401 Unauthorized status code from the previous section — the server understood your request perfectly well; it simply does not know, or does not believe, who is asking. A golden rule for working with real keys: never write one directly into code you might share, commit to version control, or post online. A leaked key lets anyone impersonate your access — treat it with the same caution as a password.
Putting the whole journey together
It helps to see the complete round trip in one picture — your Python script sends a request across the internet, and a server somewhere sends structured data back:
Every API call you write, no matter how complex the service, is a version of this same two-arrow picture: your code sends a request built from a URL and parameters, and gets back a response carrying a status code and a JSON body. requests.get() sends the top arrow; response.status_code and response.json() are how you read the bottom one.
Check your understanding
- In the URL
https://api.example.com/v2/students?class=8&subject=science, identify the host, the path, and the two query parameters. - You run
response = requests.get(url)and then tryprint(response["name"]). Python raises an error. What is the error, why does it happen, and what single method call fixes it? - A request returns
response.status_code == 404. Is the problem more likely a typo in your endpoint path, or a temporary problem with the server itself? Explain your reasoning. - Explain, in your own words, why an API is not "just a website," even though both are reached with a URL.
- You write
params = {"active": True}and pass it torequests.get(). What literal text will actually appear in the URL's query string for this parameter, and why might that matter when it is used as arequestsquery parameter for a strict API?
Summary
An API is a defined set of rules that lets one program request data or a service from another, without either needing to know how the other is built internally — the client asks, the server answers. Every API request is aimed at a URL built from four parts: protocol, host, path (endpoint), and an optional query string of key-value parameters that customise the question. In Python, the requests library sends that request with requests.get(url, params=...) and hands back a Response object — not the data itself, but a wrapper carrying a numeric status_code and a body you unlock with .json(), which converts the server's JSON text into an ordinary nested Python dictionary you can index like any other. Status codes group into success (2xx), your-mistake (4xx), and server's-mistake (5xx) ranges, and checking them before trusting a response is what separates a fragile script from a reliable one. Many real APIs additionally require an API key, sent as a parameter or, more safely, as a request header, to prove who is asking. Master this one request-response pattern, and you have the core skill behind every weather app, price tracker, and live-score screen you have ever used — you can now build one yourself.
Practice Exercises
Now it is time to practice! Complete these challenges to solidify your understanding:
- Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
- Exercise 2: Find a real-world example where apis with python: fetching web data is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
- Exercise 3: Create a mind-map connecting apis with python: fetching web data to at least 3 other topics you have studied.