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
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.
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:
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 Code
Meaning
Likely Cause
401 Unauthorized
Bad credentials
Wrong username/password or user not in ERS group
403 Forbidden
Valid auth, no permission
ERS-Operator trying POST/PUT/DELETE
404 Not Found
Wrong endpoint path
Typo in /ers/config/...
406 Not Acceptable
Wrong Accept header
Missing Accept: application/json
415 Unsupported Media
Wrong Content-Type
Missing Content-Type: application/json
500 Internal Server Error
Malformed payload
JSON body has wrong structure/keys
SSL Error
Certificate issue
Missing 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 1devices = requests.get(f"{BASE_URL}/ers/config/networkdevice", auth=AUTH, headers=HEADERS, verify=False)# Request 2 — completely independent, same auth sent againusers = requests.get(f"{BASE_URL}/ers/config/internaluser", auth=AUTH, headers=HEADERS, verify=False)
Compare Auth Complexity Across Controllers
Controller
Auth Steps
Session Required?
ISE
0 — Basic Auth on every call
No
Meraki
0 — API Key on every call
No
Catalyst Center
1 — POST for token, then use header
No (token in header)
SD-WAN
2 — POST for cookie + GET for XSRF token
Yes (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)# - Paginationapi = 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.resourcesfor device in devices: print(f" {device.name} (ID: {device.id})")
Environment Variable Configuration
# Set these instead of passing to constructorexport 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 argumentsfrom ciscoisesdk import IdentityServicesEngineAPIapi = IdentityServicesEngineAPI()
SDK vs requests — When to Use What
Aspect
requests
ciscoisesdk
Auth handling
Manual (HTTPBasicAuth every call)
Automatic
Response format
Must set Accept header + parse JSON
Dot-notation objects
Pagination
Manual (page/size params)
Built-in iterators
CSRF tokens
Manual GET + header
Automatic (if enabled)
Error handling
Parse status codes
Raises ApiError exceptions
Exam relevance
High — must understand raw flow
High — 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.
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.
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.
# ── 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']}")
# Get all endpointsendpoints = api.endpoint.get_all().response.SearchResult.resources# Get by IDep = api.endpoint.get_by_id(id=endpoint_id).response.ERSEndPoint# Create endpointapi.endpoint.create_endpoint( name="LAPTOP-JDOE", mac="11:22:33:44:55:66", group_id="<group-uuid>", static_group_assignment=True)# Get endpoint groupsgroups = 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)
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)
# ── 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.
# ── 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 setsresponse = 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 profilesprofiles = api.authorization_profile.get_all().response.SearchResult.resources# Get policy setspolicy_sets = api.network_access_policy_set.get_all().response.response# Access policy set details with dot notationfor 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.
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 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 healthhealth = api.system_health.get_all()# Version infoversion = 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 requestsfrom requests.auth import HTTPBasicAuthimport urllib3urllib3.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# Usageall_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?
You can successfully GET network devices, but POST to create a new one returns 403 Forbidden. Credentials are correct. What's the issue?
Answer
The ISE user is assigned to the ERS-Operator group (read-only). Creating resources requires the ERS-Admin group. Change the user’s group assignment in Administration → Admin Access → Administrators → Admin Groups.
ERS list endpoints always return {"SearchResult": {"total": N, "resources": [...]}}
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
Answer
A → 2 (Catalyst Center uses X-Auth-Token)
B → 3 (SD-WAN uses JSESSIONID session cookie)
C → 4 (Meraki uses X-Cisco-Meraki-API-Key)
D → 1 (ISE uses Basic Auth on every request)
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?
Answer
Use TrustSec with SGTs:
Create SGTs: POST /ers/config/sgt — create “IoT_Devices” (value=50) and “Employees” (value=4)
Create SGACL: POST /ers/config/sgacl — create “Deny_IoT_to_Servers” with deny ip
Create an authorization policy that assigns SGT=50 to IoT devices and SGT=4 to employees
Configure the SGT matrix (egress policy) to apply the SGACL between source SGT=50 and destination SGT
This enforces segmentation based on identity, not network topology. No switch ACL or VLAN changes needed.
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.
ISE sends a CoA (Change of Authorization) to the NAD, which moves the endpoint to the quarantine VLAN immediately.
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)
Answer
from ciscoisesdk import IdentityServicesEngineAPIapi = IdentityServicesEngineAPI( 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?
The token is fetched by adding X-CSRF-Token: fetch to any GET request, then using the returned value in subsequent writes. Token expires after 60 seconds of idle time.
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.