ISE ERS — Endpoint Management
Full endpoint lifecycle via the ISE ERS API — read, update (PUT), delete, create (POST), and batch operations. Tested against d000 lab ISE (2026-04-15).
Prerequisites
dsource d000 dev/network # d000 lab
# dsource d001 dev # d001 CHLA
# Verify (zsh — masks passwords)
for var in ISE_API_USER ISE_API_PASS ISE_PAN_FQDN ISE_ERS_PORT ISE_CA_CERT; do
if [[ -v "$var" ]]; then
val="${(P)var}"
[[ "$var" == *PASS* ]] && printf "%-20s %s***\n" "$var" "${val:0:3}" || printf "%-20s %s\n" "$var" "$val"
else
printf "%-20s MISSING\n" "$var"
fi
done
ERSEndPoint Object
Every ISE endpoint has this structure. All fields shown via:
# Dump the full ERSEndPoint object — shows every available field
curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint/${eid}" \
| jq -C '.ERSEndPoint'
Field Reference
| Field | Purpose | Writable |
|---|---|---|
|
Endpoint UUID — required in PUT body and URL |
Read-only |
|
Defaults to MAC address |
Yes |
|
MAC address (primary key) |
Yes (POST only) |
|
Audit trail: |
Yes |
|
Identity group UUID — determines authz policy match |
Yes |
|
|
Yes |
|
Profiler policy UUID |
Yes |
|
|
Yes |
|
Identity store name (e.g. |
Yes |
|
Identity store UUID |
Yes |
|
Guest portal username |
Yes |
|
Key-value pairs ( |
Yes |
MFC Attributes (read-only — populated by profiler)
| Field | Description |
|---|---|
|
Profiler-detected device type |
|
Detected manufacturer |
|
Detected model |
|
Detected OS (may be empty) |
Read Operations
Resolve Endpoint by MAC
# Resolve endpoint ID from MAC address
MAC="14:F6:D8:7B:31:80"
endpoint_json=$(curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint?filter=mac.EQ.${MAC}")
eid=$(jq -r '.SearchResult.resources[0].id // empty' <<< "$endpoint_json")
echo "Endpoint ID: ${eid}"
Check Current Assignment
# Check current group assignment and description (single fetch, cached)
endpoint_detail=$(curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint/${eid}")
jq -C '{
mac: .ERSEndPoint.mac,
description: .ERSEndPoint.description,
staticGroupAssignment: .ERSEndPoint.staticGroupAssignment,
groupId: .ERSEndPoint.groupId,
profileId: .ERSEndPoint.profileId
}' <<< "$endpoint_detail"
List Identity Groups
# List all endpoint identity groups (fix column alignment for names with spaces)
curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpointgroup" \
| jq -r '.SearchResult.resources[] | "\(.name)\t\(.id)"' \
| column -t -s $'\t'
Resolve Group by Name
# Resolve group UUID by name
TARGET_GROUP="Linux-Workstations"
gid=$(curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpointgroup?filter=name.EQ.${TARGET_GROUP}" \
| jq -r '.SearchResult.resources[0].id // empty')
echo "Group ID: ${gid}"
Write Operations
PUT — Update In Place (preferred)
Updates group, description, and custom attributes without deleting. Preserves profiling history.
# Update endpoint in place — set static group, description, custom attributes
DESCRIPTION="$(date +%F) ER - reassigned via ERS API"
curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-X PUT \
-d '{
"ERSEndPoint": {
"id": "'"${eid}"'",
"mac": "'"${MAC}"'",
"description": "'"${DESCRIPTION}"'",
"staticGroupAssignment": true,
"groupId": "'"${gid}"'",
"customAttributes": {
"customAttributes": {
"OS": "Arch Linux",
"DevicePurpose": "Engineering Workstation"
}
}
}
}' \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint/${eid}" \
| jq -C '.UpdatedFieldsList'
PUT requires id in both the URL path and the JSON body. The response returns UpdatedFieldsList showing old → new values for each changed field.
|
DELETE — Remove Endpoint
# Delete endpoint — clears all state including profiling history
curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-X DELETE \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint/${eid}"
echo "Deleted endpoint ${MAC} (${eid})"
POST — Create Endpoint
# Create new endpoint with static group and description
curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"ERSEndPoint": {
"mac": "'"${MAC}"'",
"description": "'"${DESCRIPTION}"'",
"staticGroupAssignment": true,
"groupId": "'"${gid}"'"
}
}' \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint"
echo "Created ${MAC} with static group assignment"
Batch Operations
ise-batch-onboard.sh (recommended)
Standalone script for batch endpoint onboarding. Creates new endpoints (POST) or updates existing ones (PUT). Accepts any MAC format — raw, colon, or dash. Verify-before/apply/verify-after pattern.
ise-batch-onboard.sh <mac-file> <group-name> <description>
dsource d001 dev/network/ise
cat > /tmp/alaris-pumps.txt << 'EOF'
# BD Alaris — 2026-05-28
C0EE40F1A72F
C0EE40F1CDCD
C0EE40F202EA
EOF
ise-batch-onboard.sh /tmp/alaris-pumps.txt Medical_Onboard \
'2026-05-28 ER - BD Alaris pumps add to medical device onboard'
dsource d001 dev/network/ise
ise-batch-onboard.sh /tmp/check-macs.txt IoT_Onboard '2026-05-07 ER - VFD Johnson Controls IoT_Onboard'
dsource d000 dev/network/ise
ise-batch-onboard.sh /tmp/check-macs.txt GuestEndpoints 'test - batch onboard validation'
1. Normalizes MACs (raw/colon/dash → C0:EE:40:F1:A7:2F) 2. Resolves group name → UUID (no hardcoded IDs) 3. BEFORE: shows current state per MAC - NEW: ○ C0:EE:40:F1:A7:2F — NEW (will create) - EXISTS: ● C0:EE:40:F1:A7:2F — EXISTS group: ... desc: ... 4. Summary: N new + N update + N invalid 5. Prompts for confirmation [y/N] 6. APPLY: POST (create) or PUT (update) per device 7. AFTER: verifies group + description match target
-
MAC normalization — paste raw from Teams/email, no manual reformatting
-
POST for new + PUT for existing — one script handles both
-
Group name resolved dynamically — works across d000 lab and d001 production
-
JSON body built via
jq -n --arg— UUIDs never touch zsh variable evaluation -
PID-scoped temp files +
trap EXITcleanup -
HTTP status code checked — reports failures per MAC
-
Tested: d000 lab (2026-05-07) — 5 devices, assign + revert confirmed
For the complete end-to-end workflow (heredoc → script → DataConnect validation), see ISE ERS Bulk Onboard Workflow.
Batch Reassign (DELETE + POST)
# Batch delete-and-recreate with per-device descriptions
TARGET_GROUP="Linux-Workstations"
TODAY=$(date +%F)
gid=$(curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpointgroup?filter=name.EQ.${TARGET_GROUP}" \
| jq -r '.SearchResult.resources[0].id // empty')
declare -A ENDPOINTS=(
["14:F6:D8:7B:31:80"]="Razer workstation - EAP-TLS"
["98:BB:1E:1F:A7:13"]="Test device"
)
for MAC in "${!ENDPOINTS[@]}"; do
DESC="${TODAY} ER - ${ENDPOINTS[$MAC]}"
echo "=== ${MAC} ==="
eid=$(curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint?filter=mac.EQ.${MAC}" \
| jq -r '.SearchResult.resources[0].id // empty')
[[ -n "$eid" ]] && curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-X DELETE \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint/${eid}" \
&& echo " Deleted"
curl -sS \
--cacert "${ISE_CA_CERT}" \
-u "${ISE_API_USER}:${ISE_API_PASS}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"ERSEndPoint": {
"mac": "'"${MAC}"'",
"description": "'"${DESC}"'",
"staticGroupAssignment": true,
"groupId": "'"${gid}"'"
}
}' \
"https://${ISE_PAN_FQDN}:${ISE_ERS_PORT}/ers/config/endpoint" \
&& echo " Created → ${TARGET_GROUP}"
done
Uses bash declare -A (associative array). In zsh use typeset -A.
|
Description Standard
Pattern: YYYY-MM-DD <initials> - <comment>
2026-04-15 ER - test static group assignment via ERS API 2026-04-15 ER - reassigned to IoT_iPSK per Alex Mejia (INC-2026-04-15) 2026-04-15 ER - endpoint update using API call - Razer Arch Linux
jq Output Modes
| Flag | Use Case | Color |
|---|---|---|
|
Display to terminal |
Yes — forced color |
|
Display to terminal |
Auto (color if tty) |
|
Variable capture ( |
No — raw string, strips quotes |
|
Pipe to file or another command |
No — forced monochrome |
Gotchas
-
column -tbreaks on group names with spaces (e.g. "Blocked List") — usecolumn -t -s $'\t' -
staticGroupAssignment: truedoes NOT prevent runtime override by the profiler during auth events — see INC-2026-04-15 -
Custom attribute keys must be pre-defined in ISE (Administration → Identity Management → Settings → Endpoint Custom Attributes)
-
ERS returns XML by default — always set both
Accept: application/jsonANDContent-Type: application/json -
ISE_CA_CERTis constructed from${CA_CERT_PATH}/ise/ROOT-CA.crt— both d000 and d001 export this variable