6.5 REST API Response Codes and Payloads¶
Blueprint 6.5: Interpret REST API response codes and results in payload using Cisco Catalyst Center and RESTCONF
- HTTP request and response structure: method, URI, headers, body, status line
- Status code classes and the specific codes the exam uses: 200, 201, 202, 204, 400, 401, 403, 404, 405, 409, 415, 429, 500, 503
- RESTCONF: URI structure, media types, method semantics, success codes, and the
errorsbody - Catalyst Center: the
responsewrapper, task-based 202 replies, and authentication errors - Turning a status code plus a payload into a diagnosis
The verb is interpret. You will be shown a request and its response, or a script that branches on status_code, and asked what happened or what the script prints. Learn the codes as pairs of "what the client did" and "what the server said".
Anatomy of a REST exchange¶
PATCH /restconf/data/ietf-interfaces:interfaces/interface=Loopback100 HTTP/1.1 <- method, URI, version
Host: 192.168.100.1
Authorization: Basic YWRtaW46Q2lzY28xMjMh <- credentials
Content-Type: application/yang-data+json <- format of the body sent
Accept: application/yang-data+json <- format wanted back
{"ietf-interfaces:interface": {"name": "Loopback100", "description": "core"}} <- body (payload)
HTTP/1.1 204 No Content <- status line
Date: Wed, 07 Oct 2026 10:15:02 GMT
| Part | Request | Response |
|---|---|---|
| First line | Method and URI | Status code and reason phrase |
| Headers | Authorization, X-Auth-Token, Content-Type, Accept |
Content-Type, Location (after a create), Set-Cookie |
| Body | Data to create or change (POST, PUT, PATCH) | Data returned (GET) or an error description |
Content-Type describes the body being sent. Accept asks for a format coming back. Getting either wrong is a classic cause of a 4xx response.
Methods and CRUD¶
| CRUD | HTTP method | RESTCONF meaning | NETCONF equivalent |
|---|---|---|---|
| Create | POST | Create a child resource; error if it already exists | <edit-config> operation create |
| Read | GET | Retrieve the resource | <get> / <get-config> |
| Update (replace) | PUT | Create the resource, or replace it entirely | <edit-config> operation replace |
| Update (merge) | PATCH | Merge the supplied fields into the existing resource | <edit-config> operation merge |
| Delete | DELETE | Remove the resource | <edit-config> operation delete |
Exam trap
PUT replaces. If you PUT an interface body that contains only name and description, any leaf you left out (an IP address, for instance) is removed, because the new resource is exactly what you sent. PATCH would have changed only the description. Questions that ask why an address "disappeared" after an API change are testing this.
Status codes¶
| Code | Name | Meaning | Typical cause on Catalyst Center or RESTCONF |
|---|---|---|---|
| 200 | OK | Success, body contains a result | GET returned data; Catalyst Center token request succeeded |
| 201 | Created | A new resource was created | RESTCONF POST, or PUT that created a new resource; Location header names it |
| 202 | Accepted | Request accepted, processing continues in the background | Catalyst Center asynchronous call; body contains a taskId |
| 204 | No Content | Success, no body | RESTCONF PATCH, PUT that replaced an existing resource, DELETE |
| 3xx | Redirection | Resource is elsewhere | Rare in these APIs |
| 400 | Bad Request | Request is malformed or invalid | Bad JSON syntax, a value that fails YANG validation, unknown leaf |
| 401 | Unauthorized | Not authenticated | Missing or wrong password, missing, expired or invalid token |
| 403 | Forbidden | Authenticated, but not allowed | User role lacks permission, such as a read-only role trying a POST |
| 404 | Not Found | URI does not name an existing resource | Typo in the path, wrong module name, list key that does not exist |
| 405 | Method Not Allowed | Resource does not support that method | POST to a resource that only supports GET |
| 409 | Conflict | Request conflicts with current state | RESTCONF POST of a resource that already exists (data-exists), datastore locked |
| 415 | Unsupported Media Type | Server cannot process the body's Content-Type |
Wrong or missing Content-Type header |
| 429 | Too Many Requests | Rate limit hit | Catalyst Center rate limiting a busy script |
| 500 | Internal Server Error | Server failed | Bug or back-end failure |
| 503 | Service Unavailable | Server temporarily unable to serve | Service starting up or overloaded |
The first digit tells you whose problem it is:
graph LR
S["Status code"] --> A["2xx success<br/>check the body or task"]
S --> B["3xx redirect<br/>follow Location"]
S --> C["4xx client error<br/>fix the request"]
S --> D["5xx server error<br/>retry later or escalate"]
C --> C1["401: who are you"]
C --> C2["403: you may not"]
C --> C3["404: no such resource"]
C --> C4["400: request is wrong"]
Exam trap
401 versus 403 is the most tested pair. 401 Unauthorized really means unauthenticated: the server does not know who you are, so fix the credentials or get a new token. 403 Forbidden means the server knows exactly who you are and refuses: fix the role or permissions. Sending the same request with a fresh token will not cure a 403.
RESTCONF in detail¶
URI structure¶
graph LR
A["https://192.168.100.1"] --> B["/restconf"]
B --> C["/data<br/>datastore resource"]
B --> O["/operations<br/>RPCs"]
C --> D["/ietf-interfaces:interfaces<br/>module:top-level node"]
D --> E["/interface=GigabitEthernet1<br/>list=key"]
E --> F["/description<br/>leaf"]
| Element | Example | Rule |
|---|---|---|
| Root | /restconf |
Discoverable at /.well-known/host-meta |
| Datastore | /restconf/data |
Configuration and state |
| Operations | /restconf/operations |
YANG RPCs |
| First node | ietf-interfaces:interfaces |
Prefixed with the module name |
| List entry | interface=GigabitEthernet1 |
list=key; several keys are comma-separated |
| Special characters in keys | interface=GigabitEthernet0%2F0%2F0 |
A / inside a key must be percent-encoded as %2F |
| Query parameters | ?depth=2, ?content=config, ?fields=name |
Defined in RFC 8040; support varies by release |
Media types¶
| Header value | Meaning |
|---|---|
application/yang-data+json |
YANG data encoded as JSON (RFC 7951) |
application/yang-data+xml |
YANG data encoded as XML |
Send Accept on reads and Content-Type on writes. Without an Accept header, IOS XE replies in XML.
Method semantics and success codes (RFC 8040)¶
| Method | Target URI | Body | Success | Common failure |
|---|---|---|---|---|
| GET | The resource | None | 200 with data | 404 if it does not exist |
| POST | The parent resource | The new child | 201 with Location |
409 if the child already exists |
| PUT | The resource itself | The complete resource | 201 if created, 204 if replaced | 400 if the key in the URI and body differ |
| PATCH | The resource itself | Fields to merge | 204 (or 200 if a body is returned) | A 4xx error, typically 404, if the target does not exist |
| DELETE | The resource itself | None | 204 | A 4xx error, typically 404, if it does not exist |
Exam trap
POST goes to the parent URI and names the new child in the body. PUT and PATCH go to the URI of the resource itself. A POST to .../interfaces/interface=Loopback100 asks the server to create something inside Loopback100, which is not what was intended.
Reading a RESTCONF error body¶
When a RESTCONF request fails, the body explains why in a structured errors object (RFC 8040 section 7.1). IOS XE returns a body shaped like this one, though some releases omit the ietf-restconf: prefix:
{
"ietf-restconf:errors": {
"error": [
{
"error-type": "application",
"error-tag": "data-exists",
"error-path": "/ietf-interfaces:interfaces/interface[name='Loopback100']",
"error-message": "object already exists"
}
]
}
}
| Field | Tells you |
|---|---|
error-type |
Layer of the failure: transport, rpc, protocol or application |
error-tag |
Machine-readable reason, such as data-exists, invalid-value, malformed-message, access-denied, in-use |
error-path |
Which node caused it |
error-message |
Human-readable explanation |
data-exists goes with 409. malformed-message and most invalid-value errors go with 400. access-denied goes with 401 or 403.
Catalyst Center payloads¶
Every Intent API body wraps its result in response:
The third tab is a trimmed task record. Note the two levels of success: the HTTP code is 200 because the task query worked, while isError: true says the deployment failed. You need both to interpret the result.
Times in Catalyst Center payloads are usually epoch milliseconds, so endTime - startTime above is 30,000 ms, or 30 seconds.
Exam trap
A 200 on a task query does not mean the task succeeded. Read isError and failureReason in the payload. Equally, a 202 on the original POST says nothing about the outcome yet.
Interpreting a script that checks responses¶
import json
def report(status, body_text):
"""Explain a Catalyst Center or RESTCONF reply the way an engineer would."""
if status in (200, 201):
body = json.loads(body_text) if body_text else {}
if "response" in body and isinstance(body["response"], list):
return f"OK, {len(body['response'])} item(s)"
return "OK"
if status == 202:
task = json.loads(body_text)["response"]["taskId"]
return f"accepted, poll task {task}"
if status == 204:
return "done, no content"
if status == 401:
return "authentication failed: refresh the token or fix credentials"
if status == 403:
return "authorised user, but the role does not allow this"
if status == 409:
errs = json.loads(body_text)["ietf-restconf:errors"]["error"]
return "conflict: " + errs[0]["error-tag"]
if 400 <= status < 500:
return f"client error {status}: fix the request"
return f"server error {status}: retry later"
samples = [
(200, '{"response": [{"hostname": "dist-sw1"}, {"hostname": "acc-sw7"}], "version": "1.0"}'),
(202, '{"response": {"taskId": "0c2a9f6e", "url": "/api/v1/task/0c2a9f6e"}, "version": "1.0"}'),
(204, ""),
(409, '{"ietf-restconf:errors": {"error": [{"error-type": "application", "error-tag": "data-exists"}]}}'),
(403, ""),
(404, ""),
(503, ""),
]
for code, text in samples:
print(code, report(code, text))
Output:
200 OK, 2 item(s)
202 accepted, poll task 0c2a9f6e
204 done, no content
409 conflict: data-exists
403 authorised user, but the role does not allow this
404 client error 404: fix the request
503 server error 503: retry later
Note the order of the if tests. The 401, 403 and 409 checks come before the generic 400 <= status < 500 test, so they produce specific messages. If the generic test came first, every 4xx would print "fix the request". The body for 204 is empty, which is why that branch never calls json.loads; calling it on an empty string raises JSONDecodeError.
Lab: provoke every status code¶
Topology: AUTO (Ubuntu, 192.168.100.50) to R1 (C8000v, Gi1 192.168.100.1/24) with RESTCONF enabled as in the Python and JSON lab. Use the lb100.json file you wrote there.
graph LR
AUTO["AUTO<br/>curl and Python"] -- "HTTPS 443" --- R1["R1 C8000v<br/>restconf"]
Define a shell shortcut on AUTO so every command prints its status code on the last line:
R="curl -sk -u admin:Cisco123! -H Accept:application/yang-data+json -H Content-Type:application/yang-data+json -w \n%{http_code}\n"
U=https://192.168.100.1/restconf/data/ietf-interfaces:interfaces
| Step | Command | Expected code | Why |
|---|---|---|---|
| 1 | $R $U/interface=GigabitEthernet1 |
200 | Resource exists, data returned |
| 2 | $R -X POST -d @lb100.json $U |
201 | New child created under the parent |
| 3 | Repeat step 2 | 409 | Loopback100 already exists (data-exists) |
| 4 | $R -X PATCH -d '{"ietf-interfaces:interface":{"name":"Loopback100","description":"patched"}}' $U/interface=Loopback100 |
204 | Merge succeeded, no body |
| 5 | $R $U/interface=Loopback999 |
404 | No such list entry |
| 6 | Step 4 with the comma after "Loopback100" removed |
400 | Malformed JSON |
| 7 | Step 1 with the password changed to wrong |
401 | Authentication failed |
| 8 | $R -X PUT -d '{"ietf-interfaces:interface":{"name":"Loopback100","type":"iana-if-type:softwareLoopback"}}' $U/interface=Loopback100 |
204 | Existing resource replaced |
| 9 | $R -X DELETE $U/interface=Loopback100 |
204 | Deleted |
After step 8, verify on R1 that PUT really replaced the resource:
The description and the IP address from earlier steps are gone, because the PUT body did not include them. After step 9, show ip interface brief | include Loopback100 returns nothing.
Finally, save the report() script above as report.py, run it with python3 report.py, and confirm the output matches. Then add a branch for 404 that prints "check the URI and the list key", and place it so that it actually runs.
Quick check¶
A RESTCONF POST returns 409 with error-tag data-exists. What should the script do if the goal is to update the existing resource?
Use PATCH (merge) or PUT (replace) to the resource's own URI instead of POST to the parent.
A Catalyst Center call that worked an hour ago now returns 401 with the same script. What is the most likely cause?
The token expired. Request a new token from /dna/system/api/v1/auth/token and retry.
A GET to .../interface=GigabitEthernet0/0/0 returns 404, but the interface exists. Why?
The / characters in the key must be percent-encoded: interface=GigabitEthernet0%2F0%2F0. Unencoded, the server reads them as path separators.
References¶
- RFC 8040: RESTCONF Protocol, sections 4 (methods) and 7 (error reporting)
- RFC 9110: HTTP Semantics, status code definitions
- Programmability Configuration Guide, Cisco IOS XE, RESTCONF chapter (search on cisco.com)
- Cisco DevNet: Catalyst Center Intent API reference, including the task API
- Cisco Live On-Demand Library: search "RESTCONF" and "Catalyst Center API"
Unlock the full question banks and cram sheets
A free account opens every question bank, the timed practice exams, the printable cram sheets, all the simulations and the lab files. Without one you see a 5-question sample of each.
No card needed. Study tips by email are optional and off unless you tick the box. Your address is never sold or shared. Privacy policy ยท Terms
Signed in as . Your account
You're on the free account, so lessons after the first module, question banks, exams and cram sheets show a sample. See Track Pass and All-Access
Also from NetManager Systems: NetManager, network monitoring for multi-vendor networks. Try the NetManager, NetDiag, PCAP Analyzer and OpsDash demos: every new account gets a free 7-day read-only login to each.