HTTP requests and JSON parsing in Python
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Making an HTTP request and turning the response into Python data is one of the most common tasks in backend work, scripting, and API integrations. In practice, the job is not just "download text and call json.loads", because you also need timeouts, status checks, and validation that the server actually returned JSON.
Making an HTTP request with requests
The most widely used library for this is requests. A basic GET request looks simple, but the important pieces are the timeout and status handling.
raise_for_status() is worth using by default. Without it, a 404 or 500 response still gives you a Response object, and your code may try to parse an error page as if it were valid data.
For query parameters and headers, pass dictionaries instead of building the URL by hand:
This is safer and easier to maintain than manual string concatenation.
Parsing JSON safely
If the server returns JSON, use response.json() instead of json.loads(response.text). It is shorter and communicates intent clearly.
The parsed value is standard Python data, usually a dict or list. At that point you can treat it like any other object from Python code.
If you need to serialize Python data back into JSON for a request body, use the json= parameter:
Using json=payload is better than calling json.dumps yourself in most cases because requests also sets the appropriate content type header.
Handling bad responses and invalid JSON
Real APIs fail in several ways:
- the server can be unreachable
- the status code can indicate an error
- the response body can be HTML instead of JSON
- the JSON can be valid but missing the fields you expect
A practical pattern is to catch transport errors separately from JSON parsing errors.
ValueError is the relevant exception here because response.json() raises it when the body cannot be decoded as JSON.
Working with nested JSON
Many APIs return nested objects. Use dictionary access carefully and prefer .get() when fields may be optional.
That pattern avoids a KeyError if address is missing. For larger payloads, it is often worth validating the schema explicitly with a dataclass or a library such as Pydantic, but the core parsing step is still the same.
Common Pitfalls
- Forgetting a timeout. A request without one can hang much longer than expected.
- Calling
response.json()before checking the status code and then debugging a confusing parse failure. - Assuming every API always returns JSON. Some endpoints return HTML or plain text on errors.
- Building query strings manually instead of using
params=. - Accessing nested keys directly when the field may be optional.
Summary
- Use
requests.getorrequests.postwith an explicit timeout. - Call
raise_for_status()so HTTP errors fail early. - Parse JSON with
response.json()instead of decoding text manually. - Use
json=when sending JSON in a request body. - Handle network errors, HTTP errors, and invalid JSON separately.
- Treat nested API fields carefully, especially when keys may be missing.
Related reading
- HTTP Requests in an AWS Lambda
- HTTP response code for POST when resource already exists
- http server respnd with an output from an async function
- HTTP URL Address Encoding in Java
- Huggingface transformers trainer output not giving any predictions?
- Hyperparameter optimization of MLPRegressor in scikit-learn
- HttpClient and using proxy - constantly getting 407
- HttpListener class with HTTPS support

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.