APIs and REST for Network Engineers
Capstone: An API-Driven Network Health Check
What you'll learn
- How inventory, requests, JSON parsing, and validation combine into one health check
- How HTTP status codes map onto the same classification lineage from earlier modules
- How RESTCONF fits as a real network-specific example, without becoming a second curriculum
- That REST is one more tool in this curriculum's lineage, not a replacement for the others
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
The whole lineage, one more transport
- Phase 2.6 — Python automationhand-written inventory, connection, validation, report
- Phase 2.7 — Netmikoa real SSH connection
- Phase 2.8 — Reliable Automationretry, dry-run, pre/post-check
- Phase 2.9 — Jinja2 templatesstructured data + template replace hardcoded config
- Phase 3.0 — Ansiblethe same ideas, orchestrated declaratively
- Phase 3.1 — REST API automationthe same shape of task, over HTTP instead of SSH
Not a replacement — a different transport for a different target
This capstone solves the same kind of problem as every capstone before it: check each device's state, validate it, report the result. What's different is the transport — HTTP instead of SSH — because this capstone's fleet, like a real controller-managed environment, is addressed through an API. Nothing here suggests REST should replace Netmiko or Ansible for a device that's perfectly reachable over SSH.
The complete script
import csv
import json
import requests
BASE_URL = "http://127.0.0.1:8765"
REQUEST_TIMEOUT = 2 # seconds
REQUIRED_VLANS = {
"SW1": [{"id": 10, "name": "USERS"}, {"id": 20, "name": "VOICE"}],
"SW2": [{"id": 10, "name": "USERS"}],
"SW3": [{"id": 10, "name": "USERS"}, {"id": 30, "name": "MGMT"}],
"SW4": [{"id": 10, "name": "USERS"}],
"SW5": [{"id": 10, "name": "USERS"}],
}
def is_valid_device(device):
"""A device record needs at least a hostname to be usable."""
return bool(device.get("hostname"))
def check_device(hostname):
"""GET this device's VLANs and classify the result -- HTTP outcome and
business/data outcome are two separate questions, checked separately."""
url = f"{BASE_URL}/devices/{hostname}/vlans"
try:
response = requests.get(url, timeout=REQUEST_TIMEOUT)
except requests.exceptions.Timeout:
return {"hostname": hostname, "status": "unreachable", "detail": "request timed out"}
except requests.exceptions.ConnectionError:
return {"hostname": hostname, "status": "unreachable", "detail": "connection failed"}
if response.status_code == 401:
return {"hostname": hostname, "status": "auth-failed", "detail": "authentication required"}
if response.status_code == 404:
return {"hostname": hostname, "status": "not-found", "detail": "device not found"}
if response.status_code != 200:
return {"hostname": hostname, "status": "error", "detail": f"unexpected status {response.status_code}"}
# HTTP succeeded -- now check whether the DATA is actually correct.
actual_vlans = response.json().get("vlans", [])
required = REQUIRED_VLANS.get(hostname, [])
missing = [v for v in required if v not in actual_vlans]
if missing:
return {
"hostname": hostname,
"status": "non-compliant",
"detail": f"missing VLANs: {[v['id'] for v in missing]}",
}
return {"hostname": hostname, "status": "compliant", "detail": "all required VLANs present"}
with open("devices.csv") as f:
devices = list(csv.DictReader(f))
valid_devices = []
for device in devices:
if is_valid_device(device):
valid_devices.append(device)
else:
print(f"Skipping invalid inventory record: {device}")
results = [check_device(device["hostname"]) for device in valid_devices]
with open("api_health_check_report.json", "w") as f:
json.dump(results, f, indent=4)
by_status = {}
for r in results:
by_status[r["status"]] = by_status.get(r["status"], 0) + 1
print("\n--- Summary ---")
print(f"Checked: {len(results)} devices")
for status, count in by_status.items():
print(f"{status}: {count}")
print("Report written to api_health_check_report.json")Genuinely complete and runnable against this module's mock API (Option B) — the exact same is_valid_device()-then-loop-then-report shape as every capstone since Phase 2.6, with check_device() as the one new piece: a GET request, checked in two stages (HTTP status, then data).
The sample inventory
hostname,ip,platform
SW1,10.10.10.1,cisco_ios
SW2,10.10.10.2,cisco_ios
SW3,10.10.10.3,cisco_ios
SW4,10.10.10.4,cisco_ios
SW5,10.10.10.5,cisco_iosUnchanged from every prior capstone — the same five devices, exercising a comparable spread of outcomes, now reached over HTTP.
Running it: expected terminal output
--- Summary ---
Checked: 5 devices
compliant: 2
non-compliant: 1
unreachable: 1
auth-failed: 1
Report written to api_health_check_report.jsonReal, verified output. SW1 and SW3 are compliant. SW2 returns a healthy 200 but is missing a required VLAN. SW4's request times out. SW5 returns 401.
[
{
"hostname": "SW1",
"status": "compliant",
"detail": "all required VLANs present"
},
{
"hostname": "SW2",
"status": "non-compliant",
"detail": "missing VLANs: [10]"
},
{
"hostname": "SW3",
"status": "compliant",
"detail": "all required VLANs present"
},
{
"hostname": "SW4",
"status": "unreachable",
"detail": "request timed out"
},
{
"hostname": "SW5",
"status": "auth-failed",
"detail": "authentication required"
}
]Real, verified output — diffed exactly against a real run of the script above. Every device has one entry with enough detail to say what actually happened, the same reporting discipline as every JSON report since Phase 2.6.
Two ways to run it
- A real device with RESTCONF enabled, or a controller's APIsubject to availability
- Real credentials, real HTTPS
- A real api_health_check_report.json
- Nothing beyond Python and requests needed
- This module's stdlib-only mock API
- The same shape of api_health_check_report.json
Option A — if RESTCONF is enabled on a real or virtual IOS-XE device, or a controller's API is available (own hardware, a personal lab, or a currently-available sandbox — subject to that resource's own availability, which this curriculum doesn't control), BASE_URL and check_device()'s URL pattern point at the real API instead. Option B — this module's mock API is a complete, legitimate way to finish this capstone, exercising every mechanic (GET, JSON parsing, status codes, timeouts, validation) for real.
RESTCONF, concretely — per the relevant RESTCONF documentation, not independently executed in this authoring environment
RESTCONF (RFC 8040) uses a standardized URL and JSON shape, built around YANG data models. A real request against an IOS-XE device's interfaces looks like this:
import requests
response = requests.get(
"https://192.0.2.1/restconf/data/ietf-interfaces:interfaces",
auth=("admin", "REPLACE_ME"), # never hardcoded for real -- getpass()/env var
headers={"Accept": "application/yang-data+json"},
verify=False, # many labs use self-signed certs -- never do this in production
timeout=5,
)
print(response.json())A real request was not made against a real device to produce this example -- no RESTCONF-capable device was available in this authoring environment. The URL shape (/restconf/data/<model>:<container>) and the Accept header are exactly as RFC 8040 and Cisco's own RESTCONF documentation specify.
{
"ietf-interfaces:interfaces": {
"interface": [
{
"name": "GigabitEthernet1",
"type": "iana-if-type:ethernetCsmacd",
"enabled": true
}
]
}
}The response.json() parsing skill from Lesson 3 applies identically here -- a real RESTCONF response is still just JSON, structured around a standard YANG model instead of this module's own simplified shape.
Optional, and explicitly not required to complete this module
RESTCONF requires a real or virtual device with it enabled — never claimed as tested here, since none was available. Everything this module actually teaches (GET, JSON parsing, parameters, error/timeout handling, authentication, mutation safety) is fully exercised by the mock-API capstone above, with or without ever touching RESTCONF for real.
Three ways to ask the same question
- SSH to the deviceor via Ansible's agentless model
- send_command() / ios_commandread current state
- Parse CLI text or trust the module
- HTTP request to the APIno SSH session
- GET a resource
- Parse JSON — already structured
What this module actually built
Key takeaways
- This capstone follows the exact inventory → check → classify → report lineage as every capstone since Phase 2.6 — the transport changed to HTTP, the shape of the problem didn't.
- check_device() checks HTTP status and response data as two separate questions, in that order — SW2's non-compliant result (a healthy 200 with missing data) is the concrete proof this distinction matters.
- RESTCONF (RFC 8040) is a real, standardized way this same model applies directly to network devices — shown concretely here, honestly labeled as not independently executed in this authoring environment, and never required to complete this module.
- REST joins Netmiko, Ansible, and everything else in this curriculum's toolkit — it doesn't retire any of them. The right tool still follows what the target actually exposes.