Unit 13 · lesson
The API Is Outside Your Trust Boundary
Core path: 30 minutes
An API response can be valid JSON and still be wrong for your program.
It can contain missing fields, unexpected types, empty lists, stale values, or a perfectly reasonable error message you never planned for.
The network boundary means one thing very clearly:
Data coming from another system is input. Treat it like input.
A successful request is only one layer of success
Suppose this code runs:
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
Three different things have happened:
transport worked
↓
HTTP status was acceptable
↓
response body parsed as JSON
You still have not proved that the JSON contains the fields your application expects.
This can parse successfully:
{
"message": "temporary maintenance"
}
But code expecting:
data[0]["title"]
is going to have a bad day.
Validate the shape you depend on
Imagine the dashboard expects a list of todo dictionaries.
Before analysis, check the assumptions that matter:
if not isinstance(todo_list, list):
raise ValueError("Expected a list of todos")
Then inspect each record before assuming fields exist:
for todo in todo_list:
if not isinstance(todo, dict):
raise ValueError("Expected each todo to be an object")
if "title" not in todo or "completed" not in todo:
raise ValueError("Todo record missing required fields")
This is intentionally simple validation, not a full schema framework.
The point is to make the contract explicit.
Missing field versus wrong type
These are different failures:
{"title": "inspect logs"}
and:
{"title": "inspect logs", "completed": "yes"}
The first is missing a required field.
The second includes the field but gives it an unexpected type.
If your program treats the string "false" like the Boolean False, the output may be wrong without producing a network error at all.
That is why "the API returned 200" is not enough evidence.
Status codes describe the HTTP layer
A rough beginner mental model:
2xx → request was handled successfully at the HTTP layer
4xx → the request/resource has a client-side problem
5xx → the server reports a failure
That is useful, but do not turn it into:
200 = the data is definitely correct
A 200 OK response can still contain incomplete or unexpected data for your application.
Different layer. Different claim.
Timeouts are part of correctness
This is better than an unbounded request:
requests.get(url, timeout=5)
Why?
Because "wait forever" is usually not a useful application behavior.
A timeout creates a defined failure path your program can handle.
That is not only performance. It is control over the behavior of the system when another system does not respond.
Fallbacks must tell the truth
Your Lesson 4 dashboard can use a local fixture when the network request fails.
That is good engineering for a classroom exercise.
But the output must say:
SOURCE: LOCAL FIXTURE
not:
SOURCE: LIVE API
A fallback that hides its origin changes the meaning of the evidence.
This pattern will matter again with AI tools: a generated answer, cached response, mock fixture, or simulated terminal output must not be presented as evidence from a real system it never contacted.
Create four data cases
Use copies of the local fixture to create:
Case A — expected structure
[{"title": "inspect logs", "completed": false}]
Case B — missing field
[{"title": "inspect logs"}]
Case C — wrong type
[{"title": "inspect logs", "completed": "false"}]
Case D — wrong top-level shape
{"title": "inspect logs", "completed": false}
Run your validation/analysis path against all four.
For each one, record:
Did JSON parsing succeed?
Did contract validation succeed?
Did analysis run?
What evidence identified the failure layer?
Read one transaction end to end
For one real or supplied API case, document:
client:
URL:
method:
timeout:
HTTP status:
body format:
top-level Python type after response.json():
required fields:
validation result:
analysis result:
data source label:
Now you can explain the entire path instead of pointing at a diagram and saying "the request goes to the server."
The arrow between client and server crosses a trust boundary.
That is what the arrow means.
Reader workbench
Separate external-data shape from live network access
This is the Unit's one-file practice surface. Read the code, predict one result, run it, then change a value, input, condition, or boundary and explain why the evidence changed. Multi-file projects, Git, terminals, packages, and live services still belong in the full development workspace.
Run the code to see output.
The full Unit build performs real HTTP work in a real Python workspace. This browser exercise isolates JSON/data-contract reasoning and does not claim a network request occurred.