Practice Questions

Test your knowledge on this topic in the ENAUTO Exam Trainer — 186 questions across 5 interactive modes.

Cisco ISE (Identity Services Engine) – Deep Dive for ENAUTO v2.0

Exam Relevance

Topic 3.0 Controller-Based Network Automation (30%) — ISE is the identity and policy controller. Unlike other Cisco controllers, ISE uses Basic Auth on every single request — no token exchange, no session cookies. It also requires specific Accept and Content-Type headers for JSON responses (default is XML).

Exam Topics Covered:
3.1 – Day-0 provisioning (network device onboarding)
3.2 – Python to manage and monitor configurations
3.5 – Security automation (policy enforcement, compliance, segmentation — TrustSec/SGTs)
3.6 – Troubleshoot REST API automation solutions
4.4 – Monitor network health

Table of Contents


1 – Architecture Overview

Cisco ISE is a Network Access Control (NAC) platform that provides identity-based access, policy enforcement, and TrustSec segmentation. It exposes two REST API frameworks: ERS (External RESTful Services) and Open API.

┌─────────────────────────────────────────────┐
│              Your Python Script              │
│   (requests library  OR  ciscoisesdk)       │
└──────────────────┬──────────────────────────┘
                   │ HTTPS (port 443)
                   ▼
┌─────────────────────────────────────────────┐
│        Cisco ISE (PAN / PSN nodes)          │
│                                             │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐ │
│  │ ERS API  │  │ Open API │  │  pxGrid   │ │
│  │ /ers/    │  │ /api/    │  │ (pub/sub  │ │
│  │ config/  │  │ v1/      │  │  context  │ │
│  │          │  │          │  │  sharing)  │ │
│  └──────────┘  └──────────┘  └───────────┘ │
└──────────────────┬──────────────────────────┘
                   │ RADIUS / TACACS+
                   ▼
┌─────────────────────────────────────────────┐
│         Network Access Devices (NADs)       │
│   Switches · WLCs · Routers · Firewalls     │
└─────────────────────────────────────────────┘

ISE Node Personas (Exam Favourite)

PersonaRoleAPI Relevance
PAN (Policy Admin Node)Central config, policy authoringERS API runs here (read/write)
PSN (Policy Service Node)RADIUS/TACACS+ authentication decisionsRead-only ERS access
MnT (Monitoring & Troubleshooting)Logging, reporting, session dataMonitoring API endpoints
pxGridContext sharing with ecosystem partnersPub/sub, context-in
A single ISE node can run multiple personas. In small deployments, one node runs all four.

Key API Differences — Three ISE APIs

APIURL PrefixPortPurpose
ERS/ers/config/443 (legacy: 9060)CRUD for ISE objects (devices, users, policies, SGTs)
Open API/api/v1/443Newer REST API for deployment, patching, backup, policy sets
pxGridWebSocket/STOMP8910Pub/sub context sharing (sessions, threats, profiling)

The exam focuses primarily on ERS API. Port 9060 is being deprecated — use port 443.


2 – Authentication with Python requests

Exam Topic: 3.2, 3.6

ISE uses the simplest but most repetitive auth model — HTTP Basic Auth on every single request. No token exchange, no session cookies.

How It Works

  1. Combine username:password and Base64-encode it.
  2. Send as Authorization: Basic <encoded> header in every request.
  3. Python’s requests.auth.HTTPBasicAuth handles this automatically.
  4. You must set Accept: application/json — ISE defaults to XML otherwise.

Full Working Example

import requests
from requests.auth import HTTPBasicAuth
import urllib3
 
# ── Disable self-signed cert warnings (lab only!) ──────────────
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
# ── Connection Details ─────────────────────────────────────────
BASE_URL = "https://ise.example.com"
USERNAME = "ersadmin"
PASSWORD = "Cisco123!"
 
# ── Reusable auth and headers ─────────────────────────────────
AUTH = HTTPBasicAuth(USERNAME, PASSWORD)
 
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json"          # CRITICAL — ISE defaults to XML!
}
 
# ── Test: Get all network devices ─────────────────────────────
response = requests.get(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
response.raise_for_status()
 
result = response.json()["SearchResult"]
print(f"Total devices: {result['total']}")
for device in result["resources"]:
    print(f"  {device['name']} (ID: {device['id']})")

Exam Trap: Accept: application/json Is Required

ISE returns XML by default. If you don’t set Accept: application/json, your response.json() call will fail with a parse error. The exam loves testing this:

# WRONG — returns XML, json() fails
headers = {"Content-Type": "application/json"}
 
# CORRECT — returns JSON
headers = {"Content-Type": "application/json", "Accept": "application/json"}

Exam Trap: ERS Admin Role Required

ISE has two ERS roles:

  • ERS-Admin — full CRUD access (GET, POST, PUT, DELETE)
  • ERS-Operator — read-only access (GET only)

If you try a POST with an ERS-Operator account, you get 403 Forbidden — not 401.

What Happens on Failure?

Status CodeMeaningLikely Cause
401 UnauthorizedBad credentialsWrong username/password or user not in ERS group
403 ForbiddenValid auth, no permissionERS-Operator trying POST/PUT/DELETE
404 Not FoundWrong endpoint pathTypo in /ers/config/...
406 Not AcceptableWrong Accept headerMissing Accept: application/json
415 Unsupported MediaWrong Content-TypeMissing Content-Type: application/json
500 Internal Server ErrorMalformed payloadJSON body has wrong structure/keys
SSL ErrorCertificate issueMissing verify=False in lab

No Session Needed — Every Request Stands Alone

# Unlike SD-WAN, you do NOT need requests.Session()
# Each request is independent — no cookies, no tokens to carry
 
# Request 1
devices = requests.get(f"{BASE_URL}/ers/config/networkdevice",
                       auth=AUTH, headers=HEADERS, verify=False)
 
# Request 2 — completely independent, same auth sent again
users = requests.get(f"{BASE_URL}/ers/config/internaluser",
                     auth=AUTH, headers=HEADERS, verify=False)

Compare Auth Complexity Across Controllers

ControllerAuth StepsSession Required?
ISE0 — Basic Auth on every callNo
Meraki0 — API Key on every callNo
Catalyst Center1 — POST for token, then use headerNo (token in header)
SD-WAN2 — POST for cookie + GET for XSRF tokenYes (requests.Session())

3 – Authentication with ciscoisesdk

Exam Topic: 3.2

The SDK handles auth headers, pagination, CSRF tokens, and response parsing automatically.

Installation

pip install ciscoisesdk

Full Working Example

from ciscoisesdk import IdentityServicesEngineAPI
 
# The SDK handles:
#   - Basic Auth header injection on every request
#   - Accept/Content-Type headers
#   - Response parsing (JSON, dot-notation access)
#   - CSRF token handling (optional)
#   - Pagination
api = IdentityServicesEngineAPI(
    username="ersadmin",
    password="Cisco123!",
    base_url="https://ise.example.com",
    version="3.2_beta",
    uses_api_gateway=True,      # Route through ISE API gateway
    uses_csrf_token=False,      # Set True if CSRF enabled on ISE
    verify=False                # Lab environment only
)
 
print("Authenticated successfully via SDK")
 
# ── Test: Get all network devices ─────────────────────────────
devices = api.network_device.get_all().response.SearchResult.resources
for device in devices:
    print(f"  {device.name} (ID: {device.id})")

Environment Variable Configuration

# Set these instead of passing to constructor
export IDENTITY_SERVICES_ENGINE_USERNAME="ersadmin"
export IDENTITY_SERVICES_ENGINE_PASSWORD="Cisco123!"
export IDENTITY_SERVICES_ENGINE_BASE_URL="https://ise.example.com"
export IDENTITY_SERVICES_ENGINE_VERSION="3.2_beta"
export IDENTITY_SERVICES_ENGINE_USES_API_GATEWAY="true"
export IDENTITY_SERVICES_ENGINE_USES_CSRF_TOKEN="false"
export IDENTITY_SERVICES_ENGINE_VERIFY="false"
# With env vars set, constructor needs no arguments
from ciscoisesdk import IdentityServicesEngineAPI
api = IdentityServicesEngineAPI()

SDK vs requests — When to Use What

Aspectrequestsciscoisesdk
Auth handlingManual (HTTPBasicAuth every call)Automatic
Response formatMust set Accept header + parse JSONDot-notation objects
PaginationManual (page/size params)Built-in iterators
CSRF tokensManual GET + headerAutomatic (if enabled)
Error handlingParse status codesRaises ApiError exceptions
Exam relevanceHigh — must understand raw flowHigh — must know the SDK exists

4 – ERS API vs Open API

Exam Topic: 3.2, 3.6

Understanding which API to use is important for both the exam and real-world automation.

Side-by-Side Comparison

AspectERS APIOpen API
URL prefix/ers/config//api/v1/
Port443 (legacy: 9060)443
Auth methodBasic AuthBasic Auth
Default formatXML (must request JSON)JSON (native)
CRUD supportFull (GET, POST, PUT, DELETE)Varies by endpoint
Enabled by defaultNo (must enable in admin)Yes (from ISE 3.4+)
Primary useIdentity objects (users, devices, SGTs, endpoints)Deployment, patching, policy sets
Response wrapperSearchResult.resourcesVaries
CSRF protectionOptional (X-CSRF-Token)N/A

How to Enable ERS API

ISE Admin GUI → Administration → Settings → API Settings
  → Enable "ERS (Read/Write)"

ERS Must Be Manually Enabled

ERS API is disabled by default in ISE. After ISE upgrades, it may revert to disabled and requires manual re-enablement. This is a common troubleshooting question on the exam — “API calls return connection refused” → ERS not enabled.

ERS API Built-In SDK Browser

ISE provides a built-in API reference at:

https://<ise-ip>/ers/sdk

This interactive browser shows every resource, their JSON/XML schemas, and lets you test calls directly. The exam may reference this as a troubleshooting tool.


5 – Network Device Management

Exam Topics: 3.1, 3.2, 3.5

Network Access Devices (NADs) are the switches, WLCs, and routers that send RADIUS/TACACS+ requests to ISE. Managing them via API is a Day-0 fundamental.

Get All Network Devices (requests)

response = requests.get(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
response.raise_for_status()
 
result = response.json()["SearchResult"]
print(f"Total: {result['total']}, Page: {result.get('currentPage', 1)}")
 
for device in result["resources"]:
    print(f"  {device['name']} — ID: {device['id']}")

Get a Network Device by ID

device_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
 
response = requests.get(
    f"{BASE_URL}/ers/config/networkdevice/{device_id}",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
device = response.json()["NetworkDevice"]
 
print(f"Name: {device['name']}")
print(f"IP:   {device['NetworkDeviceIPList'][0]['ipaddress']}")
print(f"Profile: {device.get('profileName', 'N/A')}")

Get a Network Device by Name (Filter)

# ── ERS supports filtering with query parameters ─────────────
response = requests.get(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH,
    headers=HEADERS,
    params={"filter": "name.EQ.Branch-Switch-01"},
    verify=False
)
result = response.json()["SearchResult"]

ERS Filter Syntax

OperatorSyntaxExample
Equalsname.EQ.valuefilter=name.EQ.Switch-01
Containsname.CONTAINS.valuefilter=name.CONTAINS.Branch
Starts withname.STARTSW.valuefilter=name.STARTSW.HQ
Ends withname.ENDSW.valuefilter=name.ENDSW.-01
Not equalname.NEQ.valuefilter=name.NEQ.test
Filters work on many ERS endpoints, not just network devices.

Create a Network Device

# ── Add a new NAD (Network Access Device) to ISE ─────────────
nad_payload = {
    "NetworkDevice": {
        "name": "Branch-Switch-01",
        "description": "Branch office access switch",
        "authenticationSettings": {
            "radiusSharedSecret": "MyR@diusSecret123",
            "enableKeyWrap": False,
            "dtlsRequired": False,
            "keyInputFormat": "ASCII"
        },
        "snmpsettings": {
            "version": "TWO_C",
            "roCommunity": "public",
            "pollingInterval": 3600,
            "linkTrapQuery": True,
            "macTrapQuery": True
        },
        "profileName": "Cisco",
        "coaPort": 1700,
        "NetworkDeviceIPList": [
            {
                "ipaddress": "10.10.22.66",
                "mask": 32
            }
        ],
        "NetworkDeviceGroupList": [
            "Location#All Locations#Branch-Offices",
            "Device Type#All Device Types#Switches"
        ]
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH,
    headers=HEADERS,
    json=nad_payload,
    verify=False
)
response.raise_for_status()    # 201 Created
# The Location header contains the new resource URL
new_device_url = response.headers.get("Location")
print(f"Device created: {new_device_url}")

Response on Create: 201 Created + Location Header

ISE POST requests return:

  • Status 201 Created (not 200)
  • Empty body (no JSON response)
  • Location header with the new resource URL (contains the UUID)

This is different from Catalyst Center (returns task ID) and Meraki (returns the full object).

Update a Network Device

# ── Update an existing NAD — must send the FULL object ────────
update_payload = {
    "NetworkDevice": {
        "id": device_id,
        "name": "Branch-Switch-01-Updated",
        "description": "Updated via automation",
        "authenticationSettings": {
            "radiusSharedSecret": "NewSecret456!",
            "enableKeyWrap": False,
            "dtlsRequired": False,
            "keyInputFormat": "ASCII"
        },
        "profileName": "Cisco",
        "coaPort": 1700,
        "NetworkDeviceIPList": [
            {"ipaddress": "10.10.22.66", "mask": 32}
        ],
        "NetworkDeviceGroupList": [
            "Location#All Locations#Branch-Offices",
            "Device Type#All Device Types#Switches"
        ]
    }
}
 
response = requests.put(
    f"{BASE_URL}/ers/config/networkdevice/{device_id}",
    auth=AUTH,
    headers=HEADERS,
    json=update_payload,
    verify=False
)
response.raise_for_status()    # 200 OK
print("Device updated successfully")

PUT Requires the Full Object

Unlike Meraki (partial update), ISE PUT requests require the complete resource object. If you omit a field, it may be reset to its default. Always GET the current state, modify what you need, then PUT the full object back.

Delete a Network Device

response = requests.delete(
    f"{BASE_URL}/ers/config/networkdevice/{device_id}",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
response.raise_for_status()    # 204 No Content
print("Device deleted")

Network Devices with SDK

# ── Get all devices ──────────────────────────────────────────
devices = api.network_device.get_all().response.SearchResult.resources
 
# ── Get by ID ────────────────────────────────────────────────
device = api.network_device.get_by_id(id=device_id).response.NetworkDevice
 
# ── Get by name ──────────────────────────────────────────────
device = api.network_device.get_by_name(name="Branch-Switch-01").response.NetworkDevice
 
# ── Create ───────────────────────────────────────────────────
api.network_device.create_network_device(
    name="Branch-Switch-02",
    description="Added via SDK",
    authentication_settings={
        "radiusSharedSecret": "Secret123",
        "enableKeyWrap": False,
        "dtlsRequired": False,
        "keyInputFormat": "ASCII"
    },
    profile_name="Cisco",
    coa_port=1700,
    network_device_iplist=[
        {"ipaddress": "10.10.22.67", "mask": 32}
    ],
    network_device_group_list=[
        "Location#All Locations",
        "Device Type#All Device Types"
    ]
)
 
# ── Delete ───────────────────────────────────────────────────
api.network_device.delete_by_id(id=device_id)

6 – Internal User Management

Exam Topics: 3.2, 3.5

Internal users are identity accounts stored directly in ISE (as opposed to Active Directory or LDAP).

Get All Internal Users

response = requests.get(
    f"{BASE_URL}/ers/config/internaluser",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
users = response.json()["SearchResult"]["resources"]
 
for user in users:
    print(f"  {user['name']} — ID: {user['id']}")

Get a User by ID (Full Details)

user_id = "abc123-def456-ghi789"
 
response = requests.get(
    f"{BASE_URL}/ers/config/internaluser/{user_id}",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
user = response.json()["InternalUser"]
 
print(f"Username: {user['name']}")
print(f"Enabled: {user['enabled']}")
print(f"Email: {user.get('email', 'N/A')}")
print(f"Identity Group: {user.get('identityGroups', 'N/A')}")

Create an Internal User

# ── First, get the identity group ID ─────────────────────────
groups = requests.get(
    f"{BASE_URL}/ers/config/identitygroup",
    auth=AUTH, headers=HEADERS, verify=False
).json()["SearchResult"]["resources"]
 
# Find the "Employee" group
employee_group = next(
    (g for g in groups if g["name"] == "Employee"), None
)
 
# ── Create the user ──────────────────────────────────────────
user_payload = {
    "InternalUser": {
        "name": "jsmith",
        "description": "John Smith - Engineering",
        "enabled": True,
        "email": "[email protected]",
        "password": "TempP@ss123!",
        "firstName": "John",
        "lastName": "Smith",
        "changePassword": True,
        "identityGroups": employee_group["id"] if employee_group else "",
        "passwordNeverExpires": False,
        "daysForPasswordExpiration": 90,
        "passwordIDStore": "Internal Users"
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/internaluser",
    auth=AUTH,
    headers=HEADERS,
    json=user_payload,
    verify=False
)
response.raise_for_status()   # 201 Created
print(f"User created: {response.headers.get('Location')}")

Update and Delete Users

# ── Update (PUT — full object required) ──────────────────────
user_payload["InternalUser"]["id"] = user_id
user_payload["InternalUser"]["description"] = "Updated role"
 
requests.put(
    f"{BASE_URL}/ers/config/internaluser/{user_id}",
    auth=AUTH, headers=HEADERS,
    json=user_payload, verify=False
)
 
# ── Delete ───────────────────────────────────────────────────
requests.delete(
    f"{BASE_URL}/ers/config/internaluser/{user_id}",
    auth=AUTH, headers=HEADERS, verify=False
)

Internal Users with SDK

# Get all
users = api.internal_user.get_all().response.SearchResult.resources
 
# Create
api.internal_user.create_internal_user(
    name="jdoe",
    password="Temp123!",
    enabled=True,
    change_password=True,
    email="[email protected]"
)
 
# Delete
api.internal_user.delete_by_id(id=user_id)

7 – Endpoint and Endpoint Group Management

Exam Topics: 3.2, 3.5

Endpoints are the devices (laptops, phones, IoT) that connect to the network. Endpoint groups categorize them for policy decisions.

Get All Endpoints

response = requests.get(
    f"{BASE_URL}/ers/config/endpoint",
    auth=AUTH,
    headers=HEADERS,
    params={"size": 100, "page": 1},
    verify=False
)
endpoints = response.json()["SearchResult"]["resources"]
 
for ep in endpoints:
    print(f"  {ep['name']} — ID: {ep['id']}")

Get Endpoint by MAC Address

# ── Filter endpoint by MAC address ───────────────────────────
response = requests.get(
    f"{BASE_URL}/ers/config/endpoint",
    auth=AUTH,
    headers=HEADERS,
    params={"filter": "mac.EQ.AA:BB:CC:DD:EE:FF"},
    verify=False
)

Create an Endpoint

endpoint_payload = {
    "ERSEndPoint": {
        "name": "LAPTOP-JSMITH",
        "description": "John Smith's corporate laptop",
        "mac": "AA:BB:CC:DD:EE:FF",
        "groupId": "<endpoint-group-uuid>",
        "staticGroupAssignment": True,
        "staticProfileAssignment": False
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/endpoint",
    auth=AUTH,
    headers=HEADERS,
    json=endpoint_payload,
    verify=False
)
response.raise_for_status()   # 201 Created

Get Endpoint Identity Groups

# ── List all endpoint groups ──────────────────────────────────
response = requests.get(
    f"{BASE_URL}/ers/config/endpointgroup",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
groups = response.json()["SearchResult"]["resources"]
 
for group in groups:
    print(f"  {group['name']} — ID: {group['id']}")

Create an Endpoint Group

group_payload = {
    "EndPointGroup": {
        "name": "IoT-Sensors",
        "description": "All IoT sensor devices"
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/endpointgroup",
    auth=AUTH,
    headers=HEADERS,
    json=group_payload,
    verify=False
)
response.raise_for_status()   # 201 Created

Endpoints with SDK

# Get all endpoints
endpoints = api.endpoint.get_all().response.SearchResult.resources
 
# Get by ID
ep = api.endpoint.get_by_id(id=endpoint_id).response.ERSEndPoint
 
# Create endpoint
api.endpoint.create_endpoint(
    name="LAPTOP-JDOE",
    mac="11:22:33:44:55:66",
    group_id="<group-uuid>",
    static_group_assignment=True
)
 
# Get endpoint groups
groups = api.endpoint_identity_group.get_all().response.SearchResult.resources

8 – Day-0 Network Device Onboarding

Exam Topic: 3.1 – Construct a controller-based network automation solution for Day-0 provisioning

In ISE context, Day-0 means registering new NADs (Network Access Devices) so they can send RADIUS/TACACS+ requests to ISE for authentication.

ISE Day-0 Flow

┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  New Switch  │     │  ISE API     │     │  ISE adds    │     │  Switch can  │
│  deployed    │     │  POST /ers/  │     │  NAD with    │     │  send RADIUS │
│  (needs      │────►│  config/     │────►│  shared      │────►│  to ISE for  │
│  RADIUS)     │     │  network-    │     │  secret +    │     │  802.1X/MAB  │
│              │     │  device      │     │  device      │     │              │
│              │     │              │     │  groups      │     │              │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
    Step 1               Step 2               Step 3               Step 4
  (physical)           (API-driven)         (automatic)          (operational)

Bulk NAD Onboarding Script

import requests
from requests.auth import HTTPBasicAuth
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
BASE_URL = "https://ise.example.com"
AUTH = HTTPBasicAuth("ersadmin", "Cisco123!")
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json"
}
 
# ── Device inventory to onboard ──────────────────────────────
new_devices = [
    {
        "name": "Branch-SW-01",
        "ip": "10.10.50.1",
        "secret": "R@dius2024!",
        "location": "Location#All Locations#Branch-East",
        "device_type": "Device Type#All Device Types#Switches"
    },
    {
        "name": "Branch-SW-02",
        "ip": "10.10.50.2",
        "secret": "R@dius2024!",
        "location": "Location#All Locations#Branch-East",
        "device_type": "Device Type#All Device Types#Switches"
    },
    {
        "name": "Branch-WLC-01",
        "ip": "10.10.50.10",
        "secret": "R@dius2024!",
        "location": "Location#All Locations#Branch-East",
        "device_type": "Device Type#All Device Types#WLCs"
    }
]
 
# ── Onboard each device ──────────────────────────────────────
for device in new_devices:
    payload = {
        "NetworkDevice": {
            "name": device["name"],
            "description": f"Auto-onboarded: {device['name']}",
            "authenticationSettings": {
                "radiusSharedSecret": device["secret"],
                "enableKeyWrap": False,
                "dtlsRequired": False,
                "keyInputFormat": "ASCII"
            },
            "profileName": "Cisco",
            "coaPort": 1700,
            "NetworkDeviceIPList": [
                {"ipaddress": device["ip"], "mask": 32}
            ],
            "NetworkDeviceGroupList": [
                device["location"],
                device["device_type"]
            ]
        }
    }
 
    response = requests.post(
        f"{BASE_URL}/ers/config/networkdevice",
        auth=AUTH,
        headers=HEADERS,
        json=payload,
        verify=False
    )
 
    if response.status_code == 201:
        print(f"  Onboarded: {device['name']} ({device['ip']})")
    else:
        print(f"  FAILED: {device['name']}{response.status_code}: "
              f"{response.text[:100]}")

ISE Day-0 vs Other Controllers

AspectISECatalyst CenterMerakiSD-WAN
What’s onboardedNADs (switches, WLCs)Managed devicesMeraki hardwareWAN edges
MethodPOST NAD with shared secretPnP (DHCP+template)Claim serialvBond + template
ResultNAD can send RADIUS to ISEDevice fully configuredDevice gets configDevice joins fabric
Synchronous?Yes — immediateNo — task pollingYes — immediateNo — action polling

9 – TrustSec and Security Group Tags (SGTs)

Exam Topic: 3.5 – Construct security automation solutions such as policy enforcement, compliance monitoring, and network segmentation

TrustSec is Cisco’s software-defined segmentation technology. ISE assigns SGTs to users/devices at authentication time, and network devices enforce policy based on SGT, not IP/VLAN.

How TrustSec Works

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  User        │     │  ISE assigns │     │  Network     │
│  authenticates────►│  SGT based   │────►│  enforces    │
│  via 802.1X  │     │  on policy   │     │  SGACL       │
│  or MAB      │     │  (SGT=10)    │     │  (src→dst)   │
└──────────────┘     └──────────────┘     └──────────────┘

SGT = Security Group Tag (16-bit number)
SGACL = Security Group ACL (defines what source SGT can access on destination SGT)

TrustSec Terminology for the Exam

TermMeaningExample
SGTSecurity Group Tag — identity labelEmployees (SGT=4)
SGACLAccess control list between SGTsEmployees → Servers: permit
SXPSGT Exchange Protocol — propagates SGT-IP mappingsBetween ISE and legacy switches
Inline TaggingSGT embedded in Ethernet frame (CMD)Modern switches with TrustSec HW
SGT MatrixPolicy matrix showing src→dst SGT permissionsThe “big picture” of segmentation

Get All Security Groups (SGTs)

response = requests.get(
    f"{BASE_URL}/ers/config/sgt",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
sgts = response.json()["SearchResult"]["resources"]
 
for sgt in sgts:
    print(f"  {sgt['name']} — ID: {sgt['id']}")

Get SGT Details by ID

sgt_id = "abc-123-def-456"
 
response = requests.get(
    f"{BASE_URL}/ers/config/sgt/{sgt_id}",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
sgt = response.json()["Sgt"]
 
print(f"Name: {sgt['name']}")
print(f"Value: {sgt['value']}")          # The numeric SGT tag
print(f"Description: {sgt.get('description', 'N/A')}")
print(f"Generation ID: {sgt.get('generationId', 'N/A')}")

Create a Security Group (SGT)

sgt_payload = {
    "Sgt": {
        "name": "IoT_Devices",
        "description": "Security group for IoT sensors",
        "value": 50,
        "propogateToApic": False
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/sgt",
    auth=AUTH,
    headers=HEADERS,
    json=sgt_payload,
    verify=False
)
response.raise_for_status()   # 201 Created
print(f"SGT created: {response.headers.get('Location')}")

Get Security Group ACLs (SGACLs)

# ── List all SGACLs ──────────────────────────────────────────
response = requests.get(
    f"{BASE_URL}/ers/config/sgacl",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
sgacls = response.json()["SearchResult"]["resources"]
 
for acl in sgacls:
    print(f"  {acl['name']} — ID: {acl['id']}")

Create an SGACL

sgacl_payload = {
    "Sgacl": {
        "name": "Deny_IoT_to_Servers",
        "description": "Block IoT devices from server VLAN",
        "aclcontent": "deny ip"
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/sgacl",
    auth=AUTH,
    headers=HEADERS,
    json=sgacl_payload,
    verify=False
)
response.raise_for_status()

TrustSec with SDK

# ── Get all SGTs ─────────────────────────────────────────────
sgts = api.security_groups.get_all().response.SearchResult.resources
 
# ── Create SGT ───────────────────────────────────────────────
api.security_groups.create_security_group(
    name="Contractors",
    description="External contractor devices",
    value=60,
    propogate_to_apic=False
)
 
# ── Get SGACLs ───────────────────────────────────────────────
sgacls = api.security_groups_acls.get_all().response.SearchResult.resources

TrustSec = Network Segmentation for Exam Topic 3.5

When the exam asks about “network segmentation” in the ISE context, they mean TrustSec with SGTs. The key concept is that segmentation is based on identity (who you are), not topology (where you are). This decouples security policy from IP addressing and VLANs.


10 – Authorization Profiles and Policy Management

Exam Topics: 3.5, 3.2

Authorization profiles define what access a user/device gets. Policies tie conditions to profiles.

Get Authorization Profiles

response = requests.get(
    f"{BASE_URL}/ers/config/authorizationprofile",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
profiles = response.json()["SearchResult"]["resources"]
 
for profile in profiles:
    print(f"  {profile['name']} — ID: {profile['id']}")

Get Profile Details

profile_id = "abc-123"
 
response = requests.get(
    f"{BASE_URL}/ers/config/authorizationprofile/{profile_id}",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
profile = response.json()["AuthorizationProfile"]
 
print(f"Name: {profile['name']}")
print(f"Access Type: {profile.get('accessType', 'N/A')}")
print(f"VLAN: {profile.get('vlan', {}).get('nameID', 'N/A')}")
print(f"ACL: {profile.get('dacl', 'N/A')}")

Create an Authorization Profile

authz_profile_payload = {
    "AuthorizationProfile": {
        "name": "Employee_Wired_Access",
        "description": "Standard employee wired access",
        "accessType": "ACCESS_ACCEPT",
        "vlan": {
            "nameID": "VLAN_100",
            "tagID": 1
        },
        "reauth": {
            "timer": 1800,
            "connectivity": "DEFAULT"
        }
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/authorizationprofile",
    auth=AUTH,
    headers=HEADERS,
    json=authz_profile_payload,
    verify=False
)
response.raise_for_status()

Get Downloadable ACLs (DACLs)

# ── DACLs are pushed to NADs during authorization ────────────
response = requests.get(
    f"{BASE_URL}/ers/config/downloadableacl",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
dacls = response.json()["SearchResult"]["resources"]

Create a Downloadable ACL

dacl_payload = {
    "DownloadableAcl": {
        "name": "PERMIT_ALL_TRAFFIC",
        "description": "Allow all traffic for trusted devices",
        "dacl": "permit ip any any",
        "daclType": "IPV4"
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/downloadableacl",
    auth=AUTH,
    headers=HEADERS,
    json=dacl_payload,
    verify=False
)

Network Access Policy Sets (Open API)

# ── Policy sets use the Open API, not ERS ─────────────────────
# These map to Policy > Policy Sets in the ISE GUI
 
# Get all policy sets
response = requests.get(
    f"{BASE_URL}/api/v1/policy/network-access/policy-set",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
policy_sets = response.json()["response"]
 
for ps in policy_sets:
    print(f"  {ps['name']} — ID: {ps['id']}")
    print(f"    State: {ps.get('state', 'N/A')}")
    print(f"    Default: {ps.get('default', False)}")

Authorization Profiles with SDK

# Get profiles
profiles = api.authorization_profile.get_all().response.SearchResult.resources
 
# Get policy sets
policy_sets = api.network_access_policy_set.get_all().response.response
 
# Access policy set details with dot notation
for ps in policy_sets:
    print(f"  {ps.name}: condition={ps.condition.conditionType}")

11 – Guest User Management

Exam Topic: 3.2

ISE can manage guest WiFi accounts programmatically — create temporary credentials, set expiry, and assign to guest types.

Get Guest Users

response = requests.get(
    f"{BASE_URL}/ers/config/guestuser",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
guests = response.json()["SearchResult"]["resources"]

Create a Guest User

guest_payload = {
    "GuestUser": {
        "name": "guest_visitor01",
        "guestType": "Weekly (default)",
        "guestInfo": {
            "userName": "visitor01",
            "password": "Welcome123!",
            "firstName": "Jane",
            "lastName": "Visitor",
            "emailAddress": "[email protected]",
            "company": "PartnerCorp"
        },
        "guestAccessInfo": {
            "validDays": 7,
            "location": "San Jose"
        },
        "portalId": "<sponsor-portal-id>"
    }
}
 
response = requests.post(
    f"{BASE_URL}/ers/config/guestuser",
    auth=AUTH,
    headers=HEADERS,
    json=guest_payload,
    verify=False
)
response.raise_for_status()
print("Guest account created")

Guest API Requires Sponsor Privileges

The ISE user calling the Guest API must be in a sponsor group with REST API access enabled. A regular ERS-Admin account may not have guest management rights. This is configured under Work Centers → Guest Access → Sponsor Groups.


12 – Monitoring and Health

Exam Topic: 4.4

Get Active Sessions (MnT)

# ── Active RADIUS sessions from the Monitoring node ──────────
response = requests.get(
    f"{BASE_URL}/admin/API/mnt/Session/ActiveList",
    auth=AUTH,
    headers={"Accept": "application/json"},
    verify=False
)

ANC (Adaptive Network Control) — Quarantine/Unquarantine

# ── Get ANC policies ─────────────────────────────────────────
response = requests.get(
    f"{BASE_URL}/ers/config/ancpolicy",
    auth=AUTH,
    headers=HEADERS,
    verify=False
)
anc_policies = response.json()["SearchResult"]["resources"]
 
for policy in anc_policies:
    print(f"  {policy['name']} — ID: {policy['id']}")
# ── Apply ANC policy to an endpoint (quarantine) ─────────────
anc_apply_payload = {
    "OperationAdditionalData": {
        "additionalData": [
            {"name": "macAddress", "value": "AA:BB:CC:DD:EE:FF"},
            {"name": "policyName", "value": "ANC_Quarantine"}
        ]
    }
}
 
response = requests.put(
    f"{BASE_URL}/ers/config/ancendpoint/apply",
    auth=AUTH,
    headers=HEADERS,
    json=anc_apply_payload,
    verify=False
)
response.raise_for_status()
print("Endpoint quarantined via ANC")
# ── Clear ANC policy (unquarantine) ──────────────────────────
anc_clear_payload = {
    "OperationAdditionalData": {
        "additionalData": [
            {"name": "macAddress", "value": "AA:BB:CC:DD:EE:FF"},
            {"name": "policyName", "value": "ANC_Quarantine"}
        ]
    }
}
 
response = requests.put(
    f"{BASE_URL}/ers/config/ancendpoint/clear",
    auth=AUTH,
    headers=HEADERS,
    json=anc_clear_payload,
    verify=False
)

ANC for Compliance Monitoring (Exam Topic 3.5)

ANC is how ISE implements automated compliance enforcement:

  • Detect a compromised or non-compliant endpoint
  • Apply ANC_Quarantine policy via API → triggers CoA (Change of Authorization)
  • Endpoint is moved to quarantine VLAN immediately
  • After remediation, clear the ANC policy → endpoint regains access

This is the “compliance monitoring” aspect of exam topic 3.5.

System Health with SDK

# System health
health = api.system_health.get_all()
 
# Version info
version = api.version_info.get_version_info()

13 – CSRF Token Handling

Exam Topic: 3.6

ISE supports optional CSRF protection for ERS API. When enabled, write operations require an X-CSRF-Token.

How CSRF Works in ISE

Step 1: GET any ERS endpoint with header "X-CSRF-Token: fetch"
        → ISE returns the CSRF token in the X-CSRF-Token response header

Step 2: Include that token in POST/PUT/DELETE requests
        → Header: "X-CSRF-Token: <token-value>"

Token lifetime: Valid for 60 seconds of session idle time

CSRF Example

# ── Step 1: Fetch the CSRF token ─────────────────────────────
csrf_response = requests.get(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH,
    headers={
        **HEADERS,
        "X-CSRF-Token": "fetch"       # Magic value to request token
    },
    verify=False
)
 
csrf_token = csrf_response.headers.get("X-CSRF-Token")
print(f"CSRF Token: {csrf_token}")
 
# ── Step 2: Use token in write operations ─────────────────────
response = requests.post(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH,
    headers={
        **HEADERS,
        "X-CSRF-Token": csrf_token     # Include token
    },
    json=nad_payload,
    verify=False
)

CSRF Is Optional — But Know It for the Exam

CSRF protection can be enabled/disabled in ISE at Administration → Settings → API Settings. When disabled (the default on many lab setups), you don’t need the X-CSRF-Token header. The SDK handles CSRF automatically when uses_csrf_token=True.


14 – Common Patterns and Gotchas

Pattern: Complete Workflow (Auth → Get Groups → Create User → Verify)

import requests
from requests.auth import HTTPBasicAuth
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
BASE_URL = "https://ise.example.com"
AUTH = HTTPBasicAuth("ersadmin", "Cisco123!")
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json"
}
 
# ── 1. Verify connectivity — get ISE version ────────────────
version = requests.get(
    f"{BASE_URL}/ers/config/op/systemconfig/iseversion",
    auth=AUTH, headers=HEADERS, verify=False
).json()
print(f"ISE Version: {version}")
 
# ── 2. Get identity groups ───────────────────────────────────
groups = requests.get(
    f"{BASE_URL}/ers/config/identitygroup",
    auth=AUTH, headers=HEADERS, verify=False
).json()["SearchResult"]["resources"]
print(f"\nIdentity Groups: {len(groups)}")
for g in groups:
    print(f"  {g['name']}")
 
# ── 3. Get network devices ──────────────────────────────────
devices = requests.get(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=AUTH, headers=HEADERS, verify=False
).json()["SearchResult"]["resources"]
print(f"\nNetwork Devices: {len(devices)}")
 
# ── 4. Get SGTs ──────────────────────────────────────────────
sgts = requests.get(
    f"{BASE_URL}/ers/config/sgt",
    auth=AUTH, headers=HEADERS, verify=False
).json()["SearchResult"]["resources"]
print(f"\nSecurity Groups: {len(sgts)}")
 
# ── 5. Create a new internal user ────────────────────────────
employee_group = next(
    (g for g in groups if g["name"] == "Employee"), None
)
 
if employee_group:
    user_resp = requests.post(
        f"{BASE_URL}/ers/config/internaluser",
        auth=AUTH, headers=HEADERS,
        json={
            "InternalUser": {
                "name": "automation_test_user",
                "password": "Test123!",
                "enabled": True,
                "changePassword": True,
                "identityGroups": employee_group["id"]
            }
        },
        verify=False
    )
    if user_resp.status_code == 201:
        print(f"\nUser created: {user_resp.headers['Location']}")
    else:
        print(f"\nUser creation failed: {user_resp.status_code}")
 
# ── 6. Verify — get the user back ───────────────────────────
verify = requests.get(
    f"{BASE_URL}/ers/config/internaluser",
    auth=AUTH, headers=HEADERS,
    params={"filter": "name.EQ.automation_test_user"},
    verify=False
).json()["SearchResult"]
print(f"Verified: found {verify['total']} matching user(s)")

Pagination Pattern

def get_all_ers_resources(base_url, resource_path, auth, headers):
    """Retrieve all resources from an ERS endpoint with pagination."""
    all_resources = []
    page = 1
    size = 100
 
    while True:
        response = requests.get(
            f"{base_url}/ers/config/{resource_path}",
            auth=auth,
            headers=headers,
            params={"page": page, "size": size},
            verify=False
        )
        response.raise_for_status()
        result = response.json()["SearchResult"]
        resources = result["resources"]
 
        if not resources:
            break
 
        all_resources.extend(resources)
 
        # Check if there are more pages
        if "nextPage" not in result:
            break
 
        page += 1
 
    return all_resources
 
# Usage
all_devices = get_all_ers_resources(BASE_URL, "networkdevice", AUTH, HEADERS)
all_users = get_all_ers_resources(BASE_URL, "internaluser", AUTH, HEADERS)

Gotchas for the Exam

Top ISE Exam Pitfalls

1. Accept: application/json is mandatory for JSON responses ISE defaults to XML. Without the Accept header, response.json() throws a parse error. Every exam code snippet should include both Content-Type and Accept headers.

2. Basic Auth on EVERY request — no token, no session ISE is the only controller that never exchanges credentials for a token or session. Every single API call includes the full Authorization: Basic header.

3. ERS API is disabled by default You must enable it in Administration → Settings → API Settings. After ISE upgrades, it may be disabled again. “Connection refused” on port 443/9060 → check ERS status.

4. POST returns 201 Created with an empty body The new resource ID is in the Location response header, not in the body. If you try response.json() after a POST, it fails.

5. PUT requires the FULL object, not a partial update Unlike Meraki, ISE PUT replaces the entire resource. Omitting a field resets it. Always GET first, modify, then PUT.

6. Response wrapper: SearchResult.resources (list) or ResourceName (single)

  • GET all: response.json()["SearchResult"]["resources"]
  • GET one: response.json()["NetworkDevice"] (or InternalUser, Sgt, etc.)
  • The resource key name matches the object type — know the exact keys.

7. ERS-Admin vs ERS-Operator roles ERS-Operator can only GET. POST/PUT/DELETE require ERS-Admin. Wrong role → 403 Forbidden.

8. Filter syntax uses dots: name.EQ.value Not query parameters like ?name=value. The filter format is ?filter=field.OPERATOR.value.


15 – Exam-Style Scenarios

Scenario 1: XML Response Error (Topic 3.6)

Your script throws a json.decoder.JSONDecodeError on this line. The status code is 200. What's wrong?

response = requests.get(
    f"{BASE_URL}/ers/config/networkdevice",
    auth=HTTPBasicAuth("admin", "pass"),
    headers={"Content-Type": "application/json"},
    verify=False
)
devices = response.json()["SearchResult"]["resources"]

Scenario 2: POST Returns Empty Response (Topic 3.6)

You create a network device and the response status is 201. But response.json() throws an error. How do you get the new device's ID?


Scenario 3: 403 on Device Creation (Topic 3.6)

You can successfully GET network devices, but POST to create a new one returns 403 Forbidden. Credentials are correct. What's the issue?


Scenario 4: Understanding SearchResult (Topic 3.2)

Fill in the blanks to print all internal user names:

response = requests.get(
    f"{BASE_URL}/ers/config/internaluser",
    auth=AUTH, headers=HEADERS, verify=False
)
users = response.json()["______"]["______"]
for user in users:
    print(user["name"])

Scenario 5: Auth Comparison (Topic 3.6)

Match each authentication method to the correct controller:

A. X-Auth-Token header          1. ISE
B. JSESSIONID cookie            2. Catalyst Center
C. X-Cisco-Meraki-API-Key       3. SD-WAN (vManage)
D. Authorization: Basic (every) 4. Meraki

Scenario 6: TrustSec Segmentation (Topic 3.5)

You need to prevent IoT devices from reaching the server VLAN, but allow employee laptops full access. Both connect to the same switches. How do you implement this with ISE APIs without changing VLANs or ACLs on switches?


Scenario 7: ANC Quarantine (Topic 3.5)

Your security monitoring system detects malware on endpoint AA:BB:CC:DD:EE:FF. Write the API call to immediately quarantine this device via ISE.


Scenario 8: SDK Constructor (Topic 3.2)

Fill in the blanks to initialize the ISE SDK:

from ciscoisesdk import _______________
 
api = _______________(
    username="admin",
    password="Cisco123!",
    base_url="https://ise.example.com",
    verify=False
)

Scenario 9: CSRF Token Failure (Topic 3.6)

Your ISE has CSRF protection enabled. Your GET requests work, but all POST requests return 403 Forbidden with message "CSRF token validation failed". What's missing?


16 – Quick Reference Cheat Sheet

Authentication

Every request needs:
  Header: Authorization: Basic base64(username:password)
  Header: Content-Type: application/json
  Header: Accept: application/json    ← CRITICAL (ISE defaults to XML!)
  Base URL: https://<ise-ip>/ers/config/...
  No token exchange. No session. Every request stands alone.

ERS API CRUD Operations Map

OperationMethodEndpointReturns
Network Devices
List allGET/ers/config/networkdeviceSearchResult
Get by IDGET/ers/config/networkdevice/{id}NetworkDevice
CreatePOST/ers/config/networkdevice201 + Location
UpdatePUT/ers/config/networkdevice/{id}200
DeleteDELETE/ers/config/networkdevice/{id}204
Internal Users
List allGET/ers/config/internaluserSearchResult
Get by IDGET/ers/config/internaluser/{id}InternalUser
CreatePOST/ers/config/internaluser201 + Location
UpdatePUT/ers/config/internaluser/{id}200
DeleteDELETE/ers/config/internaluser/{id}204
Endpoints
List allGET/ers/config/endpointSearchResult
Get by IDGET/ers/config/endpoint/{id}ERSEndPoint
CreatePOST/ers/config/endpoint201 + Location
Endpoint Groups
List allGET/ers/config/endpointgroupSearchResult
CreatePOST/ers/config/endpointgroup201 + Location
Identity Groups
List allGET/ers/config/identitygroupSearchResult
Get by IDGET/ers/config/identitygroup/{id}IdentityGroup
TrustSec (SGTs)
List SGTsGET/ers/config/sgtSearchResult
Get SGTGET/ers/config/sgt/{id}Sgt
Create SGTPOST/ers/config/sgt201 + Location
List SGACLsGET/ers/config/sgaclSearchResult
Create SGACLPOST/ers/config/sgacl201 + Location
Authorization
Auth ProfilesGET/ers/config/authorizationprofileSearchResult
DACLsGET/ers/config/downloadableaclSearchResult
ANC
ANC PoliciesGET/ers/config/ancpolicySearchResult
Apply ANCPUT/ers/config/ancendpoint/apply204
Clear ANCPUT/ers/config/ancendpoint/clear204
Guest Users
List guestsGET/ers/config/guestuserSearchResult
Create guestPOST/ers/config/guestuser201 + Location
System
ISE VersionGET/ers/config/op/systemconfig/iseversionVersion info
CSRF TokenGETAny endpoint + X-CSRF-Token: fetchToken in header

Open API Endpoints

OperationMethodEndpoint
Policy SetsGET/api/v1/policy/network-access/policy-set
AuthN RulesGET/api/v1/policy/network-access/policy-set/{id}/authentication
AuthZ RulesGET/api/v1/policy/network-access/policy-set/{id}/authorization

SDK Quick Reference

from ciscoisesdk import IdentityServicesEngineAPI
 
api = IdentityServicesEngineAPI(
    username=u, password=p, base_url=url, verify=False
)
 
# Network devices
api.network_device.get_all()
api.network_device.get_by_id(id="uuid")
api.network_device.get_by_name(name="Switch-01")
api.network_device.create_network_device(name=..., network_device_iplist=[...])
api.network_device.delete_by_id(id="uuid")
 
# Internal users
api.internal_user.get_all()
api.internal_user.create_internal_user(name=..., password=...)
api.internal_user.delete_by_id(id="uuid")
 
# Endpoints
api.endpoint.get_all()
api.endpoint.create_endpoint(name=..., mac=...)
 
# TrustSec
api.security_groups.get_all()                     # SGTs
api.security_groups.create_security_group(name=..., value=50)
api.security_groups_acls.get_all()                # SGACLs
 
# Authorization
api.authorization_profile.get_all()
api.downloadable_acl.get_all()
 
# ANC (quarantine)
api.anc_policy.get_all()
api.anc_endpoint.apply_anc_endpoint(additional_data=[...])
api.anc_endpoint.clear_anc_endpoint(additional_data=[...])
 
# Identity groups
api.identity_groups.get_all()
api.endpoint_identity_group.get_all()
 
# Policy sets (Open API)
api.network_access_policy_set.get_all()

ERS Response Structure Quick Reference

# ── LIST (GET all) ────────────────────────────────────────────
response.json() = {
    "SearchResult": {
        "total": 42,
        "resources": [
            {"id": "uuid-1", "name": "Device-1", "link": {...}},
            {"id": "uuid-2", "name": "Device-2", "link": {...}}
        ],
        "nextPage": {"href": "...", "rel": "next", "type": "application/json"}
    }
}
 
# ── SINGLE (GET by ID) ───────────────────────────────────────
response.json() = {
    "NetworkDevice": {          # Key = resource type name
        "id": "uuid-1",
        "name": "Device-1",
        "description": "...",
        ...
    }
}
 
# Resource type keys:
# NetworkDevice, InternalUser, ERSEndPoint, EndPointGroup,
# IdentityGroup, Sgt, Sgacl, AuthorizationProfile,
# DownloadableAcl, GuestUser, ANCPolicy

Compare: All Four Controllers at a Glance

ConceptISECatalyst CenterMerakiSD-WAN
AuthBasic Auth (every call)Basic → Token → X-Auth-TokenAPI Key headerSession cookie + XSRF
Auth headerAuthorization: BasicX-Auth-TokenX-Cisco-Meraki-API-KeyCookie: JSESSIONID
Session needed?NoNoNoYes
Write opsSynchronousAsync (taskId)SynchronousAsync (action ID)
Response keySearchResult.resourcesresponseDirect (no wrapper)data
Default formatXML (must request JSON)JSONJSONJSON
Device ID typeUUIDUUIDSerial numbersystem-ip / UUID
API base path/ers/config//dna/intent/api/v1//api/v1//dataservice/
Enabled by defaultNo (must enable ERS)YesYesYes
SDK classIdentityServicesEngineAPICatalystCenterAPIDashboardAPIcreate_manager_session()

Memory Aid — Auth Comparison

ISE = Basic auth every time (simplest, most repetitive) Catalyst = Custom Token header (X-Auth-Token) Meraki = API Key header (simplest — no exchange) SD-WAN = Session cookie + XSRF token (most complex)


Further Study


enauto ccnp ise python api controller-based trustsec sgt security day0 ers

See Also