Skip to content
The AI Agent Factory

14.6 JSON and REST APIs

Approved by
Panaversity ·
13 min read

In everyday life. A takeout window has a menu taped beside it. You ask for item 12 with no onions, and you get a bag with a receipt. You never see the kitchen. So you check the bag before you leave, because the kitchen can change a recipe without telling you.

What you will learn. How to read a request to another program and its answer, and check what arrives before you use it.

Why it matters. The other program belongs to someone else, who can change it without telling you. So your program checks every answer before it uses it.

What is JSON?

JSON is the text format most programs use to send records to each other. { } holds a dictionary and [ ] a list, text goes in quotes, and numbers, true, false and null are written plainly. JSON has no dates, so a date travels as text, as in {"number": "INV-8306", "dueDate": "2027-03-08"}.

What are the parts of a request?

An API, an application programming interface, is a way for one program to ask another for something. A REST API does it over the web, with one address, or URL, for each kind of thing. Each of those addresses is an endpoint. At the takeout window, the menu is the list of endpoints, your order is the request, and the bag with its receipt is the response.

Part of a request or responseWhat it isBrightline sample API
MethodWhat you want to do: GET reads, POST createsGET
EndpointThe address of the thing/invoices
Query stringOptions after a ?, joined by &?status=open&page=2
HeaderA labeled extra on the request or responseX-API-Key: ..., Content-Type: application/json
Status codeA number that says how it went200, 401, 503
BodyThe data, usually JSON{"invoices": [...], "page": 2, "pages": 3}

Pages. An API rarely returns thousands of records at once. It returns a page of them and tells you how many pages there are. A common bug in a client, the program that sends the requests, is reading only the first page. It gives no warning.

At work. Brightline's repository includes a small sample REST API that runs on your own computer. It needs the key from .env and serves five invoices per page.

Here is one order at the window, and what comes back. localhost in the address means your own computer. The key comes from the environment, as Where does the key go? explains. Predict each line.

// the order: GET /invoices, the open ones, page 2, with the key in a header
const response = await fetch("http://localhost:4010/invoices?status=open&page=2", {
  headers: { "X-API-Key": process.env.AP_API_KEY ?? "" },
});
// the receipt: how it went, and what kind of body came back
console.log(response.status, response.headers.get("content-type"));
// the bag: the body, read as JSON
const body = await response.json();
console.log(body.page, body.pages, body.invoices.length, body.invoices[0].number);
// a record of your own, turned into JSON text
console.log(JSON.stringify({ number: "INV-8306", amountCents: 158200, dueDate: "2027-03-08" }));
LanguageThe marks in this code
TypeScriptfetch makes the request, and await waits for the answer, which 14.7 explains. process.env.AP_API_KEY reads the key from the environment, and ?? "" uses empty text if there is none. JSON.stringify turns a record into JSON text.
Pythonhttpx is the library this book uses to send web requests, and its params builds the query string. os.environ["AP_API_KEY"] reads the key from the environment. json.dumps turns a record into JSON text.
200 application/json
2 3 5 INV-8306
{"number":"INV-8306","amountCents":158200,"dueDate":"2027-03-08"}

What should the client do for each status code?

Act on each one on purpose. Neither fetch nor httpx.get raises an error for a 401 or a 503. Each hands back the answer with its code, so a program that ignores the code reads an error message as if it were invoices.

CodeMeansWhat the client should do
200OKRead the body
400Bad request: your request was wrongFix the request. Retrying will not help
401Not authorized: no key, or the wrong keyCheck the key. Retrying will not help
404Not foundCheck the endpoint
429Too many requestsWait, then try again
503The server is busy or down for a momentWait briefly and retry

Why did the tests pass and the live run fail?

Because the tests and the code shared one picture of the data, and the API had its own. Only the live answer shows what the API really sends, and the schema at the edge reads it.

At work. Ana's Python client passes every test against a fake API, a stand-in that answers like the real one. The agent built it with the field names she expected, such as vendor_id. Against the sample API, the client is refused on the first invoice, because that API's JSON says vendorId. The schema caught it before any invoice was used. She asks for the model to accept both names.

Where does the key go?

Into an environment variable, never into code, a test or a commit. An API key is a secret that proves who is calling. An environment variable is a named value the operating system gives a running program, usually loaded from a .env file that git ignores. If a key is ever committed, treat it as leaked, even if you delete it in the next commit, because git keeps every commit.

Try it

Set up the sample API. Choose a made-up key, such as a word and a number. Send: "Please copy .env.example to .env, and put my made-up key in it: [your key]. Then start the sample API in the background, and tell me how you will stop it."

Run. Send: "Please run the file in 14-6 and show me what it prints. Don't explain it yet."

Modify. Send: "Please change page 2 to page 4 in the file. Don't run it yet." There is no page 4, and this API treats that as your mistake, so it answers 400. From the table, what should a client do then? The file still goes on to read the invoices: what will happen? Then ask the agent to run it, and ask: "What did the body of that answer say?"

Make. Send: "Please change the file so it checks the status code before it reads the invoices, and prints the error when the code is not 200. Run it."

Stop. Send: "Please stop the sample API now, and show me that nothing is still running."

What you should see. 400, then a TypeError or KeyError, because the body holds an error, not a page of invoices. After your change, the error is printed plainly.

The operating rule: read an API call by its parts, and handle each status code on purpose. Read every page, keep keys in the environment, and validate the body with a schema before you use it, even when your tests pass.

Figure 14.7. One exchange with the sample API at localhost:4010. The request is GET /invoices with the query string ?status=open&page=2 and an X-API-Key header loaded from the environment. The response is 200 OK, with Content-Type: application/json and a JSON body holding five invoices, page 2 of 3, starting with INV-8306.

Figure 14.7. One request, one response.

Next. 14.7 decides how long to wait for a call, and when to try again.

Check yourself

Question 1 / 8

0 answered

An API answers 401. What should the client do?

Owner
Panaversity