Chapter 5: Python Network Automation with RESTCONF
Learning Objectives
Build Python scripts using the requests library to interact with RESTCONF APIs on Cisco IOS XE devices
Construct RESTCONF URIs from YANG model paths for interface, routing, and ACL management
Implement full CRUD operations via RESTCONF with proper headers, authentication, and error handling
Monitor device state and retrieve operational data via RESTCONF GET operations targeting *-oper YANG modules
Pre-Chapter Quiz — Test Your Prior Knowledge
1. Which two HTTP headers are mandatory for every RESTCONF request to a Cisco IOS XE device?
2. What is the correct RESTCONF URI to target a specific interface named GigabitEthernet1 using the IETF interfaces model?
3. You want to update only the description field of an existing interface without touching its IP address configuration. Which HTTP method should you use?
4. What HTTP status code does a successful RESTCONF PUT operation return?
5. Which YANG module family should you target to read real-time interface traffic counters on IOS XE?
Section 1: RESTCONF with Python Requests
RESTCONF is an HTTPS-based protocol (RFC 8040) that exposes a Cisco IOS XE device's YANG data model as a set of addressable URLs. Python's requests library acts as the HTTP client, allowing you to read, create, update, and delete device configuration and state data using standard HTTP operations.
1.1 Enabling RESTCONF on IOS XE
Three IOS XE configuration commands are required before any Python script can reach the RESTCONF API:
ip http secure-server
ip http authentication local
restconf
username admin privilege 15 secret Cisco1234!
Verify the service is running with show platform software yang-management process and show restconf capabilities. If the yang-management process is active and capabilities are returned, the device is ready.
1.2 Python Environment and Headers
Always isolate RESTCONF projects in a virtual environment (pip install requests). Every RESTCONF script requires two elements:
Headers — Accept and Content-Type must both be set to application/yang-data+json (RFC 8040 media type)
Authentication — Use requests.auth.HTTPBasicAuth; never hard-code credentials — load from environment variables
The IETF model uses interface=GigabitEthernet1 (single string key), while the Cisco native model splits on type: GigabitEthernet=1. Interface names containing forward slashes (e.g., GigabitEthernet1/0/1) must be percent-encoded using urllib.parse.quote(name, safe='') to prevent the slash from being interpreted as a path separator.
flowchart TD
A[IOS XE Device] -->|HTTPS / TLS| B[RESTCONF API\n/restconf/data]
B --> C{YANG Data Store}
C --> D[Configuration Data\nread-write]
C --> E[Operational Data\nread-only / config false]
F[Python Script\nrequests library] -->|GET / PUT / PATCH\nPOST / DELETE| B
F -->|HTTPBasicAuth\napplication/yang-data+json| B
D -->|ietf-interfaces\nCisco-IOS-XE-native| F
E -->|Cisco-IOS-XE-oper modules| F
1.4 Choosing the Right YANG Model
Model Family
Prefix
Best Use Case
Limitation
Cisco Native
Cisco-IOS-XE-native:
Full IOS feature set, vendor-specific config
Version-dependent, not portable
IETF Standards
ietf-interfaces:, ietf-ip:
Interfaces, IP addressing, standard features
Limited to standardized features
OpenConfig
openconfig-interfaces:
Multi-vendor scripts
Less granular than native
On IOS XE 17.7.1+, run show running-config | format restconf-json on the device CLI to instantly discover the correct YANG URI for any existing configuration element.
RESTCONF URI Construction — Step by Step
Base URLhttps://10.10.20.48
+ Root pathhttps://10.10.20.48/restconf/data
+ YANG modulehttps://10.10.20.48/restconf/data/ietf-interfaces:interfaces
+ List + keyhttps://10.10.20.48/restconf/data/ietf-interfaces:interfaces/interface=GigabitEthernet1
RESTCONF requires ip http secure-server, ip http authentication local, and restconf to be configured on IOS XE before any API calls can succeed.
Both Accept and Content-Type headers must be set to application/yang-data+json — omitting either causes a 400 error or incorrectly formatted response.
The RESTCONF URI formula is <module>:<container>/<list>=<key>/<leaf> — a direct mapping of the YANG hierarchy to a URL path.
Interface names containing / (e.g., GigabitEthernet1/0/1) must be percent-encoded with urllib.parse.quote(name, safe='') to avoid 404 errors.
Use show running-config | format restconf-json on IOS XE 17.7.1+ to discover correct URIs from existing device configuration.
Section 2: RESTCONF CRUD Operations
The five HTTP methods map directly onto database CRUD operations. Understanding the exact semantics of each — especially the difference between PUT and PATCH — is essential for both the exam and production automation.
HTTP Method
CRUD
Behavior
Success Code
GET
Read
Retrieve resource; no body sent
200 OK
POST
Create
Add new resource under target container
201 Created
PUT
Create or Replace
Fully replace target resource (idempotent)
204 No Content
PATCH
Update (merge)
Merge payload into existing resource
204 No Content
DELETE
Delete
Remove target resource
204 No Content
2.1 GET — Reading Configuration and State
GET retrieves the current value of any resource. Use the fields query parameter to fetch only specific leaves and dramatically reduce response size on devices with many interfaces:
# All interfaces
url = f"{DATA_URL}/ietf-interfaces:interfaces"
# Filtered response — only name and IPv4 address
url = f"{DATA_URL}/ietf-interfaces:interfaces?fields=interface/name;interface/ietf-ip:ipv4/address"
response = requests.get(url, headers=RESTCONF_HEADERS, auth=AUTH, verify=False)
response.raise_for_status()
data = response.json()
2.2 PUT vs. PATCH — The Critical Distinction
PUT fully replaces the target resource. If you PUT a payload that omits a field, that field is deleted from the device configuration. Use PUT for idempotent provisioning where you own the complete desired state.
PATCH merges the payload into the existing resource. Fields not present in the PATCH payload are left unchanged. Use PATCH for targeted single-field updates. Think of PUT as repainting an entire wall, PATCH as touching up a single scuff mark.
# PATCH — only updates description, leaves IP address untouched
payload = {
"ietf-interfaces:interface": {
"name": "GigabitEthernet1",
"description": "Primary WAN — Updated 2024"
}
}
response = requests.patch(url, headers=RESTCONF_HEADERS, auth=AUTH,
json=payload, verify=False)
# Returns 204 on success
2.3 POST and DELETE
POST creates a new child resource and returns 201 Created. If the resource already exists it returns 409 Conflict — always handle this in automation scripts or switch to PUT for create-or-replace semantics.
DELETE removes the target resource and returns 204 No Content. Target the leaf path precisely to avoid accidentally deleting a parent container and all its children.
2.4 Error Handling
Never assume a RESTCONF call succeeded without checking the status code. Build a reusable wrapper:
flowchart TD
A[Need to interact with\na RESTCONF resource] --> B{What is your goal?}
B -->|Read current state| C[GET\nReturns 200 + JSON body]
B -->|Write / change config| D{Does the resource\nalready exist?}
D -->|Unsure — safe to overwrite all| E[PUT\nCreate or full replace\nReturns 204]
D -->|Yes — change one field only| F[PATCH\nPartial merge\nReturns 204]
D -->|No — device assigns key| G[POST\nCreate new child\nReturns 201\nor 409 if exists]
B -->|Remove config| H[DELETE\nReturns 204]
C --> I{Status 200?}
I -->|Yes| J[Parse JSON response body]
I -->|No| K[Handle error:\n401 auth / 404 path / 400 payload]
E --> L{Status 204?}
F --> L
G --> M{Status 201?}
H --> L
L -->|Yes| N[Success]
M -->|409 Conflict| O[Switch to PUT for idempotency]
RESTCONF HTTP Methods at a Glance
GETRead any resource — returns full JSON body200 OK
PUTCreate or fully replace — DANGER: omitted fields are deleted204 No Content
PATCHPartial merge — only named fields change204 No Content
POSTCreate new child under container201 Created / 409 Conflict
DELETERemove target resource — no body returned204 No Content
Key Points — Section 2
PUT is idempotent and destructive: it fully replaces the target resource — fields omitted from the payload are deleted from the device configuration.
PATCH is safe for partial updates: only the fields present in the payload are modified; all other configuration on the resource is preserved.
POST returns 201 Created on success and 409 Conflict if the resource already exists — always handle 409 in automation scripts.
Successful write operations (PUT, PATCH, DELETE) return 204 No Content with an empty body — do not try to parse a response body.
Always check status codes explicitly or call response.raise_for_status() — silent RESTCONF failures can leave devices in inconsistent states.
This section applies CRUD primitives to four real-world IOS XE automation tasks aligned with the ENAUTO exam: interface fleet management, static route configuration, ACL provisioning, and VLAN management.
3.1 Interface Fleet Automation
For Day 2 automation across multiple devices, build thin wrapper functions and iterate over a device list. Use PUT for idempotent provisioning — the same script can be re-run safely:
Static routes live under Cisco-IOS-XE-native:native/ip/route. Use PATCH to add a route without disturbing other existing routes — PATCHing the native container merges the new route into the existing configuration.
3.3 ACL Management
Named extended ACLs are managed via Cisco-IOS-XE-native:native/ip/access-list/extended=<name>. Use PUT to create or replace the entire ACL atomically. Use PATCH to add a single new ACE entry to an existing ACL without replacing the whole list.
3.4 Idempotent VLAN Provisioning
VLAN management uses the Cisco-IOS-XE-vlan model. PUT per VLAN ID is idempotent and safe to run repeatedly — it creates the VLAN if absent, or silently overwrites the name if the VLAN ID already exists, with no 409 conflict:
def provision_vlans(device_ip, vlans):
base = f"https://{device_ip}/restconf/data"
for vlan in vlans:
url = f"{base}/Cisco-IOS-XE-native:native/vlan/vlan-list={vlan['id']}"
payload = {
"Cisco-IOS-XE-vlan:vlan-list": {
"id": vlan['id'], "name": vlan['name']
}
}
response = requests.put(url, headers=HEADERS, auth=AUTH,
json=payload, verify=False)
result = "CREATED/UPDATED" if response.status_code == 204 else f"ERROR {response.status_code}"
print(f" VLAN {vlan['id']} ({vlan['name']}): {result}")
graph LR
A[Automation Script] --> B{Task Type}
B -->|Deploy interfaces\nto fleet| C[PUT per interface\nper device\nURL-encode name]
B -->|Add static route| D[PATCH native container\nmerge-safe]
B -->|Create / replace ACL| E[PUT to\naccess-list=NAME\nfull replacement]
B -->|Add single ACE| F[PATCH to\nspecific sequence entry]
B -->|Provision VLANs| G[PUT per vlan-list=ID\nidempotent]
C --> H[204 = success\nre-runnable]
D --> H
E --> H
F --> H
G --> H
Key Points — Section 3
Build thin wrapper functions around RESTCONF primitives, each handling one resource type, and iterate over device lists for fleet-scale automation.
PUT per resource ID is the standard pattern for idempotent provisioning — safe to re-run without 409 conflicts, unlike POST.
Use PATCH when adding a single ACE to an existing ACL — a PUT would erase all other ACEs not included in the payload.
Static routes use the Cisco-IOS-XE-native:native/ip/route path; VLANs use Cisco-IOS-XE-native:native/vlan/vlan-list=<id>.
Always URL-encode interface names before building URIs — this is required for any interface with a / character in its name.
Section 4: RESTCONF Monitoring and Operational Data
4.1 Configuration Data vs. Operational Data
Configuration data represents intended state — what you have told the device to do. It is read-write, stored in the running configuration datastore, and accessible via all HTTP methods.
Operational data represents actual state — what the device is currently doing. It is read-only and generated in real time. In YANG schemas, operational data nodes are marked config false. Attempting PUT/PATCH/POST/DELETE on these nodes returns an error.
graph TD
A[IOS XE YANG Data] --> B[Configuration Data\nread-write / rw]
A --> C[Operational Data\nread-only / ro / config false]
B --> D[ietf-interfaces:interfaces]
B --> E[Cisco-IOS-XE-native:native]
D --> D1[name, description\nenabled, ietf-ip:ipv4]
E --> E1[ip/route, vlan\naccess-list, hostname]
C --> G[Cisco-IOS-XE-interfaces-oper:interfaces]
C --> H[Cisco-IOS-XE-bgp-oper:bgp-state-data]
C --> I[Cisco-IOS-XE-platform-oper:components]
C --> J[Cisco-IOS-XE-fib-oper:fib-oper-data]
G --> G1[in-octets / out-octets\nin-errors / oper-status]
H --> H1[session-state\nprefix counts / uptime]
I --> I1[CPU load\nmemory / sensors]
J --> J1[FIB forwarding table]
style B fill:#d4edda,stroke:#28a745
style C fill:#cce5ff,stroke:#004085
4.2 Key Operational YANG Modules
YANG Module
Data Exposed
Cisco-IOS-XE-interfaces-oper
Interface statistics, link state, error counters, speed
RESTCONF is a synchronous request-response protocol — it does not push data to you. For monitoring, you must poll at an interval. This has architectural implications:
Scenario
RESTCONF Polling
NETCONF/gRPC Telemetry (MDT)
Frequency needed
< 1 per minute
> 1 per minute or sub-second
Number of devices
< 20 devices
20+ devices at scale
Event-driven alerting
Poll-based workaround
Native push subscriptions
Implementation complexity
Low — plain Python
Higher — requires collector
4.5 Capability Discovery
Before scripting against a specific YANG module, confirm it is loaded on the target device. Different IOS XE versions support different modules — targeting a missing module returns 404:
def list_yang_modules(device_ip, filter_prefix=None):
url = f"https://{device_ip}/restconf/data/ietf-yang-library:modules-state"
response = requests.get(url, headers=HEADERS, auth=AUTH, verify=False)
modules = response.json().get('ietf-yang-library:modules-state', {}).get('module', [])
if filter_prefix:
modules = [m for m in modules if m.get('name', '').startswith(filter_prefix)]
return [(m['name'], m.get('revision', 'unknown')) for m in modules]
Key Points — Section 4
Operational data lives in -oper YANG modules (e.g., Cisco-IOS-XE-interfaces-oper), not in the native configuration model — attempting to write to them returns an error.
Use the fields query parameter in GET requests to filter operational data responses and reduce payload size on busy devices.
RESTCONF is a polling protocol; for sub-minute or event-driven monitoring at scale, complement it with NETCONF Model-Driven Telemetry over gRPC.
Always call ietf-yang-library:modules-state to verify a YANG module is available before scripting against it — IOS XE version determines which modules are present.
BGP neighbor state is exposed at Cisco-IOS-XE-bgp-oper:bgp-state-data/neighbors; the established state value is fsm-established.
Post-Chapter Quiz — Verify Your Understanding
1. Which two HTTP headers are mandatory for every RESTCONF request to a Cisco IOS XE device?
2. You run requests.put() on an interface resource with a payload that includes only the description field. What happens to the interface's IP address configuration?
3. An interface named GigabitEthernet1/0/1 needs to be targeted in a RESTCONF URI. What Python call produces the correct encoded path segment?
4. A script calls POST to create VLAN 100 on a device where VLAN 100 already exists. What status code does the device return?
5. You want to monitor BGP neighbor session state across 50 devices every 10 seconds. Which monitoring approach is most appropriate?
6. Which YANG module and URI prefix do you use to read real-time interface error counters on IOS XE?
7. What is the correct IOS XE CLI command to discover the RESTCONF URI for an existing configuration element?
8. A RESTCONF DELETE request targeting /ietf-interfaces:interfaces/interface=GigabitEthernet2/ietf-ip:ipv4/address returns 204. What does this mean?
9. Which Python call correctly authenticates a RESTCONF request using credentials stored in environment variables?
10. You need to add a new ACE (sequence 30) to an existing 10-entry ACL without disturbing the other entries. Which method and target are correct?