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

Load credentials and verify
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 full endpoint object
# 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

id

Endpoint UUID — required in PUT body and URL

Read-only

name

Defaults to MAC address

Yes

mac

MAC address (primary key)

Yes (POST only)

description

Audit trail: YYYY-MM-DD initials - comment

Yes

groupId

Identity group UUID — determines authz policy match

Yes

staticGroupAssignment

true = admin-locked, false = profiler-assigned

Yes

profileId

Profiler policy UUID

Yes

staticProfileAssignment

true = lock profiler policy

Yes

identityStore

Identity store name (e.g. ipsk-mgr-01)

Yes

identityStoreId

Identity store UUID

Yes

portalUser

Guest portal username

Yes

customAttributes

Key-value pairs (OS, DevicePurpose, custom)

Yes

MFC Attributes (read-only — populated by profiler)

Field Description

mfcDeviceType

Profiler-detected device type

mfcHardwareManufacturer

Detected manufacturer

mfcHardwareModel

Detected model

mfcOperatingSystem

Detected OS (may be empty)

Read Operations

Resolve Endpoint by MAC

Get endpoint ID from MAC address
# 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

Fetch and display group, description, static flag (cached response)
# 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

All endpoint groups with UUIDs (handles names with spaces)
# 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

Get group UUID from group 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 with static group assignment and description
# 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)
# 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
# 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

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.

Usage
ise-batch-onboard.sh <mac-file> <group-name> <description>
Example — BD Alaris pumps to Medical_Onboard
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'
Example — BMS VFDs to IoT_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'
Example — lab testing
dsource d000 dev/network/ise
ise-batch-onboard.sh /tmp/check-macs.txt GuestEndpoints 'test - batch onboard validation'
What it does
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
Key design decisions
  • 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 EXIT cleanup

  • 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)

Delete and recreate multiple endpoints with per-device descriptions
# 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>

Examples
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

jq -C

Display to terminal

Yes — forced color

jq (no flag)

Display to terminal

Auto (color if tty)

jq -r

Variable capture (eid=$(…​))

No — raw string, strips quotes

jq -M

Pipe to file or another command

No — forced monochrome

Gotchas

  • column -t breaks on group names with spaces (e.g. "Blocked List") — use column -t -s $'\t'

  • staticGroupAssignment: true does 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/json AND Content-Type: application/json

  • ISE_CA_CERT is constructed from ${CA_CERT_PATH}/ise/ROOT-CA.crt — both d000 and d001 export this variable