Python Requests: Query Parameters, Headers, and JSON Body
python requests query parameters headers and json body: Learn how to send query parameters, custom headers, and JSON bodies with Python requests. Practical examples fo...
python requests query parameters headers and json body requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When working with the Python requests library, you often need to send query parameters, custom headers, and a JSON body in a single HTTP request. Understanding how these components combine is essential for building reliable API clients. This article covers the syntax and behavior of each part, then shows how they work together in realistic scenarios.
Constructing a Request with Query Parameters
The requests library accepts query parameters through the params keyword argument. This argument takes a dictionary or a list of tuples, and requests encodes it into the URL's query string automatically. For example:
import requests params = { "q": "python requests", "page": 2, "sort": "desc" } response = requests.get("https://api.example.com/search", params=params) print(response.url) # https://api.example.com/search?q=python+requests&page=2&sort=desc
The library handles URL encoding for you. Spaces become + or %20 depending on the context, and special characters are percent-encoded. If you need to pass a list of values for the same key, use a list in the dictionary:
params = {"tag": ["python", "http"]} # https://api.example.com/search?tag=python&tag=http
This is often more reliable than manually building the query string, especially when values contain reserved characters.
Setting Headers for Authentication and Content Type
Custom headers are passed via the headers argument. This is a plain dictionary mapping header names to values. Common use cases include authentication tokens, custom Accept types, and overriding the default User-Agent.
headers = { "Authorization": "Bearer your-token-here", "Accept": "application/json", "User-Agent": "my-api-client/1.0" } response = requests.get("https://api.example.com/user", headers=headers)
When you send a JSON body, the Content-Type header is set automatically by the json parameter. However, if you construct the body manually with data, you must set Content-Type yourself. For example:
import json payload = {"name": "Alice", "role": "admin"} headers = {"Content-Type": "application/json"} response = requests.post("https://api.example.com/users", data=json.dumps(payload), headers=headers)
Using json.dumps() and data gives you explicit control, but the json parameter is simpler and less error-prone.
Sending a JSON Body in POST Requests
The json parameter in requests.post() (and other methods) serializes a Python dictionary to a JSON string and sets the Content-Type header to application/json automatically. This is the recommended way to send JSON payloads.
payload = { "title": "New Task", "completed": False, "tags": ["work", "urgent"] } response = requests.post("https://api.example.com/tasks", json=payload)
The library converts the dictionary using json.dumps() internally, so you don't need to import the json module or manage the header. This also handles nested structures and Unicode correctly.
If you need to send a JSON body that is not a dictionary, such as a list or a scalar, you can still use json — it accepts any JSON-serializable object.
Combining Parameters, Headers, and JSON Body
A single request often needs all three: query parameters for filtering or pagination, headers for authentication or content negotiation, and a JSON body for the request data. The requests library allows you to pass all three arguments to the same call.
params = {"page": 1, "limit": 10} headers = { "Authorization": "Bearer token123", "Accept": "application/json" } payload = { "query": "python", "filters": {"status": "active"} } response = requests.post( "https://api.example.com/search", params=params, headers=headers, json=payload )
When you do this, requests builds the URL with the query string, attaches the headers, and sends the JSON-encoded body. The order of arguments does not matter, but keeping them grouped by purpose improves readability.
One subtlety: if a parameter name conflicts with a JSON body field, they are independent — the query parameter goes in the URL, the body field goes in the payload. This is exactly what most APIs expect.
Handling Response Content and Status Codes
After sending a request, you need to check whether it succeeded. The response object has a status_code attribute and a raise_for_status() method that raises an exception for 4xx and 5xx responses.
response = requests.post("https://api.example.com/tasks", json=payload) if response.status_code == 201: created_task = response.json() print(created_task["id"]) else: print(f"Request failed: {response.status_code}")
The response.json() method parses the response body as JSON. If the response is not valid JSON, it raises requests.exceptions.JSONDecodeError. Always confirm the content type or status code before parsing.
For streaming or large responses, use response.content to get the raw bytes, or response.text for the decoded string. The encoding attribute controls how text decodes the bytes.
Common Mistakes and How to Avoid Them
One frequent mistake is using data instead of json when sending a JSON payload. With data, the dictionary is form-encoded unless you set the Content-Type header and serialize the string yourself. This leads to APIs rejecting the request or misinterpreting the body.
Another issue is forgetting that query parameters are URL-encoded. If you build the URL manually and insert user input without encoding, you risk malformed URLs or injection vulnerabilities. Always use the params argument to let requests handle encoding.
Headers can also cause subtle bugs. For example, setting Content-Type manually while using json will override the automatic value. If you set it to something incorrect, the server may not parse the body. Similarly, some APIs expect a specific Accept header to return JSON instead of XML.
Finally, remember that the json parameter does not accept None or non-serializable objects. If you need to send an empty body, use data or omit the body entirely.
Performance and Connection Considerations
When making multiple requests to the same host, reuse a requests.Session object. A session maintains a connection pool and reuses TCP connections, which reduces latency and avoids the overhead of establishing a new connection for each request.
session = requests.Session() session.headers.update({"Authorization": "Bearer token"}) for page in range(1, 5): response = session.get("https://api.example.com/items", params={"page": page}) # process response
Sessions also persist cookies and allow you to set default headers. This is especially useful for API clients that authenticate once and then make many calls.
Set timeouts on every request to prevent hanging indefinitely. The timeout parameter accepts seconds or a tuple for connect and read timeouts separately.
response = requests.get("https://api.example.com/slow", timeout=(3, 10))
Without a timeout, a network failure can leave your script blocked indefinitely. Always specify a reasonable timeout for production code.
For large payloads, consider using json with a generator or streaming uploads via data with a file-like object. The requests library supports chunked transfer encoding, but this is rarely needed for typical JSON bodies.
When you combine query parameters, headers, and a JSON body, the request is built in a predictable order: the URL is constructed first, then headers are attached, and finally the body is encoded. Understanding this order helps you debug issues when a server returns unexpected errors.