Skip to main content
The Ops Playbook

Fixing Stalled Automated Device Enrollment Loops for Good

Stop firefighting stuck Mac enrollments manually — detect and auto-remediate ADE and ABM assignment failures before they ever become a ticket.

Fixing Stalled Automated Device Enrollment Loops for Good
Isla MorganIsla Morgan10 min readTier L235 min

This playbook covers

Share

#The Old Way vs The New Way

The old way of handling a stuck Automated Device Enrollment (ADE) session looks like this: a new starter’s Mac hangs at the Remote Management screen, the technician gets pulled onto a call, they ask the user to reboot, they check Apple Business Manager to confirm the device is assigned, they check the MDM server logs, they maybe re-erase the Mac with Apple Configurator, and forty-five minutes later the machine finally enrols. Multiply that by every onboarding wave a growing company runs and you have a support queue permanently clogged with tickets that all say some version of “new Mac won’t enrol”. None of that time is spent on anything the technician actually controls, because the root causes are almost always the same handful of things: a stale ABM device assignment, a DEP profile that never pushed, a network path that blocks *.apple.com during Setup Assistant, or a token that expired without anyone noticing.

The new way treats enrollment failure as a signal, not an incident. We instrument the enrollment pipeline so that failures are detected automatically from MDM server-side data and Apple Business Manager API state, we remediate the common causes with scripted API calls instead of manual console clicking, and we only wake a human when the automation itself cannot resolve the mismatch. This turns a 45-minute firefight into a 90-second background job and a Slack notification that says the ticket already closed itself.

#Why Enrollment Loops Happen

Apple’s ADE flow depends on three systems staying in sync: Apple Business Manager (device-to-org assignment), your MDM server’s DEP token (the trust relationship that lets your MDM query and assign profiles), and the device’s own attestation to Apple’s activation servers during Setup Assistant. A loop or hang at the Remote Management pane is almost always one of:

  • The MDM server token in ABM has expired or was rotated without updating the MDM console, so ABM silently stops returning assignments.
  • The device was assigned to a different MDM server than expected, often from a reseller or a previous IT process, so your MDM never sees it in the queue.
  • The device’s serial number is present in ABM but has no active enrolment profile assigned because a static assignment rule did not match the new order.
  • Network egress rules block one of Apple’s required enrollment hosts, so the handshake times out rather than failing cleanly.
automated device enrollment troubleshooting

#Prerequisites and Permissions

  • Apple Business Manager administrator or Device Enrollment Manager role, per Apple’s Apple Business Manager roles documentation.
  • An MDM server token in ABM that is valid and not within 30 days of expiry — tokens expire annually and must be renewed manually in ABM.
  • Jamf Pro: Jamf API role with Read and Update on Enrollment, Computers, and Advanced Mobile Device Searches; a Jamf API client (OAuth2 client credentials) rather than a shared admin account, per Jamf Pro API getting started guide.
  • If using Intune instead: a Global Administrator or Intune Administrator to configure the Apple ADE token in the Intune admin centre, and an Azure AD app registration with DeviceManagementServiceConfig.ReadWrite.All Graph scope, per Microsoft’s Automated Device Enrollment overview for Intune.
  • Network allowlist confirmed for the required Apple enrollment endpoints (notably *.apple.com, *.push.apple.com, and deviceenrollment.apple.com), per Apple’s enrollment networking guidance.
  • Test scope: a single ABM Location and a dedicated “IT Test” Smart Group in Jamf, or an equivalent Intune scope tag, before touching production assignment rules.
  • Blast radius: this automation reassigns MDM server tokens and enrollment profile assignments in ABM. Restrict the first rollout to devices with serial numbers in a known test batch.

#Implementation Steps

#
Step 1 — Instrument enrollment failure detection

Action: poll the MDM server’s enrollment log for devices stuck longer than 15 minutes between “Checked In” and “Enrolled” status. In Jamf Pro this is exposed via the Enrollment History endpoint. Expected result: a list of serial numbers with a stalled status and their last checked-in timestamp. Evidence to capture: the raw JSON response saved to your monitoring system, timestamped.

1curl -s -X GET "https://yourinstance.jamfcloud.com/api/v1/enrollment/history" \
2  -H "Authorization: Bearer ${JAMF_TOKEN}" \
3  -H "Accept: application/json" | \
4  jq '.results[] | select(.status == "PENDING") | {serialNumber, timestamp}'

Expected output example:

1{
2  "serialNumber": "C02ZX1234ABC",
3  "timestamp": "2024-05-14T09:02:11Z"
4}

#
Step 2 — Cross-check ABM assignment state

Action: for each stalled serial, query the Apple Business Manager API (or the School and Business Manager API, same surface) to confirm which MDM server the device is currently assigned to. Expected result: either a match with your MDM server UID (meaning the fault is on the enrollment profile side) or a mismatch (meaning the device is assigned elsewhere, often the reseller’s default MDM). Evidence to capture: the assignedServer field from the response, logged against the serial number.

1curl -s -X GET "https://api-business.apple.com/v1/orgDevices/${SERIAL}" \
2  -H "Authorization: Bearer ${ABM_ACCESS_TOKEN}" \
3  -H "Accept: application/json"

Authentication for the ABM API uses OAuth 2.0

client credentials with a private key JWT, documented in Apple’s Apple Business Manager API reference.

#
Step 3 — Auto-remediate the common cases

Action: run a decision script against the cross-check result. Expected result: three branches handled without a human — reassign to the correct MDM server, re-push the assignment, or flag a genuine network fault. Evidence to capture: the branch taken, written to the remediation log with a correlation ID.

1import json
2import subprocess
3import sys
4
5def reassign_device(serial, mdm_server_id, abm_token):
6    payload = {
7        "data": {
8            "type": "orgDeviceActivities",
9            "attributes": {"activityType": "ASSIGN_DEVICES"},
10            "relationships": {
11                "devices": {"data": [{"type": "orgDevices", "id": serial}]},
12                "mdmServer": {"data": {"type": "mdmServers", "id": mdm_server_id}}
13            }
14        }
15    }
16    cmd = [
17        "curl", "-s", "-X", "POST",
18        "https://api-business.apple.com/v1/orgDeviceActivities",
19        "-H", "Authorization: Bearer " + abm_token,
20        "-H", "Content-Type: application/json",
21        "-d", json.dumps(payload)
22    ]
23    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
24    return json.loads(result.stdout)
25
26def remediate(stalled_devices, correct_mdm_id, abm_token, log_path):
27    with open(log_path, "a") as log:
28        for device in stalled_devices:
29            serial = device["serialNumber"]
30            assigned = device.get("assignedServer")
31            if assigned is None:
32                outcome = "UNASSIGNED_FLAG_HUMAN"
33            elif assigned != correct_mdm_id:
34                response = reassign_device(serial, correct_mdm_id, abm_token)
35                outcome = "REASSIGNED" if response.get("data") else "REASSIGN_FAILED"
36            else:
37                outcome = "PROFILE_REPUSH_NEEDED"
38            log.write(json.dumps({"serial": serial, "outcome": outcome}) + "\n")
39
40if __name__ == "__main__":
41    stalled = json.loads(sys.argv[1])
42    remediate(stalled, sys.argv[2], sys.argv[3], "/var/log/ade_remediation.log")

#
Step 4 — Re-push the enrollment profile via Jamf

Action: for devices already on the correct MDM server but still stalled, force a PreStage enrollment re-push using the Jamf Pro API. Expected result: the device re-appears in the enrollment queue within one push cycle. Evidence to capture: the PreStage scope confirmation response and updated serialsInPrestage array.

1curl -s -X POST "https://yourinstance.jamfcloud.com/api/v2/computer-prestages/${PRESTAGE_ID}/scope" \
2  -H "Authorization: Bearer ${JAMF_TOKEN}" \
3  -H "Content-Type: application/json" \
4  -d '{"serialsToAdd": ["C02ZX1234ABC"], "versionLock": 3}'

#
Step 5 — Trigger user-facing recovery without a call

Action: send a Slack or email notification to the end user with a one-line instruction — hold power button for 10 seconds, then power on — rather than routing to L1. Expected result: device re-attempts Setup Assistant against the now-correct assignment and completes enrollment. Evidence to capture: the enrollment completion webhook from Jamf confirming status: COMPLETED with a timestamp inside 5 minutes of the notification.

1{
2  "webhook": "ComputerEnrolled",
3  "serialNumber": "C02ZX1234ABC",
4  "status": "COMPLETED",
5  "timestamp": "2024-05-14T09:09:47Z",
6  "prestageId": "7"
7}

#
Step 6 — Schedule the token expiry guard

Action: run a weekly cron job that checks ABM MDM server token expiry and alerts 30 days out, since an expired token is the single largest cause of mass enrollment failure. Expected result: a proactive alert well before any device is affected. Evidence to capture: the days-remaining value logged each run.

1#!/bin/bash
2TOKEN_EXPIRY=$(curl -s -X GET "https://api-business.apple.com/v1/mdmServers/${MDM_SERVER_ID}" \
3  -H "Authorization: Bearer ${ABM_ACCESS_TOKEN}" | jq -r '.data.attributes.tokenExpirationDate')
4EXPIRY_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "${TOKEN_EXPIRY}" "+%s")
5NOW_EPOCH=$(date "+%s")
6DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
7if [ "${DAYS_LEFT}" -le 30 ]; then
8  echo "ALERT: ABM MDM token expires in ${DAYS_LEFT} days" | \
9    curl -s -X POST -H 'Content-type: application/json' \
10    --data "{\"text\":\"ABM MDM token expires in ${DAYS_LEFT} days for server ${MDM_SERVER_ID}\"}" \
11    "${SLACK_WEBHOOK_URL}"
12fi

#Verification and Expected Evidence

  1. Confirm the stalled device list returned by Step 1 is empty for the same serial numbers within one polling cycle after remediation. Evidence: two consecutive clean polling runs, timestamped at least 15 minutes apart.
  2. Confirm the ABM assignment record shows your production MDM server ID for the remediated serial. Evidence: the assignedServer field from the ABM API response matching your known MDM server UID.
  3. Confirm the device appears in Jamf Pro inventory with an enrollment method of PreStage enrollment and a recent last-check-in timestamp. Evidence: exported inventory record or screenshot of the device record’s Enrollment tab.
  4. Confirm the end user received no ticket-worthy interruption — check that the Slack/email nudge, not a live call, preceded successful enrollment. Evidence: notification send timestamp versus enrollment completion timestamp, delta under 10 minutes.
Fixing Stalled Automated Device Enrollment Loops for Good architecture diagram 2

#Rollback

If a reassignment in Step 3 targets the wrong MDM server ID (for example, a staging server ID was used by mistake), reverse the ABM assignment activity with a second ASSIGN_DEVICES call pointing back at the original server ID captured in the Step 2 log entry before any change was made. ABM assignment activities are idempotent and immediately supersede the previous assignment, so no waiting period applies. If a PreStage re-scope in Step 4 was applied to the wrong PreStage, remove the serial from that PreStage’s scope and add it to the correct one using the same endpoint with serialsToRemove. Always capture the versionLock value before editing to avoid overwriting concurrent scope changes made by another technician.

#Failure and Escalation Conditions

  • Escalate to a human immediately if Step 2 returns assignedServer: null for more than five devices in a single batch — this usually means a reseller has not completed ABM assignment and requires a purchase-order-level conversation, not an API fix.
  • Escalate if the same serial number fails remediation twice in the remediation log within 24 hours — this indicates a network egress block or a corrupted activation record on the device itself, both of which need on-site or hands-on diagnosis.
  • Escalate if the token expiry guard in Step 6 reports fewer than 7 days remaining — token renewal requires interactive sign-in to ABM and cannot be automated end-to-end per Apple’s design, so a human must renew it manually.
  • Wake an on-call engineer outside business hours only if enrollment failures exceed 10% of a scheduled onboarding batch, since that threshold typically signals an ABM-side or Apple push service outage rather than a configuration fault.
  • Monitoring signal: alert on the count of entries in /var/log/ade_remediation.log with outcome REASSIGN_FAILED or UNASSIGNED_FLAG_HUMAN exceeding three in any rolling hour.
  • Rollback trigger: any remediation batch where more than 20% of devices fail verification in Step 1 after two remediation passes — pause the automation and revert to manual triage for that batch.

#Measuring Ticket Deflection

Baseline the current state before deploying this automation by pulling the last 90 days of tickets tagged with enrollment, DEP, ADE, or Remote Management from your ITSM tool and recording the average handle time. Most organisations find this sits between 25 and 50 minutes per ticket once you include the callback and re-attempt cycles. After deployment, track three numbers weekly: the count of stalled devices detected by Step 1, the percentage resolved by Steps 3 through 5 without any technician touch, and the count that still required human escalation under the conditions above. A healthy deployment should show automated resolution above 85% within the first month, with the remaining escalations concentrated in genuine reseller-assignment or network-policy issues rather than repeat failures of the same known cause. Report the deflected ticket count and the cumulative technician hours saved to leadership monthly — this is the number that justifies keeping the automation maintained as Apple updates the ADE and ABM APIs.

Evidence trail

Sources and verification

Primary documentation and external technical references used in this article.

  1. 01Apple's Apple Business Manager roles documentationsupport.apple.com
  2. 02Jamf Pro API getting started guidedeveloper.jamf.com
  3. 03Microsoft's Automated Device Enrollment overview for Intunelearn.microsoft.com
  4. 04Apple's Apple Business Manager API referencedeveloper.apple.com
Isla Morgan

Isla Morgan

Ops Playbook Architect

Isla Morgan is the macOS Platform Engineering Editor for The Ops Playbook, specialising in the design and day-to-day operation of secure, scalable enterprise Mac fleets.

Published
View Profile
Reader Interaction

Comments

Add a thoughtful note on Fixing Stalled Automated Device Enrollment Loops for Good. Comments are checked for spam and held for moderation before appearing.

Loading comments...
Comment submission is disabled until Cloudflare Turnstile keys are configured.

Learn More About KBY

Was this useful?

Operate smarter, with fewer recurring tickets.

Receive new operational playbooks, incident-prevention guidance, automation scripts and recovery runbooks.