NetAuto Academy

APIs and REST for Network Engineers

Handling Errors, Timeouts, and Status Codes

Intermediate14 min read

What you'll learn

  • How to check a status code and react correctly to 4xx/5xx
  • How to catch a real request timeout without the script hanging or crashing
  • How to catch a connection error when a target isn't reachable at all
  • That HTTP success and business/data success are two separate questions
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

Status codes, checked deliberately

check_status.py
response = requests.get("http://127.0.0.1:8765/devices/SW99/interfaces")
print(response.status_code)
try:
  response.raise_for_status()
  print("no error raised (unexpected)")
except requests.exceptions.HTTPError as e:
  print(type(e).__name__, "-", e)

raise_for_status() turns a 4xx or 5xx response into a real Python exception — an explicit opt-in check, not automatic. Calling it is what actually enforces 'don't trust a bad response.'

output
404
HTTPError - 404 Client Error: Not Found for url: http://127.0.0.1:8765/devices/SW99/interfaces

Real, verified output — requesting an unknown device against this module's mock API. Without calling raise_for_status(), this 404 would just sit in response.status_code, easy to forget to check.

Quick checkConcept check
What does response.raise_for_status() do?

Two different failures: timeout and connection error

handle_timeout.py
try:
  requests.get("http://127.0.0.1:8765/devices/SW4/vlans", timeout=1)
except requests.exceptions.Timeout as e:
  print("Timeout:", e)

timeout=1 tells requests to give up after 1 second of waiting. This module's mock API deliberately makes SW4 respond slowly — a real, reproducible way to trigger this exception, not a hypothetical.

output
Timeout: HTTPConnectionPool(host='127.0.0.1', port=8765): Read timed out. (read timeout=1)

Real, verified output — raised after almost exactly 1 second, matching the timeout= value. requests.exceptions.Timeout covers both a slow connection and a slow response.

Always set timeout= explicitly

Without timeout=, requests will wait indefinitely for a response that may never come — a script that looks like it's just running, when it's actually stuck forever. This is the HTTP equivalent of Netmiko's own conn_timeout — a required habit, not an optional nicety.

handle_connection_error.py
try:
  requests.get("http://127.0.0.1:9999/devices/SW1/interfaces", timeout=1)
except requests.exceptions.ConnectionError as e:
  print(type(e).__name__)

A different exception for a different problem: nothing is listening at all on port 9999, so the connection itself never gets established — distinct from a timeout, where a connection succeeds but the response never arrives in time.

output
ConnectTimeout

Real, verified output. ConnectTimeout is one of requests' more specific exceptions, and it's also a ConnectionError — catching requests.exceptions.ConnectionError (as shown above) catches it, along with every other connection-level failure.

Quick checkScenario
A request to an unreachable IP hangs, then eventually raises requests.exceptions.ConnectionError. A request to a real, reachable server that's just slow to respond raises requests.exceptions.Timeout instead. What's the actual difference between these two failures?
Quick checkIdentify the correct code
Which code correctly makes a request that won't hang forever, and won't crash the script if the target times out?

HTTP success is not the same claim as data success

two_separate_questions.py
response = requests.get("http://127.0.0.1:8765/devices/SW2/vlans", timeout=2)
print("HTTP succeeded:", response.status_code == 200)

vlans = response.json()["vlans"]
required = [{"id": 10, "name": "USERS"}]
missing = [v for v in required if v not in vlans]
print("but required VLANs missing:", missing)

SW2's request returns a perfectly healthy 200 — the HTTP layer has nothing to complain about. Whether the DATA inside that response is what's actually required is an entirely separate check, run afterward.

output
HTTP succeeded: True
but required VLANs missing: [{'id': 10, 'name': 'USERS'}]

Real, verified output. A 200 status code only proves the request was received and answered — it says nothing about whether the answer is the one you needed.

Exactly the theme from every capstone before this one

'Ran without error' was never the same claim as 'succeeded' — Reliable Automation's post-check, Configuration & Template Automation's rendered-config validation, and Ansible's check-mode all drew this same line. Here it's a status code that can mislead if trusted alone: 200 only means the HTTP layer is happy. This module's capstone (Lesson 7) checks both questions, in that order, on every device.

Quick checkMyth check
If an API request returns status code 200, does that guarantee the data inside the response is what was actually required?

Key takeaways

  • raise_for_status() turns a 4xx/5xx response into a real exception — an explicit choice, not automatic behavior.
  • requests.exceptions.Timeout (a slow response) and requests.exceptions.ConnectionError (no connection at all) are two distinct, real failure modes — both verified directly against this module's own mock API.
  • timeout= is required, not optional — without it, a request can hang indefinitely, the HTTP equivalent of Netmiko's conn_timeout.
  • A 200 status code proves the HTTP layer succeeded — it says nothing about whether the response's actual data is correct. Checking both, in order, is exactly what this module's capstone does for every device.