CCNP / Learning Path
E10 ENCOR Core · Hard · 70-90 min · 1x CSR1000v (IOS-XE 17.3), 1x NAT

RESTCONF: Programmable Config

Difficulty 70-90 min 1x CSR1000v (IOS-XE 17.3), 1x NAT
Lab brief & addressing
E10 topology card

Topology Open full size ↗

The Mission

Every lab so far, you configured a device by typing at it. Automation flips that: the device becomes an API you call from a program. By the end, you will read and change a live router's configuration over HTTPS — no CLI — and prove a single PUT became real IOS config, then delete it with a DELETE. You are learning the machine-to-machine interface every controller, script, and CI pipeline uses under the hood.

You'll be able to

  • Turn on RESTCONF and read the device's config and live state as structured JSON (application/yang-data+json).
  • Map HTTP verbs to config actions — GET reads, PUT/POST/PATCH write, DELETE removes — and read the status code that proves each one.
  • Address any piece of config by a URL built from a YANG model, and read a RESTCONF error body when you get the URL wrong.

Before you start

  • One IOS-XE router (this lab is captured on a Cisco CSR1000v, IOS-XE 17.3.6 — RESTCONF needs IOS-XE 16.x+, it is not on classic IOSv 15.x).
  • A management host that can reach the router and run curl (Python requests works identically). In GNS3, connect the router's GigabitEthernet1 to a NAT cloud so your host can reach it.
  • Comfortable with E07 (you know why on-box automation matters). This is the off-box, model-driven counterpart.

Addressing plan

Device / InterfaceAddressRole
CSR1 Gi1 (management)192.168.122.10 / 24the device you drive by API
Management host (curl/Python)192.168.122.1the API client
RESTCONF endpointhttps://192.168.122.10/restconfTCP 443 (HTTPS)
Credentialscisco / cisco123 (priv 15)HTTP Basic auth

All JSON and status codes below are captured verbatim from a live CSR1000v 17.3.6 — the URL host is shown as your management IP.

The one idea behind this lab

Inside every modern IOS-XE box the configuration is described by YANG data models — a strict tree of what can be configured and read. RESTCONF publishes that tree over HTTPS: each branch has a URL, and you act on it with ordinary HTTP verbs. GET a URL and you get that slice of config or live state back as JSON; PUT a JSON body to it and you set that config; DELETE it and it's gone. It is the exact same configuration you would type at the CLI — but now it is addressable and machine-readable, which is the whole reason a script (or Ansible, or a controller) can drive a thousand devices without a human touching a keyboard. The skill here is not curl; it is seeing config as data you can request and replace.

See & Do Turn on RESTCONF and read the box as JSON

Type the config on CSR1, then run the curl commands from your management host. Read each response before moving on.

Enable RESTCONF (and the auth it requires)

CSR1(config)# aaa new-model
CSR1(config)# aaa authentication login default local
CSR1(config)# aaa authorization exec default local
CSR1(config)# username cisco privilege 15 secret cisco123
CSR1(config)# ip http secure-server   ! RESTCONF rides on HTTPS (tcp/443)
CSR1(config)# restconf
CSR1(config)# netconf-yang              ! brings up the shared data-management backend
Why all of this? RESTCONF authenticates every call, so it needs a real user (aaa + username). It rides on the HTTPS server, so it needs ip http secure-server (that also auto-generates a self-signed cert). Confirm the backend processes are up before you call it:
CSR1# show platform software yang-management process
confd            : Running
nesd             : Running
ncsshd           : Running
dmiauthd         : Running
nginx            : Running
ndbmand          : Running
pubd             : Running
nginx is the web front door; ndbmand/confd are the datastore behind it. If a call returns "currently unavailable," these are still starting — give it a minute.

Discover the API root

host$ curl -k -u cisco:cisco123 https://192.168.122.10/.well-known/host-meta
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
    <Link rel='restconf' href='/restconf'/>
</XRD>
host$ curl -k -u cisco:cisco123 https://192.168.122.10/restconf/ \
     -H "Accept: application/yang-data+json"
{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}
Read it: the device advertises where its API lives (host-meta → /restconf), and the root tells you the three doorways: data (config & state you can read/write), operations (RPCs you can call), and the YANG library version. -k accepts the self-signed cert; -u is HTTP Basic auth.

Your first read — the hostname, as data

host$ curl -k -u cisco:cisco123 -H "Accept: application/yang-data+json" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/hostname
{
  "Cisco-IOS-XE-native:hostname": "CSR1"
}
That URL is the config tree. /restconf/data/ is the configuration datastore; Cisco-IOS-XE-native:native/hostname is the path to the hostname leaf in Cisco's native YANG model. You just read one line of the running config as a JSON object — no CLI, no screen-scraping.

Read an interface — and look at the HTTP itself

host$ curl -k -i -u cisco:cisco123 -H "Accept: application/yang-data+json" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/GigabitEthernet=1
HTTP/1.1 200 OK
Content-Type: application/yang-data+json
Cache-Control: private, no-cache, must-revalidate, proxy-revalidate
Pragma: no-cache

{
  "Cisco-IOS-XE-native:GigabitEthernet": {
    "name": "1",
    "ip": {
      "address": {
        "dhcp": {}
      }
    },
    "mop": { "enabled": false, "sysid": false },
    "Cisco-IOS-XE-ethernet:negotiation": { "auto": true }
  }
}
Read the envelope, not just the body. HTTP/1.1 200 OK is the verdict; Content-Type: application/yang-data+json is the RESTCONF media type that tells your program "this is YANG-modeled JSON." The body says Gi1 gets its address by DHCP and auto-negotiates — the same facts as show run interface Gi1, but parseable in one line of code.

Now You (guided) Change the router with a single HTTPS call

Reading is half the story. The payoff is writing: create a whole interface with one PUT, then prove it became real IOS config. You build the request; I'll give you the target and the shape.

  1. PUT to the URL of the object you want to exist — a Loopback100 under native/interface — with a JSON body describing it. PUT means "make this resource look exactly like my body" (create if absent, replace if present).
  2. Give it a description and 10.100.100.1/32.
  3. Then GET the same URL to confirm, and check on the device that it is real running config.

Target to hit: the PUT returns HTTP 201 Created; the read-back shows your loopback; and show ip interface brief on CSR1 lists Loopback100 up/up. A REST call and a CLI both wrote the same config — that is the point.

Stuck? Reveal the PUT
host$ curl -k -u cisco:cisco123 -X PUT \
     -H "Content-Type: application/yang-data+json" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100 \
     -d '{
       "Cisco-IOS-XE-native:Loopback": {
         "name": 100,
         "description": "Created via RESTCONF",
         "ip": { "address": { "primary": {
             "address": "10.100.100.1", "mask": "255.255.255.255" } } }
       }
     }'

The last path segment Loopback=100 is the resource key — the "100" in the URL and in the body must agree. No response body comes back on a create; the status code is the answer.

What "correct" looks like

The PUT returns just a status line — 201 means the resource was created:

host$ curl ... -X PUT ... -o /dev/null -w "%{http_code}\n"
201

Read it back over REST — it's there:

host$ curl -k -u cisco:cisco123 -H "Accept: application/yang-data+json" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100
{
  "Cisco-IOS-XE-native:Loopback": {
    "name": 100,
    "description": "Created via RESTCONF",
    "ip": { "address": { "primary": {
        "address": "10.100.100.1", "mask": "255.255.255.255" } } }
  }
}

Now the proof that matters — on the router's own CLI, the REST write is indistinguishable from something you typed:

CSR1# show ip interface brief
Interface              IP-Address      OK? Method Status                Protocol
GigabitEthernet1       192.168.122.10  YES DHCP   up                    up
Loopback100            10.100.100.1    YES other  up                    up

CSR1# show running-config interface Loopback100
interface Loopback100
 description Created via RESTCONF
 ip address 10.100.100.1 255.255.255.255
end

One HTTPS request created a fully-real interface. That is the entire premise of network automation — config is just data you can POST.

Solo Challenge No commands this time

Do this from memory

Run a complete CRUD life-cycle on a second object entirely over RESTCONF, and then delete the loopback you made in Now You. Work out, for each step, the verb, the URL, and the status code that proves success:

  • Create a Loopback200 (any /32 you like) — which verb, and what code confirms it?
  • Update just its description without resending the whole object — which verb merges a change instead of replacing?
  • Delete both loopbacks — which verb, and what "success, nothing to return" code comes back?

You pass when you can state all three and predict each status code:

  • PUT (or POST to the parent) creates → 201 Created.
  • PATCH merges a partial change → 204 No Content (success, nothing to send back).
  • DELETE removes → 204, and a follow-up GET then returns 404 Not Found.
Check your reasoning (open after you've done it)

Deleting the Now-You loopback and confirming it's gone:

host$ curl -k -u cisco:cisco123 -X DELETE -o /dev/null -w "%{http_code}\n" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100
204
host$ curl -k -u cisco:cisco123 -o /dev/null -w "%{http_code}\n" \
     -H "Accept: application/yang-data+json" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100
404

The verb is the intent: PUT = "make it exactly this," PATCH = "change just this part," POST = "add a child under here," DELETE = "remove it." 204 means the write worked and there's nothing to return; the follow-up 404 is your proof the object is truly gone. Same four verbs drive every RESTCONF-capable device on earth.

Break & Fix Read a RESTCONF error on purpose

Automation lives or dies on reading the error the machine sends back. Ask for a path that doesn't exist — here, an operational leaf under a wrong keypath:

host$ curl -k -u cisco:cisco123 -H "Accept: application/yang-data+json" \
     https://192.168.122.10/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces-oper-data/interface=GigabitEthernet1/statistics
{
  "errors": {
    "error": [
      {
        "error-message": "uri keypath not found",
        "error-tag": "invalid-value",
        "error-type": "application"
      }
    ]
  }
}
Read the fingerprint: RESTCONF doesn't just throw an HTTP 400 — it returns a structured error your code can branch on: error-tag (invalid-value) is the machine-readable reason, error-message is the human hint, error-type says which layer rejected it. The two errors you'll hit constantly:
  • 401 Unauthorized — wrong/missing -u credentials (the call never reaches the datastore).
  • 400 / "malformed message" — you forgot -H "Content-Type: application/yang-data+json" on a write, or the JSON key doesn't match the model.

Now fix it: the reliable way to learn a correct URL is to read down the tree — GET the parent container first (.../Cisco-IOS-XE-native:native/interface) and copy the exact model names and keys from the JSON it returns. The model is self-documenting; let the device tell you the path.

Done check

Close this lab when every one of these is true, driven entirely from your management host:

CallProof you want
GET /restconf/200 + the ietf-restconf:restconf root JSON
GET .../native/hostname{"Cisco-IOS-XE-native:hostname":"CSR1"}, Content-Type: application/yang-data+json
PUT .../Loopback=100201 Created
show ip interface brief on CSR1Loopback100 10.100.100.1 up up — the REST write is real IOS config
DELETE .../Loopback=100 then GET204, then 404 — created and removed purely by API

Takeaway for your notes: "A device is an API: config is a URL you GET and a JSON body you PUT. The verb is the intent and the status code is the truth — 201 created, 204 changed/deleted, 404 gone, 401 who-are-you. Once config is data, a script can do what a thousand keystrokes did."

Reset this lab?

Restore its GNS3 project to the original unsolved topology.

All saved device configuration in this lab will be erased. Every node must be stopped first.