Free tech learning for everyone, from basics to pro

APIsProgramming and Application SecurityIntermediate

Calling REST APIs from Python with requests

In short: The requests library makes API calls short and readable. Use a Session for shared headers and connection reuse, always set a timeout, check the status with raise_for_status(), and read secrets from the environment. Add retries for 429 and 5xx errors and loop through pages to get complete results.

Getting started

requests is the most widely used Python HTTP library. Install it in a virtual environment so your project has its own dependencies:

python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install requests

Other good options exist, such as httpx, which adds async support, but the ideas below apply to all of them.

A robust API call in PythonSessionbase URL,auth, headersRequesttimeout setCheck statusraise_for_status()Parse JSONresponse.json()Handle errorsretry or log
Figure 1. A reliable script sets a timeout, checks the status, parses the body and handles failures, every time.

A first GET and POST

import os
import requests

BASE = "https://nms.example.com/api/v1"
TOKEN = os.environ["NMS_TOKEN"]          # set outside the script

s = requests.Session()
s.headers.update({
    "Authorization": f"Bearer {TOKEN}",
    "Accept": "application/json",
    "User-Agent": "fmtechub-inventory/1.0",
})

r = s.get(f"{BASE}/devices", params={"site": "london"}, timeout=10)
r.raise_for_status()                     # raises HTTPError on 4xx/5xx
for dev in r.json():
    print(dev["name"], dev.get("ip", "-"))

new_vlan = {"vlan_id": 30, "name": "VOICE"}
r = s.post(f"{BASE}/vlans", json=new_vlan, timeout=10)
if r.status_code == 409:
    print("VLAN 30 already exists")
else:
    r.raise_for_status()
    print("Created", r.json().get("id"))

Points worth noticing: params= builds and encodes the query string for you; json= serialises the dict and sets Content-Type automatically; a Session reuses the TCP and TLS connection, which is much faster when you make hundreds of calls.

Tip: requests has no default timeout. Without timeout=, a script can hang forever on a silent device. Always set one.

Errors, retries and rate limits

Networks fail and APIs throttle. Separate the errors you can retry from the ones you must fix:

  • Retry: connection errors, timeouts, 429, 502, 503, 504. Wait longer each time (exponential back-off) and honour Retry-After.
  • Do not retry: 400, 401, 403, 404, 409. Log the response body, which often explains the problem.
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry = Retry(
    total=5,
    backoff_factor=1,                    # 1s, 2s, 4s ...
    status_forcelist=[429, 502, 503, 504],
    allowed_methods=["GET", "PUT", "DELETE"],   # idempotent only
    respect_retry_after_header=True,
)
s.mount("https://", HTTPAdapter(max_retries=retry))

try:
    r = s.get(f"{BASE}/devices", timeout=10)
    r.raise_for_status()
except requests.exceptions.HTTPError as e:
    print("API error:", e.response.status_code, e.response.text[:200])
except requests.exceptions.RequestException as e:
    print("Network problem:", e)

Pagination

Pagination loopRequest pagelimit + offsetCollect itemsextend listNext link?check responseFollow itrepeat request
Figure 2. Many APIs return results in pages; the script keeps requesting until there is no next page.

APIs rarely return thousands of items in one go. Common patterns are offset and limit, page numbers, a cursor token, or a next URL in the body or the Link header. Read the documentation, then loop until no next page remains.

def get_all(session, url, **params):
    items = []
    while url:
        r = session.get(url, params=params, timeout=10)
        r.raise_for_status()
        data = r.json()
        items.extend(data["results"])
        url = data.get("next")           # None on the last page
        params = {}                      # next URL already has them
    return items

devices = get_all(s, f"{BASE}/devices", limit=100)
print(len(devices), "devices")

Good habits

  • Never pass verify=False. For a private CA, use verify="/path/to/ca.pem" or set REQUESTS_CA_BUNDLE.
  • Read tokens from environment variables, a .env file excluded from Git, or a secrets manager.
  • Do not print tokens or full headers in logs.
  • Put API logic in small functions such as get_devices(), so the rest of the script does not care about HTTP details.
  • Test against a lab or sandbox, and add a dry-run mode before any POST, PUT or DELETE.

Practice

What does raise_for_status() do?
It raises an HTTPError if the response status is 4xx or 5xx, so failures are not silently ignored.
Why use json=payload instead of data=json.dumps(payload)?
json= serialises the data and sets the Content-Type: application/json header automatically, avoiding a common 400/415 error.
Your script returns exactly 100 devices but the controller has 340. What is the likely cause?
The API paginates results; the script only read the first page and must follow the next link or offset.

Key takeaways

  • Use a requests.Session for shared headers and faster connection reuse.
  • Always set timeout= and call raise_for_status().
  • Retry 429 and 5xx with back-off; fix 4xx errors instead of retrying.
  • Loop through pages until there is no next link.
  • Read secrets from the environment and never use verify=False.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.