Mental model
The Python requests library is the standard way to call REST APIs from Python. It wraps the HTTP machinery (sockets, TLS, redirects, encoding) so your code reads like English:
import requests
r = requests.get("https://api.example.com/devices")
print(r.status_code)
print(r.json())
Cisco objective 2.9 says “Construct a Python script that calls a REST API using the requests library”. That is this topic.
The Response object
Every request returns a Response. Four attributes do 90% of the work.
| Attribute / method | What it is | Example value |
|---|---|---|
r.status_code | HTTP status as int | 200 |
r.reason | HTTP status phrase as str | 'OK' |
r.text | Response body as str | '{"ok":true}' |
r.json() | Response body parsed as dict/list (if JSON) | {"ok": True} |
r.headers | Response headers as a case-insensitive dict | {"Content-Type": "application/json", ...} |
r.ok | True if 200 <= status_code < 400 | True |
r.url | Final URL after redirects | 'https://api.example.com/devices' |
Call r.raise_for_status() right after the request to make any 4xx or 5xx throw. Otherwise your script keeps going on a 500 and prints junk.
GET
import requests
r = requests.get("https://api.example.com/devices")
r.raise_for_status()
data = r.json()
for d in data["devices"]:
print(d["name"], d["ip"])
Query parameters
Two equivalent ways:
# In the URL
r = requests.get("https://api.example.com/devices?limit=10&offset=20")
# As a dict (requests URL-encodes them for you)
r = requests.get("https://api.example.com/devices", params={"limit": 10, "offset": 20})
Prefer the dict version. It handles special characters correctly.
Headers
r = requests.get(
"https://api.example.com/devices",
headers={
"Authorization": "Bearer eyJhbGci...",
"Accept": "application/json",
},
)
JSON body (POST / PUT / PATCH)
r = requests.post(
"https://api.example.com/devices",
headers={"Authorization": "Bearer eyJhbGci..."},
json={"name": "sw3", "ip": "10.0.0.3"},
)
r.raise_for_status()
print(r.json()["id"])
json= is the magic kwarg: requests sets Content-Type: application/json for you and encodes the dict as JSON. Compared to data=json.dumps(...) + manual header setting, this is one less thing to get wrong.
Basic auth
r = requests.get(
"https://api.example.com/secure",
auth=("admin", "cisco123"),
)
requests base64-encodes the credentials and sets the Authorization: Basic ... header for you.
Full example: list and create
import requests
BASE = "https://api.example.com/api/v1"
HEADERS = {"X-API-Key": "your-key-here"}
# List
r = requests.get(f"{BASE}/devices", headers=HEADERS)
r.raise_for_status()
print(f"{len(r.json())} devices known")
# Create
new_device = {"name": "sw3", "ip": "10.0.0.3"}
r = requests.post(f"{BASE}/devices", headers=HEADERS, json=new_device)
r.raise_for_status()
print(f"created device id = {r.json()['id']}")
20 lines. That is a working automation script, ready to loop over an input file.
requests.get, .post, .json(), raise_for_status
10 steps in the Python REPL: import requests, GET a devices endpoint, read status and body, POST with json=, add headers={}, use auth=().
Open the lab →FAQ
Why not use the built-in urllib? You can, and the stdlib urllib.request works. requests is three times less code for every realistic API call, handles auth and JSON for free, and is the de-facto standard everyone else writes about.
httpx or aiohttp? httpx is a modern alternative with the same API plus async support. aiohttp is async-first. requests is still the one 95% of tutorials and Cisco DevNet samples use; learn it first.
My script hangs forever. Pass timeout=5 (seconds). Without it, requests waits indefinitely. In production, always set a timeout.
How do I upload a file? requests.post(url, files={"file": open("x.txt", "rb")}). Not on 200-901 but useful to know.
