The web without the pages
Browsers are not the only HTTP clients. Most HTTP traffic today is programs talking to programs: your phone's weather app fetching a forecast, a checkout page asking a payment company's servers to charge a card, one company's backend pulling shipping rates from another's. These program-facing services are APIs (Application Programming Interfaces), and building and calling them is a large share of what working software engineers do all week.
An API exists because a program needs data, not a rendered page. GET /weather/lisbon on an API answers with the temperature as structured data a program can use, instead of HTML meant for human eyes. Everything else is the HTTP you already know:
- Each endpoint, meaning one method and path combination the API answers, such as
GET /weather/{city}, is a resource in the REST sense of lesson 4-3. - Requests and responses carry the same headers, status codes, and bodies from lessons 4-1 and 4-2.
- The body format is almost always JSON, which deserves a proper introduction.
JSON, properly
JSON (JavaScript Object Notation) is a plain-text format for structured data. It won the API world because it is readable by humans, and because its shapes map directly onto the data structures every language already has:
{
"city": "Lisbon",
"temp_c": 21,
"windy": false,
"tags": ["sunny", "mild"]
}{ }is an object: key–value pairs (a dict in Python, an object in JavaScript).[ ]is an array: an ordered list.- Values are strings (always in double quotes), numbers,
true/false,null, or nested objects and arrays.
The strictness matters: single quotes, trailing commas, and Python-style True are all invalid JSON, and a server receiving them answers 400 Bad Request. When an API call fails with a 400, a malformed body is the first suspect.
Parsing a JSON response in Python
json.loads turns JSON text into Python data, where loads is short for "load from string".
import json response = '{"city": "Lisbon", "temp_c": 21, "windy": false, "tags": ["sunny", "mild"]}' data = json.loads(response) print(data["city"], "is", data["temp_c"], "C") print("windy:", data["windy"]) print("first tag:", data["tags"][0])
Output
Lisbon is 21 C
windy: False
first tag: sunnyAfter parsing, the object is a dict and the array is a list, so you index them like any other Python data. Nothing about them remembers having been JSON.
Notice that the JSON false came back as Python's False, with a capital F. The parser translates values into the host language's own spellings, which is why the strictness rules apply to the text on the wire and not to your code.
The data["tags"][0] line shows the two shapes composing. A key lookup returns a list, and the list is then indexed, which is how deeply nested API responses get read one step at a time.
The valid spelling
The valid JSON is {"name": "ada", "admin": true}.
JSON requires double quotes around both strings and keys, which rules out any single-quoted version. It forbids trailing commas, and it spells booleans lowercase as true, false, and null, which rules out the Python-style True.
Those rules feel picky until you consider the alternative. A format with optional variations needs a parser per dialect, and JSON's whole value is that one parser reads every producer's output.
Strict parsers are why 400 Bad Request so often turns out to be a typo in a hand-written body. The server never got as far as your data, so the error says nothing about what you were trying to do.
Building a response body
Playing the server now: total up the orders and emit the JSON text that would become the response body.
import json orders = [ {"id": 1, "item": "keyboard", "price": 45}, {"id": 2, "item": "monitor", "price": 220}, ] total = sum(order["price"] for order in orders) reply = {"count": len(orders), "total": total} print(json.dumps(reply))
Output
{"count": 2, "total": 265}Reading the code
sum(order["price"] for order in orders)adds the prices in one line, pulling one key out of each dict as it goes.len(orders)is the count, taken from the list itself rather than tracked separately.json.dumps(reply)turns the dict into JSON text, the mirror image ofjson.loads. The pair is the whole interface between your program's data and the wire.- The printed output has no newlines or indentation, which is what a real API sends. Pretty-printing is for humans reading the response, and it only adds bytes to the transfer.
The format APIs speak
It is JSON, short for JavaScript Object Notation.
It is the default body format for API requests and responses, declared with the Content-Type: application/json header from lesson 4-2. This lesson parsed it with json.loads and produced it with json.dumps, which is the round trip in both directions.
Its name comes from the language whose object syntax it borrowed, though it long ago stopped being a JavaScript-only concern. Every language in common use ships a JSON parser.
Unit 5 puts all of this together, creating, reading, and deleting resources on a JSON API with curl.