Latest Cisco, PMP, AWS, CompTIA, Microsoft Materials on SALE Get Now Get Now

NETCONF, RESTCONF and YANG: Learn the Model, the Protocols Are Thin

Programmable interfaces are usually introduced as a replacement for the command line, which is the wrong framing and makes them harder to learn than they need to be. The command line is a language for humans that happens to be parseable; NETCONF and RESTCONF are interfaces to a data model that happens to be readable. The difference that matters is not the transport or the encoding but the fact that there is a schema underneath, describing exactly what may be configured and what shape it takes.

That schema is YANG, and understanding it is most of the work. Once the model is clear — what a container is, why a list needs a key, which nodes are configuration and which are read-only state — both protocols become thin wrappers around operations on it. NETCONF sends remote procedure calls over a persistent session; RESTCONF maps the same tree onto HTTP verbs and URLs. Neither adds semantics the model does not already have.

This article covers what YANG actually describes, how a NETCONF session is established and what its operations do, how RESTCONF expresses the same thing over HTTP, how datastores and commits differ between them, and the failure catalogue — the problems that present as model errors and are almost always something more mundane.

Blog ClaimAlmost every error a newcomer meets on these interfaces is a namespace, a missing list key, or a datastore that was never enabled — and none of those is a modelling problem, which is why reading the model harder rarely helps.
netconf-restconf-yang-ccie-lab-fig1

One schema addressed by two protocols. The model defines everything; NETCONF and RESTCONF differ only in transport, encoding and how they handle state.

What Does YANG Actually Describe?

What is in a model?

A tree of nodes with types and constraints. Containers group things. Lists hold repeated entries, each identified by a key. Leaves hold single values with a defined type. Every node is marked as configuration or as read-only state, which is what lets a client ask for one and not the other. The model also declares a namespace, and that namespace is what makes the difference between a request that works and one that is rejected as unknown.

A Deeper Dive into YANG

Reading a model as a tree

YANG source is verbose and the tree representation is what people actually work from. It shows the hierarchy, marks configuration nodes, indicates which leaves are keys, and shows types — everything needed to construct a request, in a form that fits on a screen. Generating it is the first thing to do with any unfamiliar model.

# Render a model as a tree - the practical starting point
$ pip install pyang
$ pyang -f tree ietf-interfaces.yang

module: ietf-interfaces
  +--rw interfaces
  |  +--rw interface* [name]
  |     +--rw name           string
  |     +--rw description?   string
  |     +--rw type           identityref
  |     +--rw enabled?       boolean    <true>
  +--ro interfaces-state
     +--ro interface* [name]
        +--ro oper-status    enumeration

# rw = configuration, ro = read-only state
# *  = a list,  [name] = its key,  ? = optional

The three families of model

Standard models from the IETF are portable across vendors and cover the common ground at the cost of the lowest common denominator. Vendor-neutral community models go further while remaining multi-vendor. Native models expose everything a platform can do and are as portable as the platform is. The choice is the usual trade between portability and coverage, and mixing them in one automation project is normal rather than a compromise.

Family Example Portable Coverage Use when
IETF standard ietf-interfaces Across vendors Common ground only Simple, portable operations
Vendor-neutral community openconfig-interfaces Across supporting vendors Broad Multi-vendor automation
Native Cisco-IOS-XE-native No Everything the platform does Anything the others do not cover

Configuration data and state data are different things

Every node in a model is marked as writable configuration or as read-only state, and the distinction is more useful than it first appears. Configuration is what an operator decided; state is what resulted, including values the device derived for itself. An interface's administrative status is configuration because somebody set it; its operational status is state because the device determined it. Confusing the two produces requests that attempt to write something no client can write.

The practical consequence shows up in retrieval. Asking for configuration returns a document that could be sent back to a device to reproduce the same setup; asking for everything returns that plus counters, statuses and negotiated values, which is much larger and cannot be replayed. Knowing which one you want before making the request avoids parsing a great deal that was never relevant.

This distinction is also what makes model-driven backup meaningful. A configuration-only retrieval of the whole tree is a complete, structured, machine-comparable record of the device's intent — something a text capture of the command line only approximates, because it mixes the two and imposes an ordering that carries no meaning.

Namespaces, which cause most early errors

Every module declares a namespace, and a request must place its data in the right one or the device will reject it as referring to something it does not have. In XML that is an xmlns attribute; in JSON it is a module prefix on the top-level name. Getting it wrong produces an error that says the element is unknown, which reads like a model problem and is a typing problem.

<!-- The namespace is not decoration -->
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
  <interface>
    <name>GigabitEthernet2</name>
    <description>Set by NETCONF</description>
  </interface>
</interfaces>

# In JSON the module name prefixes the top-level container
{
  "ietf-interfaces:interface": {
    "name": "GigabitEthernet2",
    "description": "Set by RESTCONF"
  }
}

Lists need their keys

A list entry is identified by its key, and an operation on a list without supplying the key has not identified anything. This is the second most common early error and it produces a message about a missing element, which again reads as a model problem. The key is visible in the tree output in square brackets, which is one more reason to generate the tree before writing anything.

<!-- WRONG: which interface? -->
<interface>
  <description>New description</description>
</interface>

<!-- RIGHT: the key identifies the entry -->
<interface>
  <name>GigabitEthernet2</name>
  <description>New description</description>
</interface>

# In RESTCONF the key goes in the URL after an = sign
GET /restconf/data/ietf-interfaces:interfaces/interface=GigabitEthernet2

Why the model matters more than the protocol

People arriving from a scripting background often expect the hard part to be the protocol, and it is not. Both protocols can be learned in an afternoon; the model is what takes time, because it is where all the domain knowledge lives. Knowing that an interface has a description leaf is trivial. Knowing which module owns the routing configuration on a given platform, how policy is represented, and which parts are covered by a standard model and which are only in the native one — that is the actual work.

This has a practical implication for how to learn it. Time spent reading tree output for the models you intend to use returns far more than time spent on protocol details, and the fastest way to understand any area is to configure it by hand at the command line and then retrieve that subtree through the model. The correspondence between the two is immediately visible and it teaches the model's shape faster than reading it cold.

It also explains why automation projects stall at the point of touching an unfamiliar feature. The protocol works, the credentials work, the previous scripts work — and nobody knows which nodes express the new requirement. That is a model discovery problem and it is solved by retrieval and comparison rather than by anything protocol-related.

Enabling the interfaces on the device

! Both protocols need AAA before they will start
aaa new-model
aaa authentication login default local
aaa authorization exec default local
username admin privilege 15 secret <password>
!
netconf-yang
restconf
ip http secure-server
! ^ restconf requires the secure HTTP server
!
! Confirm the processes are actually running
R1# show platform software yang-management process
confd            : Running
nesd             : Running
syncfd           : Running
ncsshd           : Running
dmiauthd         : Running
nginx            : Running
!
R1# show netconf-yang datastores
Pitfall: authorisation not configured, so the interfaces never start Symptom: netconf-yang is configured, the device accepts the command, and no session can be established — connections to the port are refused or drop immediately. The configuration looks complete. Cause: the management processes require AAA authorisation to be configured, and without aaa authorization exec they do not start. Enabling the feature does not enable its prerequisites. Confirm: show platform software yang-management process shows processes not running; the running configuration has no aaa authorization line. Fix: configure AAA authentication and authorisation, then confirm every process reports as running before troubleshooting anything client-side.
Sub claimThe model declares a namespace and every list declares a key, which is why the two most common beginner errors both report as unknown elements and neither is a problem with the model.

How Does a NETCONF Session Work?

What happens on connection?

The client opens an SSH session to the management subsystem, and both ends immediately send a hello message listing what they support. That exchange is not a formality: it tells the client which datastores exist, whether validation is available, whether the device can roll back on error, and which models it implements. Everything the client subsequently attempts should be governed by what that hello said, and a great deal of trouble comes from assuming a capability that was never advertised.

A Deeper Dive into NETCONF

Reading the capability exchange

<!-- What the device says it can do, at session start -->
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <capabilities>
    <capability>urn:ietf:params:netconf:base:1.1</capability>
    <capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:validate:1.1</capability>
    <capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>
    <!-- followed by one entry per supported YANG module -->
  </capabilities>
  <session-id>41</session-id>
</hello>

# If :candidate is absent, every example using it will fail.
# Check the hello before assuming.

The operations that matter

Six cover nearly everything. Retrieve configuration, retrieve configuration and state together, edit, lock, unlock, and close. Locking is the one most often skipped and most worth using: it prevents another client or a person at the console changing the configuration underneath an in-progress operation, and releasing it is automatic if the session drops.

Operation Does Note
<get-config> Returns configuration from a datastore Takes a source and an optional filter
<get> Returns configuration and state Always from running
<edit-config> Changes a datastore Operation attribute decides merge or replace
<lock> / <unlock> Prevents concurrent modification Released automatically on disconnect
<validate> Checks a datastore without applying Requires the validate capability
<commit> Applies candidate to running Requires the candidate capability

The edit operation attribute

An edit defaults to merging, which adds and updates without removing anything. Replace substitutes the entire subtree, so anything present on the device and absent from the request is deleted — which is exactly what a declarative tool wants and exactly what surprises someone expecting an additive change. Create fails if the node exists, delete fails if it does not, and remove is a delete that tolerates absence.

<!-- Default: merge. Adds the description, touches nothing else -->
<config>
  <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
    <interface>
      <name>GigabitEthernet2</name>
      <description>Merged in</description>
    </interface>
  </interfaces>
</config>

<!-- Replace: this becomes the WHOLE interface entry -->
<interface xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
           nc:operation="replace">
  <name>GigabitEthernet2</name>
  <description>Only this remains</description>
</interface>
<!-- ^ any address, any other leaf on Gi2, is now GONE -->

Error handling options

An edit carrying several changes can stop at the first failure, continue past it, or undo everything already applied. The last is what most automation wants, because a half-applied configuration is worse than an unapplied one, and it requires the rollback capability to have been advertised. Combined with a test option that validates before applying, it produces an operation that either fully succeeds or changes nothing.

<!-- All or nothing -->
<edit-config>
  <target><running/></target>
  <error-option>rollback-on-error</error-option>
  <test-option>test-then-set</test-option>
  <config> ... </config>
</edit-config>

# error-option:  stop-on-error (default) | continue-on-error | rollback-on-error
# test-option:   test-then-set (default) | set | test-only

Filters, and why they are not optional at scale

An unfiltered retrieval on a device with a substantial configuration returns a very large document, and doing that repeatedly across a fleet is slow enough to matter and heavy enough on the device to be noticed. Filtering narrows the request to the subtree that is actually wanted, which is both faster and considerably easier to parse because the response contains only the relevant nodes.

Two filter styles exist. A subtree filter is a skeleton of the structure being asked for, which is verbose and needs no additional syntax to learn. An XPath filter is a path expression, which is compact and expressive but requires the device to have advertised support for it. Subtree filtering works everywhere and is the safer default for portable code.

The habit worth forming is to never issue an unfiltered retrieval against a production device except deliberately, for a backup. Everything else should name what it wants, which also makes the intent of the code obvious to whoever reads it later.

A working client session

# Python, using ncclient
from ncclient import manager

with manager.connect(host='192.0.2.1', port=830,
                     username='admin', password='<password>',
                     hostkey_verify=False,
                     device_params={'name': 'iosxe'}) as m:

    # what did the device advertise?
    for c in m.server_capabilities:
        if 'candidate' in c or 'rollback' in c:
            print(c)

    # retrieve one subtree
    f = '''<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>'''
    print(m.get_config(source='running', filter=('subtree', f)))

    # change something, safely
    with m.locked('running'):
        m.edit_config(target='running', config=payload,
                      error_option='rollback-on-error')
A session is stateful and that is the pointNETCONF holds a session open across many operations, which is what makes locking, staged edits and a single commit possible. It also means a dropped connection discards uncommitted work and releases locks — usually the desired behaviour, and worth knowing before an intermittent management path is blamed for something else.
Read the hello before writing any client codeIt lists the datastores, the error-handling capabilities and every model the device implements, which together answer most of the questions that would otherwise be resolved by trial and error. Capturing it once per platform and release is a few minutes that saves a great deal of guessing.
Sub claimThe capability exchange states exactly which datastores and error-handling behaviours are available, which makes assuming a capability the device never advertised the root of a whole category of confusing failures.

How Does RESTCONF Map the Same Model to HTTP?

What is the mapping?

The path through the model becomes the URL, and the HTTP verb becomes the operation. Retrieving is a GET, replacing is a PUT, merging is a PATCH, removing is a DELETE, and creating a new entry is a POST to the parent. List keys appear in the URL after an equals sign. Once that correspondence is clear, anyone comfortable with HTTP can operate on a YANG model without learning a new protocol at all.

A Deeper Dive into RESTCONF

The verb table

Verb Does NETCONF equivalent Fails if
GET Retrieve <get-config> / <get> The resource does not exist
PATCH Merge Default <edit-config> The parent does not exist
PUT Replace the resource operation="replace" — creates it if absent
POST Create a child operation="create" The child already exists
DELETE Remove operation="delete" The resource does not exist

Reading and writing one interface

# Retrieve - note the module name before the colon
curl -sk -u admin:'<password>' \
  -H 'Accept: application/yang-data+json' \
  'https://192.0.2.1/restconf/data/ietf-interfaces:interfaces/interface=GigabitEthernet2'

{
  "ietf-interfaces:interface": {
    "name": "GigabitEthernet2",
    "description": "Uplink",
    "type": "iana-if-type:ethernetCsmacd",
    "enabled": true
  }
}

# Merge a single leaf - PATCH leaves everything else alone
curl -sk -u admin:'<password>' -X PATCH \
  -H 'Content-Type: application/yang-data+json' \
  -d '{"ietf-interfaces:interface":{"name":"GigabitEthernet2",
       "description":"Changed by RESTCONF"}}' \
  'https://192.0.2.1/restconf/data/ietf-interfaces:interfaces/interface=GigabitEthernet2'

Query parameters worth knowing

Four of them cover most needs. Content selects configuration, state, or both, which matters because a plain retrieval of a large subtree returns a great deal that is not editable. Depth bounds how far down the tree the response goes. Fields selects specific leaves. And the defaults handling decides whether values equal to their model default are included, which is the reason two apparently identical devices can return different-sized responses.

# Configuration only, no operational state
'.../restconf/data/ietf-interfaces:interfaces?content=config'

# State only
'.../restconf/data/ietf-interfaces:interfaces?content=nonconfig'

# Only the top level, not the whole subtree
'.../restconf/data/ietf-interfaces:interfaces?depth=2'

# Just two leaves from each entry
'.../restconf/data/ietf-interfaces:interfaces?fields=interface(name;enabled)'

# Include or omit values that match the model default
'.../restconf/data/ietf-interfaces:interfaces?with-defaults=report-all'

Status codes carry the diagnosis

RESTCONF returns ordinary HTTP status codes and they mean what they usually mean, which makes debugging more approachable than the equivalent in XML. Unauthorised means credentials. Not found means the path does not exist, which is usually a namespace or a key rather than a missing feature. Conflict means a create was attempted on something that already exists. Bad request means the payload was malformed or the model rejected it, and that response carries a body explaining why.

Reading the body on an error is the step most often skipped, because the status code alone feels sufficient. It is not: the body contains the same structured error information NETCONF returns, including the path that failed, and it converts a generic bad request into a specific statement about which node was wrong. Any client worth using logs it.

There is one distinction worth internalising. A not-found on a path that should exist is nearly always a client-side spelling or namespace problem, whereas a not-found on a container that genuinely is not configured yet is normal — retrieving something absent is not an error condition in the operator's sense, and code should treat the two differently rather than failing on both.

Finding the root and what is supported

The API root is discoverable rather than assumed, which matters when writing a client intended to work across platforms and releases. The same discovery mechanism exposes which models the device implements and which capabilities it supports, giving RESTCONF an equivalent of the NETCONF hello.

# Discover the API root rather than hardcoding it
curl -sk -u admin:'<password>' \
  'https://192.0.2.1/.well-known/host-meta'

<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
  <Link rel='restconf' href='/restconf'/>
</XRD>

# What capabilities does it support?
curl -sk -u admin:'<password>' \
  -H 'Accept: application/yang-data+json' \
  '.../restconf/data/ietf-restconf-monitoring:restconf-state/capabilities'

# Which modules are implemented?
curl -sk -u admin:'<password>' \
  '.../restconf/data/ietf-yang-library:modules-state'

Choosing between the two protocols

Consideration NETCONF RESTCONF
Transaction across many changes Yes, with candidate and commit No — each request is independent
Locking Yes No
Rollback on error Yes Per request only
Ease of ad-hoc use Needs a client library Any HTTP tool
Encoding XML JSON or XML
Best for Configuration management systems Scripts, integrations, quick queries
RESTCONF has no transaction across requestsEach request stands alone and takes effect immediately. A sequence of five requests that fails on the third leaves the first two applied, and there is no mechanism to undo them. Where a change must be atomic across several parts of the model, that is a NETCONF job with a candidate datastore, not a RESTCONF one with careful ordering.
Sub claimRESTCONF maps model paths to URLs and operations to HTTP verbs and adds nothing else, which makes it excellent for ad-hoc work and unsuitable for anything that must be atomic across several changes.

How Do Datastores and Commits Work?

What are the datastores?

Running is what the device is doing. Startup is what it will do after a reload. Candidate, where supported, is a scratch copy that can be edited freely and applied in one operation — which is what makes a multi-part change atomic. A more recent architecture adds two conceptual datastores: intended, meaning the configuration after any templating has been resolved, and operational, meaning everything the device actually has including values it derived itself.

A Deeper Dive into Datastores

The candidate datastore, which must be enabled

On many platforms the candidate datastore is not available until it is explicitly turned on, and enabling it changes how writes behave for every client — direct writes to running are no longer accepted, because the model becomes edit-then-commit. That is a deliberate trade and it is worth making knowingly, because a script that wrote directly to running will stop working the moment candidate is enabled.

! Enabling candidate changes the write model for everyone
netconf-yang feature candidate-datastore
!
! Confirm which datastores now exist
R1# show netconf-yang datastores
Datastore Name : running
Datastore Name : candidate
!
! After this, edit-config to running is refused and the
! sequence becomes: edit candidate, validate, commit.

Edit, validate, commit

# The atomic pattern, with ncclient
with manager.connect(host='192.0.2.1', port=830,
                     username='admin', password='<password>',
                     hostkey_verify=False) as m:

    with m.locked('candidate'):
        m.discard_changes()          # start from a clean copy
        m.edit_config(target='candidate', config=part_one)
        m.edit_config(target='candidate', config=part_two)
        m.validate(source='candidate')
        m.commit()

# Either both parts are applied or neither is.
# A failure anywhere before commit leaves running untouched.

Confirmed commit, which protects against losing the device

A change that breaks management connectivity is unrecoverable remotely, and this is the mechanism that addresses it. A confirmed commit applies the change and starts a timer; if a second confirming commit does not arrive before it expires, the device reverts automatically. It is the programmable equivalent of a scheduled reload as a safety net, and it is considerably more precise.

# Apply, but revert automatically unless confirmed
m.commit(confirmed=True, timeout='120')

# ... verify connectivity and the change from the outside ...

m.commit()          # confirm it, making the change permanent

# If the session drops or the timer expires first, the device
# reverts to the previous running configuration on its own.

Locking, and what it does not protect against

A lock stops another NETCONF client from editing the same datastore, and on most implementations it also blocks configuration from the command line for the duration. That is genuinely useful during a multi-step change, and it is released automatically if the session dies, which avoids the classic problem of a crashed script leaving a device unmodifiable.

What it does not do is prevent a RESTCONF request from taking effect, on platforms where the two interfaces do not share locking, nor stop anything that changes configuration by another path entirely. Treating a lock as an absolute guarantee is therefore optimistic; treating it as protection against the most likely collision — another instance of your own automation — is accurate and still worth having.

The related discipline is to hold locks briefly. A lock held across a long computation, or worse across a wait for human input, blocks everyone else for that period and turns a safety mechanism into an availability problem. Acquire, edit, commit, release.

Saving to startup

Neither protocol saves to startup as a side effect of writing to running, which catches people out in exactly the way an unsaved command-line change does. NETCONF has a copy operation for it; on platforms that expose a save action in the model, RESTCONF can invoke that instead. Whichever is used, it is a separate deliberate step.

# NETCONF: copy running to startup
m.copy_config(source='running', target='startup')

# RESTCONF: invoke the platform's save operation
curl -sk -u admin:'<password>' -X POST \
  -H 'Content-Type: application/yang-data+json' \
  'https://192.0.2.1/restconf/operations/cisco-ia:save-config'

# Confirm from the CLI that they now match
R1# show archive config differences nvram:startup-config system:running-config

Which datastore answers which question

Datastore Contains Writable Read it to answer
running Current configuration Yes, unless candidate is enabled “What is configured now?”
candidate Staged edits Yes “What is about to be applied?”
startup Configuration after reload By copy only “Will this survive a restart?”
operational Config plus derived state No “What is the device actually doing?”
The operational datastore answers a question the others cannotRunning holds what was configured; operational holds what exists, including values the device negotiated or derived. An interface speed that was auto-negotiated appears in operational and not in running, which is why a comparison between two devices based on configuration alone can show them identical while they behave differently.
Use confirmed commit for anything touching management reachabilityInterface addressing, access lists on management paths, routing that carries the management session — any of these can lock you out, and a confirmed commit turns that from a site visit into a two-minute wait. It costs one extra call and is the single most valuable habit on these interfaces.
Sub claimOnly the candidate datastore makes a multi-part change atomic, and enabling it changes the write model for every existing client — which makes it a decision about the whole automation estate rather than about one script.

Which Programmability Failures Look Like Model Problems?

What are the failures worth memorising?

Five. The interfaces that never started because authorisation was missing. A namespace omitted or wrong. A list operation with no key. A replace that removed everything it did not mention. And a script that worked against one software release and fails against another because the model changed underneath it.

A Deeper Dive into the Failure Catalogue

The accidental replace

Pitfall: PUT or operation="replace" used where merge was meant Symptom: a request intended to set one leaf removes the interface's address, its description, and everything else that was configured on it. The request succeeded and the outcome is an outage. Cause: replace substitutes the entire subtree with what was supplied, so any node present on the device and absent from the payload is deleted. In RESTCONF that is the plain meaning of PUT, which reads as an innocuous write to anyone coming from a general HTTP background. Confirm: compare the configuration before and after — everything not in the payload has gone. Fix: use PATCH, or the default merge behaviour in NETCONF, unless full replacement is genuinely intended; and test any replace against a lab device before pointing it at anything else.

The model that changed

Pitfall: a script broken by a software upgrade Symptom: automation that ran for a year fails after an upgrade, reporting unknown elements or rejecting values that were previously accepted. Nothing in the script changed. Cause: models are versioned and evolve — nodes are added, deprecated, moved between modules, and constraints tightened. Native models change more freely than standard ones, which is the trade for their coverage. Confirm: retrieve the module list from both releases and compare the revision dates for the modules the script uses. Fix: pin the models a script depends on, test automation against a new release before deploying it, and prefer standard models for anything that must survive upgrades unattended.

The silent partial application

Pitfall: a multi-part change applied halfway Symptom: a configuration push reports an error and the device is left in a state that is neither the old configuration nor the new one, with some interfaces changed and others not. Cause: writing directly to running applies each change as it is processed, and the default error handling stops at the first failure without undoing what preceded it. RESTCONF has no mechanism to do otherwise across requests. Confirm: compare the device against the intended configuration — the boundary between changed and unchanged corresponds to where the error occurred. Fix: use the candidate datastore with a single commit, or at minimum specify rollback-on-error so one edit either fully applies or does not.

Reading the error properly

The error response contains considerably more than the message that client libraries usually surface — a type, a tag, a severity, and frequently the exact path that failed. Printing the whole thing rather than the summary turns most of these failures into a one-line diagnosis, and it is the first change worth making to any script that is proving hard to debug.

<!-- The full error, which names the failing path -->
<rpc-error>
  <error-type>application</error-type>
  <error-tag>missing-element</error-tag>
  <error-severity>error</error-severity>
  <error-path>/interfaces/interface</error-path>
  <error-message>Missing key leaf 'name'</error-message>
</rpc-error>

# In ncclient, print the whole thing rather than str(e)
from ncclient.operations import RPCError
try:
    m.edit_config(target='running', config=payload)
except RPCError as e:
    print(e.type, e.tag, e.path, e.message)

A diagnostic order that works

# Bottom-up, and most cases end in the first two steps
!
# 1. Are the interfaces running on the device?
R1# show platform software yang-management process
!
# 2. Can you connect and authenticate at all?
$ ssh -p 830 admin@192.0.2.1 -s netconf
$ curl -sk -u admin:'<password>' https://192.0.2.1/restconf/data/ietf-yang-library:modules-state | head
!
# 3. Does the device implement the model you are using?
#    (grep the module list from step 2)
!
# 4. Does a plain read of that subtree work?
#    If GET fails, the write was never going to succeed.
!
# 5. Only now, the payload - namespace, keys, operation
R1# show netconf-yang sessions
R1# show netconf-yang statistics

Building something worth keeping

The first useful thing to automate is not configuration but retrieval. A script that collects a configuration-only snapshot of every device on a schedule produces a structured, comparable record that is immediately valuable, cannot break anything, and exercises every part of the stack that a configuration push would — credentials, connectivity, model paths, parsing. Getting that working first means the risky part starts from a proven foundation.

The second is a comparison: today's snapshot against yesterday's, per device, reported as a structured difference. Because the data is modelled rather than textual, the differences are meaningful — a changed leaf rather than a moved line — which makes the output genuinely readable and turns configuration drift into something detected automatically rather than discovered during an incident.

Only then is configuration writing worth attempting, and by that point the model paths for everything being touched are already known from the retrieval work. That ordering is slower to reach the exciting part and considerably faster to reach something dependable.

Blueprint framing

The CCIE Enterprise Infrastructure v1.1 blueprint covers programmability within the automation domain, and the questions are about the model and the mechanics rather than about writing large programs: what YANG describes, what distinguishes the two protocols, which datastores exist, and how a change is made and verified. Being able to construct a correct request by hand — with the right namespace and the list key present — is what that material is testing.

Always test a read before attempting a writeA successful retrieval of the subtree proves the connection, the credentials, the namespace and the path all at once. If that fails, no amount of adjusting the write payload will help, and the failure is somewhere earlier than the thing being edited.
Sub claimThe full error response names the failing path and the specific tag, which turns most of these failures into a one-line diagnosis and is discarded by client libraries that surface only the message.

Conclusion

YANG is the part worth learning and the protocols are thin by comparison. A model is a tree of typed nodes, some configuration and some read-only, with lists identified by keys and everything living in a declared namespace. Once that is clear, NETCONF and RESTCONF are two ways of addressing the same tree — one with a persistent session, remote procedure calls and locking, the other with URLs and HTTP verbs.

The differences that matter operationally come down to transactions. NETCONF with a candidate datastore can stage several changes and apply them as one, roll back on error, and confirm a commit so that a change which breaks management reverts itself. RESTCONF can do none of that across requests, which makes it excellent for reading, for single changes and for integration with anything that speaks HTTP, and the wrong choice for a configuration push that must be atomic.

Almost everything that goes wrong early is mundane. The management processes never started because authorisation was not configured. A namespace was omitted. A list key was missing. A replace removed what it did not mention. And a script that worked last year meets a model that has moved on. None of those is a modelling problem, and recognising that is what stops the debugging from starting in the wrong place.

Reference Notes

  1. RFC 7950 defines YANG 1.1, including containers, lists, leaves, leaf-lists, and the config statement that marks a node as configuration or state.
  2. RFC 7950 specifies that each module declares a namespace, and that list entries are identified by the leaves named in the key statement.
  3. RFC 6241 specifies NETCONF, including the <hello> capability exchange performed at session establishment.
  4. RFC 6241 defines the base operations <get>, <get-config>, <edit-config>, <copy-config>, <lock>, <unlock> and <close-session>.
  5. RFC 6241 defines the operation attribute values merge, replace, create, delete and remove, with merge as the default.
  6. RFC 6241 defines the error-option values stop-on-error, continue-on-error and rollback-on-error, and the test-option values test-then-set, set and test-only.
  7. RFC 6241 describes the candidate datastore and the confirmed commit capability, in which a commit reverts automatically unless confirmed within a timeout.
  8. RFC 6242 specifies NETCONF over SSH and assigns TCP port 830 to the NETCONF subsystem.
  9. RFC 8040 specifies RESTCONF, mapping datastore resources to URLs and operations to the HTTP methods GET, POST, PUT, PATCH and DELETE.
  10. RFC 8040 defines the query parameters content, depth, fields and with-defaults, and the discovery of the API root through /.well-known/host-meta.
  11. RFC 8342 defines the network management datastore architecture, adding the intended and operational datastores to the conventional running, candidate and startup.
  12. The CCIE Enterprise Infrastructure v1.1 unified exam topics include network programmability within the automation domain.