Skip to content

Jamf Pro Commands

Neil Martin edited this page Sep 18, 2026 · 25 revisions

Jamf Pro Commands

Jamf Pro commands live under the pro namespace. For shared CLI patterns (apply, scaffold, pagination, patch, dry-run, destructive ops), see CLI Patterns.

jamf-cli pro <resource> <action> [flags]

Commands are generated against the Jamf Pro 11.32.0 API spec, which adds pro sso-settings oidc-broker-config get / update (the tenant's OIDC broker configuration, /v3/sso/oidc-broker-config) and pro jamf-pro-notifications delete --all.

pro resource names come from the API's own tags and URL paths: 38 resources renamed, 24 merged, 13 split. 100 former names resolve to their replacement until 2027-03-09, printing a warning that neither --quiet nor --no-hints silences. Three refuse, their endpoints no longer being ingested. Another 48 invocations moved their operation, so the alias resolves and the subcommand does not; each refuses and names the replacement. Full table: Pro Command Renames; this page uses the new names.

On a platform gateway profile (auth-method: platform), 59 pro command entries are refused before a request is sent, with exit code 8 and Refused by policy. Among them: pro computers lock/restart/shutdown and sixteen pro mobile-devices actions. See Gateway Profiles: Refused Commands and Platform API GA Migration.

Platform API commands are reached through this same pro namespace: blueprints, compliance benchmarks, platform devices, DDM reports. See Platform API Commands. Jamf AI Governance, Jamf Account and platform audit live under a top-level platform namespace instead. See Jamf Platform Commands.

Aliases

These are permanent and print no warning. The retired resource names in Pro Command Renames also resolve, but print a deprecation warning and stop working after 2027-03-09.

Command Alias
computer-inventory computers, comp
mobile-devices md
scripts scr
buildings bld
categories cat
departments dept
group-tools gt
api-roles ar
api-integrations ai
jamf-protect jp
computer-groups-smart-groups smart-computer-groups
jamf-cloud-distribution-service jcds
jamf-cloud-distribution-service-files jcds-files
device dev
config cfg

Platform API resources reached through pro carry their own aliases (bp, cb, pdev, pdg, ddm); see Platform API Commands.

jamf-cli pro computers list          # same as: jamf-cli pro comp list
jamf-cli pro mobile-devices list -o table   # same as: jamf-cli pro md list -o table
jamf-cli pro scripts list           # same as: jamf-cli pro scr list

Overview

overview fetches ~39 API endpoints in parallel and prints a consolidated summary of the instance. Under platform gateway auth it adds Platform API data.

  • Health & Alerts: active alerts, server health, SLASA/CSA scopes
  • Instance: server URL, Jamf Pro version, gateway URL (platform auth)
  • Fleet: managed/unmanaged computer and mobile device counts, check-in frequency
  • Enrollment & Certificates: ADE instances with per-instance token expiry, VPP locations with per-location token expiry, APNs certificate status, MDM auto-renewal, DEP sync status, prestages
  • Configuration: policies, config profiles, packages, scripts, patch titles, App Installers, eBooks, webhooks
  • Organization: sites, buildings, departments, categories, computer smart/static groups, mobile smart/static groups, static user groups
  • Features: SSO (SAML + Admin), volume purchasing, DEP, cloud deployments, patch management, SMTP
jamf-cli pro overview              # table format
jamf-cli pro overview -o json      # JSON for scripting

Open the Web Interface

pro open launches the Jamf Pro web interface in a browser. With no argument it opens the dashboard; with a section name it opens that page.

jamf-cli pro open                                  # dashboard
jamf-cli pro open policies
jamf-cli pro open smart-computer-groups
jamf-cli pro open settings/api-roles-and-clients
jamf-cli pro open --list                           # every section name
jamf-cli pro open computers --print                # print the URL instead of launching
jamf-cli pro open computers --field url            # bare URL, for scripting

139 sections are named. pro open --list prints them with the heading the interface uses for each (name, page, path), through the shared formatter, so -o json, -o csv and --select all work on it. Tab completion offers each name with its heading. Read the list from the binary rather than from here; it moves with the interface.

91 of the 139 are settings/-prefixed. Those names drop the interface's own category segment and the CLI restores it, so you do not have to know which settings category a page sits in:

pro open settings/api-roles-and-clients  ->  /view/settings/system-settings/api-roles-and-clients

Record-detail and wizard pages are deliberately unnamed: they need an id or mid-flow state. An argument that is not a section name but looks like a path is opened verbatim, which keeps those pages reachable.

jamf-cli pro open computers.html?id=42
jamf-cli pro open view/settings/system-settings/sso

Refusals

Exit 2 in each case, with nothing launched:

Invocation Message
pro open ssso unknown section "ssso", hinting jamf-cli pro open --list and the path form. Suggestions match on substring before edit distance.
pro open --list computers --list lists every section name and takes no section: drop --list to open "computers". The argument is refused, not discarded.
pro open https://example.com/foo "https://example.com/foo" is a URL, not a section: this command opens a page of this Jamf Pro instance
pro open ../../etc Unknown section. .. segments, including the %2e%2e spelling, and any resolved URL whose host is not the instance's, are refused; so is anything that is not absolute http or https.

When the URL is printed instead of launched

Under any of --print, --no-input, --dry-run, --out-file, --field, --select, or a non-terminal stdout. jamf-cli pro open computers | pbcopy and a CI job both get the URL rather than a browser launch attempt. Consume it with --field url for a bare string or -o json for a row. In the launch case stdout stays empty and the note goes to stderr, so nothing downstream reads a launch as data.

$BROWSER wins where it is set, then open / xdg-open / rundll32.

Auth

pro open is the only one of the four product open commands that can send a request.

Profile Requests
instance (token or oauth2) none; the configured URL is used
platform gateway exactly one: GET /v1/jamf-pro-server-url, scope jss-url:read ("Read JSS URL" on a Jamf Pro API role)

A platform integration names a tenant, never a Jamf Pro host, which is why the gateway case has to ask. pro open --list needs no credentials at all: the section table is compiled into the binary.

protect open, school open and security open are the same command for the other three products. None of them sends a request, all three carry jamf:no-auth, and security open is the constant https://radar.wandera.com/ because Security Cloud has no per-tenant URL.

Computers (comp)

These commands send /v4/computers-inventory, and get reads the v4 detail endpoint. On a 404 the generated subcommands retry /v3, then /v2, then /v1, warning on stderr, so an instance on an older API keeps working. pro comp erase and pro comp remove-mdm are hand-written, send /v4/computers-inventory/{id}/… with no fallback, and answer 404 on an instance that does not serve v4.

Through a gateway profile, -v shows the GA path shape (/pro/v4/…, no /api segment) and the default section set:

GET https://eu.api.jamfcloud.com/pro/v4/computers-inventory?section=GENERAL&section=HARDWARE&section=OPERATING_SYSTEM&page=0&page-size=2000

list and get hit the detail endpoint and return the GENERAL section by default. --section adds more (HARDWARE, OPERATING_SYSTEM, SECURITY, USER_AND_LOCATION, DISK_ENCRYPTION) and is repeatable. Run jamf-cli pro computers list --help for the full section list.

jamf-cli pro computers list --all -o table                  # List all computers
jamf-cli pro computers list --sort general.name:asc         # Sorted by name
jamf-cli pro computers list --section HARDWARE --section OPERATING_SYSTEM  # Add hardware + OS columns
jamf-cli pro computers get 42 -o json                       # Get a single computer by ID
jamf-cli pro computers get --name "Neil's MacBook"          # Get by name
jamf-cli pro computers get --serial C02X1234                # Get by serial number
jamf-cli pro computers get 42 --section HARDWARE --section SECURITY  # Include hardware + security detail
jamf-cli pro computers list --field general.name            # Just the names

Note: the resource is computer-inventory; computers and comp are permanent aliases. computers-inventory resolves too, warns, and retires on 2027-03-09. Filter and sort fields live inside sections (general.name, hardware.serialNumber).

pro computer-inventory-collection-settings-custom-path create accepts scope: APP only (case-sensitive). It sends POST /v2/computer-inventory-collection-settings/custom-path; the withdrawn v1 accepted [APP, FONT, PLUGIN]. --help lists no allowed values for it, and a FONT or PLUGIN path answers 400.

Mobile Devices (md)

list uses the detail endpoint and takes --section; get returns the full device record and takes none. list fetches HARDWARE for you, so its default table carries serial number and model. Common sections: GENERAL, HARDWARE, USER_AND_LOCATION, SECURITY, APPLICATIONS, NETWORK, CERTIFICATES. Run jamf-cli pro mobile-devices list --help for the full list.

jamf-cli pro mobile-devices list --all -o table            # List all mobile devices (includes hardware by default)
jamf-cli pro mobile-devices list --section USER_AND_LOCATION  # Include assigned user
jamf-cli pro mobile-devices get 7                          # Get a single device by ID
jamf-cli pro mobile-devices get --serial F4K3SER1AL        # Get by serial number
jamf-cli pro mobile-devices get --udid XXXXXXXX-XXXX-...   # Get by UDID
jamf-cli pro mobile-devices list --section SECURITY --section NETWORK  # Multiple detail sections (list only)

Scripts (scr)

jamf-cli pro scripts list -o table                 # List scripts
jamf-cli pro scripts get 15 -o json                # Get a script by ID
jamf-cli pro scripts apply --scaffold > s.json     # Generate template, edit, then apply
cat s.json | jamf-cli pro scripts apply --yes
jamf-cli pro scripts delete --name "Deploy Agent" --yes  # Delete by name

Buildings (bld), Categories (cat), Departments (dept)

jamf-cli pro buildings list -o table                 # List buildings
jamf-cli pro buildings get --name "HQ" -o yaml       # Get a building by name
jamf-cli pro categories list --field name            # Category names only

# Apply (upsert): create or replace by name
echo '{"name":"HQ","city":"Cupertino"}' | jamf-cli pro buildings apply --yes
echo '{"name":"Security","priority":1}' | jamf-cli pro categories apply --yes

# Scaffold → edit → apply
jamf-cli pro departments apply --scaffold > d.json
cat d.json | jamf-cli pro departments apply --yes

# Delete by name
jamf-cli pro buildings delete --name "Old Office" --yes
jamf-cli pro categories delete --name "Unused Category" --yes

Classic API Output Formats

Classic API commands (classic-*) return raw XML by default, the native wire format. Use -o to convert:

Format Description
(none) / -o xml Pretty-printed XML (default for Classic API)
-o raw Exact bytes from the API, no processing
-o json XML converted to JSON
-o table Tabular view of selected fields
# Default: pretty-printed XML
jamf-cli pro classic-policies get 100

# Force JSON output
jamf-cli pro classic-policies get 100 -o json

# Raw bytes, for piping to another tool
jamf-cli pro classic-policies get 100 -o raw > policy.xml

# Table view of the list
jamf-cli pro classic-policies list -o table

Note: Modern API commands (computers, scripts, buildings) return JSON by default. The XML formats apply to Classic API commands only.

Classic API Writes: --from-file, --set, --scaffold

Classic create and update read their XML body from --from-file, from --set, or, with neither flag, from stdin. apply takes --from-file or --set the same way.

# From a file
jamf-cli pro classic-network-segments create --from-file segment.xml

# From stdin
cat segment.xml | jamf-cli pro classic-network-segments create

# Print a complete XML template, edit it, send it
jamf-cli pro classic-network-segments create --scaffold > segment.xml

# Build the whole body from flags: no file, no XML to write
jamf-cli pro classic-network-segments create \
  --set name="Amsterdam Office" \
  --set starting_address=10.1.1.1 \
  --set ending_address=10.1.1.254

# Rename an existing segment and change nothing else
jamf-cli pro classic-network-segments update --name "Amsterdam Office" --set name="AMS Office"

--scaffold and --set are available on 114 of the 117 Classic create/update/apply commands, across 44 of the 45 Classic resources carrying those verbs. The exception, classic-computer-configs, binds no request-body schema; it takes --from-file and stdin, and is refused on a gateway profile.

--help on these commands lists the required fields, the optional top-level sections, each enum's allowed values, and which fields carry credentials.

A Classic body can carry a credential, so put it in a file, never in a flag value or in argv.

--set builds the whole body

Classic --set builds the entire body, so it is mutually exclusive with --from-file. (The Platform and Security Cloud --set overlays onto a --from-file body instead.)

--set and --from-file are mutually exclusive; --set builds the whole body, so pass the file
alone or set fields alone

A Classic PUT is a partial update: omitted fields keep their values, so --set alone is a valid update. A body carrying only <name> renames a network segment and leaves its address range, its override flags and a group's whole criteria array intact.

Piping XML to stdin and passing --set warns instead of failing (warning: --set builds the whole body; ignoring stdin), because a CI runner hands every process an empty stdin.

Classic API behaviour on a bad body

The Classic API hides a malformed body. Wire-checked:

You send The API answers
An element the schema does not declare 201, and the server drops the element
A value outside an enum 201, and the field reads back as the default (frequency: "Twice per fortnight" becomes Once per computer)
A missing required field 409, with the field named inside an HTML error page, one field per response

--set therefore refuses an unknown field and an out-of-enum value, each refusal exiting 1. --help renders an Allowed values: block per enum. A [] suffix on a dotted path there means each element of an array is constrained: account_maintenance.accounts[].action: Create | Reset | Delete | DisableFileVault on classic-policies create.

--set bogus: network_segment_post declares no such field. Settable here: building, department, ...
--set general.frequency: "Twice per fortnight" is not one of Once per computer,
  Once per user per computer, ... (the Classic API accepts an out-of-range value and
  silently substitutes its default, so this is refused here)

An empty value for an enum field is refused too. Without the refusal, --set general.frequency="$FREQ" with FREQ unset sends <frequency></frequency>, which the API answers 200 while resetting the policy's execution frequency to Once per computer:

$ jamf-cli pro classic-policies create --set general.name=X --set general.frequency=
--set general.frequency: an empty value is not one of Once per computer, Once per user per
computer, Once per user, Once every day, Once every week, Once every month, Ongoing; omit
the flag to leave the field unchanged (a shell variable that expanded to nothing is the
usual cause)

A non-enum field accepts key= and renders an empty element, so you can clear a field.

Credentials go in a file, never in --set

--set refuses a credential field (exit 1), and shell completion omits credential fields:

$ jamf-cli pro classic-directory-bindings create --set name=X --set password=hunter2
--set password: password carries a credential and cannot be passed as a flag value, where it
would land in shell history, ps output and CI logs; put the whole body in a file and use
--from-file

A secret the schema does not declare gets the unknown-field refusal instead:

$ jamf-cli pro classic-distribution-points create --set name=X --set ws_password=hunter2
--set ws_password: distribution_point_post declares no such field. Settable here:
connection_type, context, enable_load_balancing, ... Run --scaffold for the whole shape,
or use --from-file for a field the spec does not know

Affected resources include distribution points, SMTP servers, LDAP servers, directory bindings, VPP accounts, disk-encryption configurations (the institutional FileVault recovery keystore and its key material included) and JWT configurations (the signing key). Each command's --help lists its own under Credential fields (--from-file only, never --set).

Policy scaffold sections to delete before sending

jamf-cli pro classic-policies create --scaffold fills each optional section with one specimen entry. Those specimens reference objects that do not exist on your instance:

  • general.category.id renders the spec's example 0, which answers 409 No match found for category 0.
  • scope and account_maintenance answer 500.

Delete the sections you do not need before sending.

Policies (Classic API)

jamf-cli pro classic-policies list -o table           # List all policies
jamf-cli pro classic-policies get 100                 # Get policy by ID
jamf-cli pro classic-policies get --name "Deploy Chrome"   # Get by name

# Apply (upsert): create or replace by name
cat policy.xml | jamf-cli pro classic-policies apply --yes

# Delete by name
jamf-cli pro classic-policies delete --name "Legacy Policy" --yes

Configuration Profiles (Classic API)

jamf-cli pro classic-macos-config-profiles list       # macOS config profiles
jamf-cli pro classic-mobile-config-profiles list      # Mobile config profiles
jamf-cli pro classic-macos-config-profiles get 22     # Get by ID

Deploying from a .mobileconfig file

Use --mobileconfig-file to inject a .mobileconfig file into the profile payload on create, update, or apply:

# Apply (upsert) a macOS config profile: create or replace by name
cat > profile-meta.xml <<EOF
<os_x_configuration_profile>
  <general>
    <name>Wi-Fi Settings</name>
    <category><name>Network</name></category>
  </general>
</os_x_configuration_profile>
EOF
jamf-cli pro classic-macos-config-profiles apply \
  --from-file profile-meta.xml \
  --mobileconfig-file /path/to/wifi.mobileconfig \
  --yes

Note: On update and apply (replace), the CLI injects <redeploy_on_update>All</redeploy_on_update> so devices receive the updated profile without a re-enrol. It also preserves the existing PayloadUUID and PayloadIdentifier so the profile renews cleanly on device.

Deploying from a preference plist (custom payload)

--custom-payload-file wraps a preference plist (XML or binary) into a com.apple.ManagedClient.preferences payload and injects it as the profile's mobileconfig payload, taking the preference domain from the filename. Available on create, update and apply, and mutually exclusive with --mobileconfig-file.

# Create a profile from a plist; name defaults to the plist filename (without extension)
jamf-cli pro classic-macos-config-profiles create \
  --custom-payload-file /path/to/com.example.app.plist

# Override the profile name
jamf-cli pro classic-macos-config-profiles apply \
  --custom-payload-file /path/to/com.example.app.plist \
  --name "Example App Settings" \
  --yes

# Override the preference domain (useful when the filename doesn't match the bundle ID)
jamf-cli pro classic-macos-config-profiles apply \
  --custom-payload-file /path/to/settings.plist \
  --custom-payload-domain com.example.app \
  --name "Example App Settings" \
  --yes

# Bundle multiple plists into a single profile (domain inferred per file)
jamf-cli pro classic-macos-config-profiles apply \
  --custom-payload-file /path/to/com.example.app.plist \
  --custom-payload-file /path/to/com.example.other.plist \
  --name "Multi-domain Settings" \
  --yes
Flag Description
--custom-payload-file Path to a preference plist (XML or binary). Repeatable. Mutually exclusive with --mobileconfig-file.
--custom-payload-domain Preference domain override. Only valid with exactly one --custom-payload-file; inferred from filename when omitted.
--name Profile name override. Useful when the plist filename is not a meaningful profile name.

Payload wire handling (v1.26.0+)

The server entity-decodes a submitted <payloads> value one extra time and rejects the write with HTTP 409 "Unable to update the database" when the result contains a bare & or <. A plist must encode those characters, so a profile carrying <string>R&amp;D</string> fails, as does a get XML body piped back into apply. Jamf PI-827.

The CLI handles the escaping on create, update, and apply for classic-macos-config-profiles and classic-mobile-config-profiles. No flags to set:

# A profile with & or < in a value now uploads instead of returning 409
jamf-cli pro classic-macos-config-profiles apply \
  --from-file profile-meta.xml \
  --mobileconfig-file /path/to/rnd-settings.mobileconfig \
  --yes

# get → apply round-trips (backup restore) work too
jamf-cli pro classic-macos-config-profiles get 22 -o xml > profile.xml
jamf-cli -p target pro classic-macos-config-profiles apply --from-file profile.xml --yes

Handled for you:

Input Handling
Any & or < in a payload value Escaped to the one form the server accepts, then wrapped in CDATA
A GET/backup XML body piped back in Entity-decoded first, so the true plist is recovered before re-escaping
A CMS-signed .mobileconfig The inner profile is extracted and uploaded, with a note on stderr, because the Classic API cannot preserve a signature
A binary plist (bplist00 header) Converted to XML
An embedded ]]> or CDATA section inside the plist Rewritten as escaped character data so it cannot terminate the wrapper early

Post-write verification. After a successful write the CLI re-fetches the stored payload, compares it against what you submitted, and names each differing dotted path on stderr, with the reason, listing at most three and then … and N more:

warning: the server stored 2 payload value(s) differently than submitted:
  - PayloadContent[3].LoginwindowText: extra & / < entity layer added by the server (Jamf PI-827) — no wire format stores this payload type faithfully
  - PayloadContent[3].AdminHostInfo: extra & / < entity layer added by the server (Jamf PI-827) — no wire format stores this payload type faithfully

No flag changes that warning. Storage behaviour is per payload type: com.apple.ManagedClient.preferences (Custom Settings values and dict keys) and com.apple.notificationsettings store byte-exact; other macOS payload types and all mobile device payloads keep one extra entity layer, so a literal & or < arrives on device as &amp; / &lt;. Where the value matters, use a Custom Settings (MCX) payload or the Platform blueprints API.

The comparison normalises CR and CRLF line endings before diffing, because the server rewrites \r to \n on ingest. U+2028, U+2029 and U+0085 are compared exactly: those round-trip byte-exact, so a difference there is corruption.

Note: The escaping and verification cover configuration profiles only. AppConfig is spec-compliant, so classic-mobile-apps --appconfig-file gets no escaping (it would corrupt values: A & B stored as A &amp; B); the signed, binary and ]]> handling above still applies. classic-mac-apps --appconfig-file is accepted and then discarded, a following get returning no app_configuration: Mac App Store AppConfig is deprecated server-side.

Packages (Classic API)

jamf-cli pro classic-packages list -o table           # List all packages
jamf-cli pro classic-packages get 50                  # Get package by ID
jamf-cli pro classic-packages get --name "Firefox"     # Get by name

Accounts (Classic API)

Manage Jamf Pro local accounts and groups via the Classic API. Both subcommands carry full CRUD.

# User accounts
jamf-cli pro classic-account-users list
jamf-cli pro classic-account-users get 10
cat user.xml | jamf-cli pro classic-account-users create
cat user.xml | jamf-cli pro classic-account-users update 10
jamf-cli pro classic-account-users delete 10 --yes

# Local groups
jamf-cli pro classic-account-groups list
jamf-cli pro classic-account-groups get 5
cat group.xml | jamf-cli pro classic-account-groups create
cat group.xml | jamf-cli pro classic-account-groups update 5
jamf-cli pro classic-account-groups delete 5 --yes

Note: classic-accounts list returns both users and groups in one payload, for the overview case.

Groups (Classic API)

classic-computer-groups and classic-mobile-device-groups manage smart and static groups through the Classic API. Each carries list, get, create, update, delete plus name-based apply. get, update and delete take --name in place of a numeric ID.

# Computer groups (smart + static)
jamf-cli pro classic-computer-groups list
jamf-cli pro classic-computer-groups get --name "All Managed Clients"
cat group.xml | jamf-cli pro classic-computer-groups apply --yes   # create or replace by name
jamf-cli pro classic-computer-groups delete --name "Old Group" --yes

# Mobile device groups (smart + static)
jamf-cli pro classic-mobile-device-groups list
jamf-cli pro classic-mobile-device-groups get --name "All iPads"
jamf-cli pro classic-mobile-device-groups apply --from-file ipads.xml --yes

Four Modern API equivalents return JSON: computer-groups-smart-groups (alias smart-computer-groups), computer-groups-static-groups, mobile-device-groups-smart-groups and mobile-device-groups-static-groups. Use the Classic commands for the XML group payload (smart-group criteria), or alongside other Classic resources.

pro static-computer-groups is a retiring alias of pro computer-groups-static-groups (the v3 endpoint the gateway publishes) and is not refused on a gateway profile: pro static-computer-groups list warns about the name and returns the groups.

Advanced Searches (Classic API)

classic-advanced-mobile-device-searches manages saved advanced mobile device searches via the Classic API, alongside classic-advanced-computer-searches. Full CRUD plus name-based apply, with --name lookup on get/update/delete.

jamf-cli pro classic-advanced-mobile-device-searches list
jamf-cli pro classic-advanced-mobile-device-searches get --name "Out of Compliance"
jamf-cli pro classic-advanced-mobile-device-searches apply --from-file search.xml --yes

Patch Reports (Classic API)

Served on a gateway profile. pro classic-patch-titles list and pro classic-patch-policies list both return XML at exit 0, and pro classic-patch-reports get <id> reaches Jamf Pro: a missing title answers its own 404, exit 4 (the policy refusal would be exit 8).

# Get a specific patch report by software title ID
# (Classic API exposes no list endpoint; fetch a report by ID)
jamf-cli pro classic-patch-reports get 3

Device History (Classic API)

classic-computer-history and classic-mobile-history fetch a device's Classic API history record: management commands, policy logs, audits, user and location history. Both are get-only, and get looks up a device by id, --name, --serialnumber (alias --serial), --macaddress or --udid.

# Get a computer's full history by ID
jamf-cli pro classic-computer-history get 42

# Look up by serial number (--serial is an alias for --serialnumber)
jamf-cli pro classic-computer-history get --serial C02X1234

# Return only one section of the record, server-side, with --subset (tab-completable)
jamf-cli pro classic-computer-history get --serial C02X1234 --subset Commands
jamf-cli pro classic-mobile-history get --name "Lab iPad 12" --subset ManagementCommands

--subset narrows the response to a single section server-side (one value; the API's &-combined multi-subset syntax isn't supported here yet). Tab-complete to see the valid sections:

  • Computer history: General, ComputerUsageLogs, Audits, PolicyLogs, CasperRemoteLogs, ScreenSharingLogs, CasperImagingLogs, Commands, UserLocation, MacAppStoreApplications
  • Mobile device history: General, ManagementCommands, UserLocation, Audits, Applications, Ebooks

Note: With --subset and a non-ID lookup (--serial, say), the CLI resolves the record's ID first, then requests the subset by ID, the only form the Platform Gateway's Classic proxy allows. --serial is also an alias on classic-mobile-devices get.

Application Usage (Classic API, v1.25.0+)

classic-computer-app-usage fetches per-day application usage for one computer over a date range. No Modern API equivalent exists, and the Classic endpoint carries its date range in the URL path (/JSSResource/computerapplicationusage/id/{id}/{start}_{end}), so it is one hand-written command with no list/get pair.

Identify the computer with exactly one of --id, --serial, --udid, or --name. Non-ID identifiers are resolved to a computer ID via inventory search first. Supply the range with either --start and --end (yyyy-mm-dd), or --last for a rolling window ending today.

# Fixed range, by ID
jamf-cli pro classic-computer-app-usage --id 42 --start 2026-06-01 --end 2026-06-30

# Rolling window, by serial number
jamf-cli pro classic-computer-app-usage --serial C02XL0ABCDEF --last 30d

# Two weeks, by computer name, as CSV
jamf-cli pro classic-computer-app-usage --name "Neil's MacBook" --last 2w -o csv
Flag Description
--id Computer ID (used directly, no lookup)
--serial Computer serial number (resolved via inventory search)
--udid Computer UDID
--name Computer name (resolved via inventory search)
--start Range start date, yyyy-mm-dd (use with --end)
--end Range end date, yyyy-mm-dd (use with --start)
--last Rolling window ending today, e.g. 30d or 2w (instead of --start/--end)

Every output format carries one row per (date, application): date, name, version, foreground, open. foreground is minutes in the foreground, open the launch count, so -o json pipes into jq without walking the API's per-day nesting.

Note: This command negotiates JSON with the server, the only way the endpoint returns its array-shaped body, so its output is JSON and not XML.

App Installers

app-installers (with its nested global-settings group), app-installers-deployments and app-installers-titles cover every App Installer endpoint: per-deployment computers, installation summaries, history, version updates and retry operations.

The App Installer endpoints are published in the gateway's Jamf Pro API, so these commands work on a platform gateway profile and on an instance profile. The feature probe reports App Installer availability, the Cloud Services Connection state and the environment's capabilities.

jamf-cli pro app-installers get
{
  "cloudServicesEnabled": true,
  "features": [
    "CONTROL_DEPLOYMENT_PROCESS",
    "M2M_INTEGRATION"
  ]
}
# List all App Installer deployments
jamf-cli pro app-installers-deployments list

# Get a deployment by name
jamf-cli pro app-installers-deployments get --name "Slack"

# Create or replace a deployment
jamf-cli pro app-installers-deployments apply --from-file slack-deployment.json --yes

# Update a deployment's fields in place (fetch-merge-put with --set)
jamf-cli pro app-installers-deployments update --name "Slack" --set <key>=<value>

# Delete a deployment
jamf-cli pro app-installers-deployments delete --name "Slack" --yes

# Export all deployment configurations as CSV
jamf-cli pro app-installers-deployments export --out-file deployments.csv

# List computers in a deployment (by name or ID)
jamf-cli pro app-installers-deployments computers --name "Slack"

# Get installation summary for a deployment
jamf-cli pro app-installers-deployments installation-summary --name "Slack"

Object history

# Get a deployment's object history (notes + change details, paginated)
jamf-cli pro app-installers-deployments history --name "Slack"

# Filter history with RSQL over username, date, note, details
jamf-cli pro app-installers-deployments history --name "Slack" --filter 'username=="admin"'

# Add a note to a deployment's object history (body piped via stdin)
jamf-cli pro app-installers-deployments add-history-note --name "Slack" --scaffold   # show the body template: {"note":""}
echo '{"note":"Approved for production"}' | jamf-cli pro app-installers-deployments add-history-note --name "Slack"

Version updates and retries

# Trigger a version update for a deployment's app title
jamf-cli pro app-installers-deployments version-update --name "Slack"

# Retry all failed installations for one deployment
jamf-cli pro app-installers-deployments installation-retry --name "Slack"

# Retry failed installations across EVERY deployment in a single server-side call
jamf-cli pro app-installers-deployments installation-retry --all --yes

# Retry a failed installation on a single computer within a deployment
#   args: <deployment-id> <computerId>
jamf-cli pro app-installers-deployments installation-retry-by-computer-id 12 345

Note: installation-retry --all hits the collection-level retry endpoint, retrying failures across all deployments in one request. --yes skips its confirmation prompt.

Titles

# List available App Installer titles
jamf-cli pro app-installers-titles list

# Get a title by ID, or by name (matched against the catalogue's titleName)
jamf-cli pro app-installers-titles get 001
jamf-cli pro app-installers-titles get --name "Adobe Creative Cloud"

# Get a specific version of a title (default: the newest)
jamf-cli pro app-installers-titles get 001 --version 6.5.0

# List the versions available for a title (server-ordered)
jamf-cli pro app-installers-titles versions 001
jamf-cli pro app-installers-titles versions --name "Adobe Creative Cloud"

# Only versions above a floor
jamf-cli pro app-installers-titles versions 001 --start-version 6.0.0

--name matches the catalogue's titleName field. The titles collection declares no server-side filter, so the lookup re-fetches and matches titleName client-side. Against a 363-title catalogue, get --name "Adobe Creative Cloud" returns title 001 with titleName, bundleId, publisher, iconUrl, version and installationPathShared.

Global settings

app-installers global-settings is a nested command group on the app-installers singleton, so it takes no IDs; /v1/app-installers/global-settings carries a PUT on itself. See Sub-Resource Command Groups.

The former name pro app-installer-global-settings resolves two tokens deep and warns:

$ jamf-cli pro app-installer-global-settings get
warning: `app-installer-global-settings` is a deprecated name for `app-installers global-settings` and
stops working after 2027-03-09. Use `pro app-installers global-settings`.
# Get the settings object
jamf-cli pro app-installers global-settings get

# Update it (run with --scaffold to see the field paths)
jamf-cli pro app-installers global-settings update --set <key>=<value>

# Read the default Global Deployment Process Controls
# -> {"commandsBatchSize": 10000, "batchFrequencyInMinutes": 15,
#     "daysOfWeek": ["MONDAY", "TUESDAY", ...], ...}
jamf-cli pro app-installers global-settings deployment-controls

# Object history for the settings (paginated; RSQL over username, date, note, details)
jamf-cli pro app-installers global-settings history
jamf-cli pro app-installers global-settings history --filter 'username!=admin'

# Add a note to that history
jamf-cli pro app-installers global-settings add-history-note --scaffold   # {"note":""}
echo '{"note":"Enabled cloud services"}' | jamf-cli pro app-installers global-settings add-history-note

App Installer wire behaviour

None of these are stated in the spec.

  • An identifier must be a positive numeric string or -1, and the server checks the shape before the lookup. app-installers-deployments get 0 answers 400 INVALID_ID (id field must be string of positive numeric value or -1), not 404.
  • -1 is the no-assignment sentinel. An omitted smartGroupId reads back as "-1". The same holds for categoryId and siteId.
  • A create's href is not callable through the gateway. The server builds it from the Jamf Pro instance hostname with an /api prefix, and the gateway base URL carries neither. Take the id from the response and build your own path.
  • Both retries answer 404 with an empty errors array when there is nothing to retry, on a deployment that get answers 200 for in the same run. That is Jamf Pro's own response.

Notifications

pro jamf-pro-notifications (alias notifications) reads and dismisses the alerts Jamf Pro raises about the instance.

# Every current notification
jamf-cli pro jamf-pro-notifications list

# Dismiss one, by id and type
jamf-cli pro jamf-pro-notifications delete 1 2 --yes

# Dismiss every dismissible notification in one call
jamf-cli pro jamf-pro-notifications delete --all --yes

--all (v1.30.0, Jamf Pro 11.32.0) sends DELETE /v1/notifications, which dismisses every dismissible notification server-side in one request. It confirms first unless --yes, and it refuses to be combined with a positional: dismissing one notification and dismissing the lot are different endpoints, not one endpoint with a wider target. The per-notification delete <id> <type> signature is unchanged; the Use string now reads [<id>] [<type>] because with --all both are optional.

-n / --dry-run previews the --all request without sending it. In v1.29.0 it did not: delete --all --yes -n sent a live tenant-wide DELETE and got a 204, wire-checked before and after the fix. A destructive generated command declares its own --dry-run, which shadows the root persistent flag, and the template's own check sat after the --all block had already sent the request. The preview now runs before both the confirmation and the request. --all was also unreachable before v1.30.0 for a second reason: cobra validates Args before RunE, so the emitted ExactArgs refused the invocation before the flag could be read.

The other collection-level --all in the pro namespace is pro app-installers-deployments installation-retry --all; it was never affected by the --dry-run defect, because it is not destructive and declares no local --dry-run.

Scope Management

Nine Classic API resources carry a scope subcommand with get, add and remove, so you read and change scope without writing XML: classic-policies, classic-macos-config-profiles, classic-mobile-config-profiles, classic-mac-apps, classic-mobile-apps, classic-ebooks, classic-restricted-software, classic-vpp-assignments, classic-vpp-invitations.

Breaking in v1.30.0: the positional is an id, not a name

scope get, scope add and scope remove now take an optional [<id>], with --name as the alternative, matching every other Classic command. A v1.29.0 script that passes a bare name is broken: the name is refused, not resolved.

# v1.29.0
jamf-cli pro classic-policies scope get "Deploy Chrome"
jamf-cli pro classic-policies scope add "Deploy Chrome" --computer-group "All Managed Clients"

# v1.30.0
jamf-cli pro classic-policies scope get 1
jamf-cli pro classic-policies scope get --name "Deploy Chrome"
jamf-cli pro classic-policies scope add 1 --computer-group "All Managed Clients"
jamf-cli pro classic-policies scope add --name "Deploy Chrome" --section exclusion --building "London"
jamf-cli pro classic-policies scope remove --name "Deploy Chrome" --computer-group "Test Group"

scope get takes no category flags, only --name, and goes through the shared formatter, so every -o value works on it:

jamf-cli pro classic-macos-config-profiles scope get --name "Wi-Fi Settings" -o table
jamf-cli pro classic-macos-config-profiles scope get 12 -o yaml

A positional and --name together are refused: pass either an <id> argument or --name, not both. A non-numeric positional exits 2 and names the replacement:

$ jamf-cli pro classic-policies scope get "Deploy Chrome"
message: "Deploy Chrome" is not an id; Classic ids are numeric
hint:    pass a name as --name "Deploy Chrome" — the positional argument used to be the name
         on these commands and is now the id

A --name matching more than one record is refused rather than resolved to the first in document order. Classic names are not unique; a live tenant carried two ebooks sharing one.

Categories, per resource

Each scope command registers only the categories its own resource can hold, so a cross-family flag no longer exists on it. It arrives as cobra's unknown-flag error, and the hint names what the resource does accept:

$ jamf-cli pro classic-policies scope add 1 --class "Year 9"
message: unknown flag: --class
hint:    scope categories for this resource: --building, --computer, --computer-group,
         --department, --ibeacon, --jss-user, --jss-user-group, --network-segment, --user
         or --user-group
Resource Categories Sections
classic-policies --building --computer --computer-group --department --ibeacon --jss-user --jss-user-group --network-segment --user --user-group target, limitation, exclusion
classic-macos-config-profiles as classic-policies target, limitation, exclusion
classic-mac-apps as classic-policies, minus --ibeacon target, limitation, exclusion
classic-mobile-config-profiles --building --department --ibeacon --jss-user --jss-user-group --mobile-device --mobile-device-group --network-segment --user --user-group target, limitation, exclusion
classic-mobile-apps as classic-mobile-config-profiles, minus --ibeacon target, limitation, exclusion
classic-ebooks both families: --building --class --computer --computer-group --department --jss-user --jss-user-group --mobile-device --mobile-device-group --network-segment --user --user-group target, limitation, exclusion
classic-restricted-software --building --computer --computer-group --department --user target, exclusion (no limitation section at all)
classic-vpp-assignments --jss-user --jss-user-group --user-group target, limitation, exclusion
classic-vpp-invitations --jss-user --jss-user-group --user-group target, limitation, exclusion

Which sections a category is valid in does not vary by resource, with one exception:

Category Valid sections Notes
--building, --computer, --computer-group, --department, --jss-user, --jss-user-group, --mobile-device, --mobile-device-group target or exclusion --jss-user is a Jamf Pro user name; --jss-user-group a Jamf Pro user group name
--ibeacon, --network-segment, --user, --user-group limitation or exclusion --user is a directory or local username, free text, not a Jamf Pro user; --user-group is a directory (LDAP/IdP) user group name
--class (classic-ebooks only) target
--user on classic-restricted-software exclusion the exception; the other four categories there are target or exclusion

--ibeacon (policies, macOS profiles, mobile profiles) and --class (ebooks) are new in v1.30.0. Both were readable through scope get and writable by nothing. --user is now accepted as a classic-restricted-software exclusion, where it was refused outright while the wire stored it.

--computer and --mobile-device values are resolved by format: a 40-character hex string as a UDID, a plain integer as a numeric Jamf Pro ID, anything else as a device name. Serial numbers are not resolved here; use a name, UDID or numeric ID.

--class sends two requests

Jamf Pro stores <classes> only while the stored category is empty. A write made while it already holds a member clears it, and carrying the identical value, omitting the element, and every identifier shape all clear it alike. Since a scope PUT replaces <scope> wholesale, no single request can preserve an existing class across any other scope change: scope add --building on an ebook holding a class used to destroy the class and report success.

So the CLI sends the intended change with <classes> emptied, then the same scope with them populated. It is keyed on the category rather than the resource, and the first request carries the change you asked for, so an interruption leaves you no worse off than the single request it replaced. Two PUTs go out per --class write, which matters if you are counting requests or handling a partial failure.

What a write sends and checks

The request body carries nothing but <scope>. A Classic PUT is a partial update at top-level-section granularity: wire-verified against both a direct instance and the gateway, every non-scope byte comes back identical, a 19 KB configuration profile's <payloads> included. <scope> itself is replaced wholesale, which is why the current scope is read first. One scope add used to cost three GETs and a PUT re-sending the whole document.

After the write the CLI re-fetches and diffs the whole scope it sent against what came back, naming anything missing, matching on name, id or udid because the server augments what it was sent. It previously verified only the category the command touched, so a change the server dropped elsewhere in the scope went unreported.

-n / --dry-run works. It used to fail every time: the preview suppressed the PUT and the check then reported that the server had not persisted it.

Both operations are idempotent. Adding an item that is already present, or removing one that is absent, prints a message to stderr and returns success.

A Classic HTTP error now renders the one useful sentence from its HTML status page instead of ~400 bytes of markup, which is how Error: Unable to match computer group and Error: Duplicate name became readable.

<limit_to_users> and a dead limitations.computer_groups field are gone from the body. The server denormalises <limitations><user_groups> into <limit_to_users> on every write and back on every read, verified in both directions.

If you write your own <scope> XML rather than using these commands, element order is load-bearing. The Classic XML binding is sequence-ordered: it reads scope children in schema order and silently ignores whatever arrives out of it, answering 200 either way. The order a GET returns is not schema order and differs per resource.

Sub-Resource Command Groups

A sub-path that carries PUT, PATCH or DELETE on itself is a separately-writable object and gets a command group of its own, with its own plain verbs. Ten qualify:

Command group Endpoint
pro activation-code organization-name /v1/activation-code/organization-name
pro app-installers global-settings /v1/app-installers/global-settings
pro csa token /v1/csa/token
pro enrollment adue-session-token-settings /v1/adue-session-token-settings
pro local-admin-password settings /v2/local-admin-password/settings
pro managed-software-updates-plans feature-toggle /v1/managed-software-updates/plans/feature-toggle
pro self-service settings /v1/self-service/settings
pro self-service-plus settings /v1/self-service-plus/settings
pro sso-settings cert /v2/sso/cert
pro sso-settings oidc-broker-config /v3/sso/oidc-broker-config
jamf-cli pro csa token get
jamf-cli pro local-admin-password settings get
jamf-cli pro self-service settings get
jamf-cli pro managed-software-updates-plans feature-toggle get
jamf-cli pro enrollment adue-session-token-settings get

SSO settings, the SSO certificate and the OIDC broker

pro sso-settings is the SSO configuration (/v3/sso). The certificate and the OIDC broker configuration are groups beneath it:

Command Sends Returns
pro sso-settings get / update GET/PUT /v3/sso the SSO configuration
pro sso-settings download GET /v3/sso/metadata/download the Jamf Pro SAML metadata file
pro sso-settings cert get / create / update / delete /v2/sso/cert the SSO certificate
pro sso-settings cert download GET /v2/sso/cert/download the certificate keystore
pro sso-settings cert parse POST /v2/sso/cert/parse a parsed keystore, without storing it
pro sso-settings oidc-broker-config get / update GET/PUT /v3/sso/oidc-broker-config the tenant's selected OIDC broker configuration
pro sso-settings failover GET /v1/sso/failover the failover URL

pro sso-settings delete no longer exists. To delete the SSO certificate, run pro sso-settings cert delete.

oidc-broker-config is new in Jamf Pro 11.32.0, so it did not exist under any earlier spelling. get never returns secret fields. update is a full replacement: pipe a full JSON document to stdin, or set individual fields with --set (fetch-merge-replace). --scaffold prints the field template. Omit clientSecret and privateKeyJwt to keep the stored value, and supply either to rotate it. Changing clientAuthMethod in the same request requires the new method's credential, because the authentication service clears the credential belonging to the method being left.

jamf-cli pro sso-settings oidc-broker-config get
jamf-cli pro sso-settings oidc-broker-config update --scaffold
jamf-cli pro sso-settings oidc-broker-config update --set enabled=true

Command groups that return no data

pro csa token, pro local-admin-password settings and pro managed-software-updates-plans feature-toggle are command groups: a bare invocation prints help and exits 0, and one asking for data is refused:

$ jamf-cli pro csa token -o json
{
  "error": "usage",
  "exitCode": 2,
  "exitCodeName": "usage",
  "hint": "run `jamf-cli pro csa token get`",
  "message": "`pro csa token` is a command group and returns no data; it returned data before the sub-resource split"
}

Exit 2. The refusal fires on -o/--output, --field, --select and --out-file. A bare invocation, and a typo beneath it, are unaffected.

Restored Write Operations

The resource's own root keeps create, update and delete. A sub-path's write is qualified by the segment that owns it. 19 such operations ship.

# The customization's own CRUD: POST /v2/enrollment-customizations
cat customization.json | jamf-cli pro enrollment-customization create

# The panel writes
jamf-cli pro enrollment-customization ldap-create <id>        # POST /v1/enrollment-customization/{id}/ldap
jamf-cli pro enrollment-customization ldap-update <id> <panel-id>
jamf-cli pro enrollment-customization ldap-delete <id> <panel-id>
jamf-cli pro enrollment-customization sso-create  <id>        # sso-update / sso-delete likewise
jamf-cli pro enrollment-customization text-create <id>        # text-update / text-delete likewise
jamf-cli pro enrollment-customization all-delete  <id> <panel-id>
Command Endpoint
pro computer-inventory attachments-delete <id> <attachmentId> DELETE /v4/computers-inventory/{id}/attachments/{attachmentId}
pro packages manifest-delete [<id>] DELETE /v1/packages/{id}/manifest
pro venafi proxy-trust-store-create / proxy-trust-store-delete <id> POST/DELETE /v1/pki/venafi/{id}/proxy-trust-store
pro patch-software-title-configurations dashboard-delete [<id>] DELETE /v3/patch-software-title-configurations/{id}/dashboard
pro computer-prestages create-scope / update-scope POST/PUT /v2/computer-prestages/{id}/scope
pro mobile-device-prestages create-scope / update-scope POST/PUT /v2/mobile-device-prestages/{id}/scope

Each path above is wire-confirmed with -v. pro venafi proxy-trust-store-create cannot succeed: it sends application/json where the endpoint declares application/pem-certificate-chain, so it answers 415 Content-Type 'application/json' is not supported before reading the ID. The read (proxy-trust-store) and the delete work; upload the chain through Jamf Pro's own UI until the content type is fixed.

pro enrollment-customization-panels create/update/delete are refused, and the refusal names the panel command to run instead:

$ jamf-cli pro enrollment-customization-panels update 1 2
message: `pro enrollment-customization-panels update` no longer names this operation:
         `enrollment-customization-panels` and `enrollment-customization` are one resource
         now, and its plain verbs are the resource's own
hint:    run `jamf-cli pro enrollment-customization ldap-update`

Exit 2. The panel reads under that spelling (all, ldap, sso, text, markdown, parse-markdown) still work.

--scaffold on commands that take positionals

--scaffold prints the request-body template and sends nothing, so it needs none of the positionals the command declares. The declared ceiling still applies: a scaffold carrying too many positionals is refused (accepts at most 2 arg(s), received 3).

jamf-cli pro enrollment-customization ldap-update --scaffold
jamf-cli pro venafi patch --scaffold

Device Deep-Dive

pro device aggregates one device's record into a single view: identity, hardware, OS, security posture, user info, MDM command history and policy execution logs. Identify the device by Jamf Pro ID, serial number or name; resolution cascades ID → serial → name.

jamf-cli pro device C02X1234                          # By serial number
jamf-cli pro device 42                                # By Jamf ID
jamf-cli pro device "Neil's MacBook"                  # By name
jamf-cli pro device C02X1234 -o json                    # JSON output (alias: dev)

The CLI fetches MDM and policy history in parallel. Under platform gateway auth the report adds blueprint assignments, compliance benchmark status and DDM declaration state. On a partial failure (missing Classic API privileges, say) it warns on stderr and finishes the report.

MDM command history and policy execution rows carry a completion timestamp (dateCompleted) alongside status.

Reports

pro report subcommands aggregate Jamf Pro data into operational summaries and default to table output.

Subcommand Description
security Fleet security posture: FileVault, Gatekeeper, SIP, firewall, OS distribution
patch-status Patch compliance percentages per title; --scan-failures for per-policy failure detail
device-compliance Devices with stale check-ins or outdated OS versions
inventory-summary Hardware model and OS version breakdown
software-installs Installed software version distribution; --title filters, --bundle-id and --path add a column and group by it
ea-results Extension attribute results across devices
policy-status Policy execution health: config findings, failure rates, per-device failure counts
profile-status Configuration profile deployment health
app-status Managed app deployment health
update-status Managed software update deployment status and plan state summary; --scan-failures for per-device failure detail
# Security posture
jamf-cli pro report security
jamf-cli pro report security -o json

# Patch compliance
jamf-cli pro report patch-status
jamf-cli pro report patch-status --scan-failures   # include per-policy failure detail

# Policy health (config issues + failure rates)
jamf-cli pro report policy-status

# Managed software update status (fast: summary tables only)
jamf-cli pro report update-status

# Include per-device failure details (API-expensive)
jamf-cli pro report update-status --scan-failures

# Cap enrichment to 50 devices/plans (random sample)
jamf-cli pro report update-status --scan-failures --limit 50

# Profile and app deployment health
jamf-cli pro report profile-status
jamf-cli pro report app-status

# Fleet compliance and inventory
jamf-cli pro report device-compliance
jamf-cli pro report inventory-summary
jamf-cli pro report software-installs
jamf-cli pro report ea-results

security output sections

  • Security Summary: encryption rate, Gatekeeper/SIP/firewall compliance percentages
  • Flagged Devices: devices with security gaps (missing FileVault, firewall off, etc.)
  • OS Version Distribution: count and percentage per OS version

software-installs flags

Flag Description
--title Filter to application names containing this substring (case-insensitive)
--include-system Include system apps from /System/ and /Library/ (excluded by default)
--bundle-id Add a bundle_id column and group by bundle ID
--path Add a path column and group by install path

Default columns are title, version, device_count. --bundle-id and --path each add a column and join the grouping, so one title and version spanning several bundle IDs or install paths reports a row per value.

jamf-cli pro report software-installs --title Safari -o csv
# device_count,title,version
# 1,Safari.app,26.0.1
# 2,Safari.app,17.5

jamf-cli pro report software-installs --title Safari --bundle-id --path -o csv
# bundle_id,device_count,path,title,version
# com.apple.Safari,1,/Applications/Safari.app,Safari.app,26.0.1
# com.apple.Safari,2,/Applications/Safari.app,Safari.app,17.5

Without these flags, two apps sharing a display name (a relocated copy, or a repackaged vendor app under a different bundle ID) merge into one row.

patch-status flags

Flag Description
--scan-failures Also fetch patch policy failure counts and device-level failure detail

update-status flags

Flag Default Description
--scan-failures false Enrich error devices and failed plans with inventory details and plan events (API-expensive: fetches full computer and mobile inventory, plus one events call per failed plan)
--limit smart Max failures to enrich per table. -1 (default): max(10%, 100); 0: all; N: explicit cap (random sample)

Without --scan-failures, update-status returns summary tables only and prints a stderr hint if failures exist.

Report aggregation in multi mode

Run report commands through multi and it aggregates output from all instances: summaries are combined, and detail rows from each instance merge into one table with a profile column added. --sequential gives per-instance output.

# Aggregate patch compliance across all MSP instances
jamf-cli multi --filter 'pro-*' -- pro report patch-status

# See each instance's output separately
jamf-cli multi --filter 'pro-*' --sequential -- pro report security

Group Tools

group-tools inspects computer groups: filtering, membership listing, unused-group detection and export.

# List all computer groups with optional filters
jamf-cli pro group-tools list
jamf-cli pro group-tools list --type smart                    # Only smart groups
jamf-cli pro group-tools list --type static                   # Only static groups
jamf-cli pro group-tools list --empty                         # Groups with zero members
jamf-cli pro group-tools list --name-pattern "test"           # Case-insensitive name filter

# Show members of a computer group by name
jamf-cli pro group-tools members "All Managed Clients"
jamf-cli pro group-tools members "All Managed Clients" -o csv

# Analyze computer groups for hygiene issues
jamf-cli pro group-tools analyze
jamf-cli pro group-tools analyze --unused                     # Unreferenced groups that also hold no members

# Export all computer group definitions
jamf-cli pro group-tools export
jamf-cli pro group-tools export --format yaml --out-file groups.yaml

Every group reported 0 members before v1.31.1. Earlier releases read a field /v1/computer-groups does not return, so list --empty listed the whole instance and pro audit never emitted its Empty smart groups finding on any tenant. The counts now come from the two v3 collections that carry one.

A count the CLI could not read reads unknown, not 0. --empty and --unused take only the groups proved empty and report on stderr how many they skipped. --unused names removal candidates, so a group holding members no longer appears in it.

Device Actions

Device action commands live under pro computers (the computer-inventory resource) and pro mobile-devices. They target devices by serial number, name, ID, group, or file. Destructive operations sit behind extra confirmation flags.

A destructive generated action sends its own HTTP method and quotes its own action name. A bulk pro mobile-device-groups erase --from-file ids.txt sends POST /api/v2/mobile-device-groups/{id}/erase, wire-confirmed. A DELETE prompt reads This will delete …; any other action quotes its own name:

$ jamf-cli pro mobile-device-groups erase 67
⚠️  This will run "erase" on mobile-device-group 67. Type 'yes' to confirm:

$ jamf-cli pro mobile-device-groups erase --from-file ids.txt
⚠️  This will run "erase" on 2 mobile-device-groups. Type 'yes' to confirm:

$ jamf-cli pro mobile-device-groups erase --from-file ids.txt --dry-run
[dry-run] Would run "erase" on mobile-device-group "67" (id: 67)
[dry-run] Would run "erase" on mobile-device-group "66" (id: 66)

The --from-file and --group help text matches. The hand-written pro computers/pro mobile-devices actions below quote their own operation too: ⚠️ This will remove-mdm computer "FVFC41HCLYWP" (serial: FVFC41HCLYWP, id: 5).

Most MDM device actions are refused on a platform gateway profile, exit code 8. They go through POST /v2/mdm/commands, which the gateway's published Jamf Pro API omits: GET is declared on that path, POST is not. Add a second oauth2 profile pointed at the Jamf Pro instance for them.

Refused on a gateway profile Still served
pro computers lock, restart, shutdown, enable-remote-desktop, disable-remote-desktop, set-recovery-lock, set-auto-admin-password, settings blank-push, redeploy-framework, ddm-sync, renew-mdm, remove-mdm, erase, flush-commands
pro mobile-devices lock, restart, shutdown, enable-lost-mode, disable-lost-mode, play-lost-mode-sound, clear-passcode, clear-restrictions-password, delete-user, log-out-user, unlock-user-account, apply-redemption-code, refresh-cellular-plans, request-mirroring, stop-mirroring, settings erase, unmanage, update-inventory, flush-commands

erase and remove-mdm on computers are hand-written commands on a published path, so they survive. pro mdm commands, the generic command-issuing endpoint, is refused.

Computer actions

# Send a blank push to trigger check-in
jamf-cli pro computers blank-push --serial C02X1234

# Redeploy the Jamf management framework
jamf-cli pro computers redeploy-framework --serial C02X1234

# Force a Declarative Device Management sync
jamf-cli pro computers ddm-sync --serial C02X1234

# Renew the MDM profile
jamf-cli pro computers renew-mdm --serial C02X1234

# Remove the MDM profile (destructive)
jamf-cli pro computers remove-mdm --serial C02X1234 --yes

# Erase a computer (destructive)
jamf-cli pro computers erase --serial C02X1234 --yes

# Erase with a Find My PIN (from a JSON body file)
jamf-cli pro computers erase --serial C02X1234 --yes --body-file erase-body.json

# Print the JSON template for erase body
jamf-cli pro computers erase --scaffold

# Lock a computer (destructive)
jamf-cli pro computers lock --serial C02X1234 --yes --confirm-destructive

# Enable/disable Remote Desktop
jamf-cli pro computers enable-remote-desktop --serial C02X1234 --yes
jamf-cli pro computers disable-remote-desktop --serial C02X1234 --yes

# Restart a computer
jamf-cli pro computers restart --serial C02X1234 --yes

# Shut down a computer
jamf-cli pro computers shutdown --serial C02X1234 --yes

# Set or clear the Recovery Lock password (Apple Silicon / T2)
jamf-cli pro computers set-recovery-lock --serial C02X1234 --yes

# Flush failed MDM commands (default: leaves pending commands in the queue)
jamf-cli pro computers flush-commands --serial C02X1234 --yes

# Flush both pending and failed commands
jamf-cli pro computers flush-commands --serial C02X1234 --status both --yes

# Flush failed commands from all members of a group
jamf-cli pro computers flush-commands --group "Problem Devices" --yes

# Preview without executing
jamf-cli pro computers flush-commands --serial C02X1234 --dry-run

Mobile device actions

# Erase a mobile device (destructive)
jamf-cli pro mobile-devices erase --serial F4K3SER1AL --yes

# Unmanage a mobile device (destructive)
jamf-cli pro mobile-devices unmanage --serial F4K3SER1AL --yes

# Restart a mobile device
jamf-cli pro mobile-devices restart --serial F4K3SER1AL --yes

# Shut down a mobile device
jamf-cli pro mobile-devices shutdown --serial F4K3SER1AL --yes

# Request an inventory update (Classic API; no modern equivalent)
jamf-cli pro mobile-devices update-inventory --serial F4K3SER1AL --yes

# Lock a mobile device (destructive)
jamf-cli pro mobile-devices lock --serial F4K3SER1AL --yes --confirm-destructive

# Clear the passcode
jamf-cli pro mobile-devices clear-passcode --serial F4K3SER1AL --yes

# Enable/disable Lost Mode
jamf-cli pro mobile-devices enable-lost-mode --serial F4K3SER1AL --yes
jamf-cli pro mobile-devices disable-lost-mode --serial F4K3SER1AL --yes

# Play the Lost Mode sound
jamf-cli pro mobile-devices play-lost-mode-sound --serial F4K3SER1AL --yes

# Clear the restrictions password
jamf-cli pro mobile-devices clear-restrictions-password --serial F4K3SER1AL --yes

# Flush failed MDM commands (default: leaves pending commands in the queue)
jamf-cli pro mobile-devices flush-commands --serial F4GH5678 --yes

# Flush both pending and failed commands
jamf-cli pro mobile-devices flush-commands --serial F4GH5678 --status both --yes

# Flush failed commands from all members of a group
jamf-cli pro mobile-devices flush-commands --group "Problem iPads" --yes

# Preview without executing
jamf-cli pro mobile-devices flush-commands --serial F4GH5678 --dry-run

Targeting flags

Exactly one targeting flag is required. They are mutually exclusive.

Flag Description
--serial Target a single device by serial number
--name Target a single device by name
--id Target a single device by Jamf Pro ID
--group Target all members of a smart or static group
--from-file Target multiple devices from a file (one serial or ID per line; blank lines and # comments ignored)

Bulk targeting

--group and --from-file enable bulk mode. The CLI lists every resolved device before executing.

# Blank push to all members of a group
jamf-cli pro computers blank-push --group "All Macs" --yes

# Redeploy framework from a file of serials
jamf-cli pro computers redeploy-framework --from-file devices.txt --yes

A --from-file line resolving to no device is named on stderr, skipped, and counted as a failure in the summary and the exit code; --allow-partial-failure downgrades that to a warning. A file where nothing resolves fails. Lookups batch at roughly one request per 100 entries.

Safety gates for destructive bulk operations

Destructive actions (erase, remove-mdm, unmanage) in bulk mode require both --yes and --confirm-destructive:

# Blocked: --confirm-destructive is required for bulk erase
jamf-cli pro computers erase --group "Decommissioned" --yes
# ERROR: --confirm-destructive is required for bulk destructive operations

# This works
jamf-cli pro computers erase --group "Decommissioned" --yes --confirm-destructive

# Unmanage retired iPads
jamf-cli pro mobile-devices unmanage --group "Retired iPads" --yes --confirm-destructive

Flush MDM Command Queue

flush-commands removes pending and/or failed MDM commands from a device's queue. Available on both computers and mobile-devices.

Flag Description
--status Commands to flush: failed (default), pending, or both
--yes Skip confirmation prompt (required when --no-input is set)
-n, --dry-run Preview without executing

Targeting accepts --serial, --name, --id, or --group. --from-file is not supported: flush targets one device or one group per call.

--status failed (the default) removes stuck commands and leaves unattempted commands in the queue. Use pending or both only to clear the whole queue.

Group flush requires --yes and offers no interactive prompt.

In CI/CD (--no-input), --yes is required:

jamf-cli pro computers flush-commands --serial C02X1234 --no-input --yes

Jamf Protect Deployment Tasks

Jamf Protect deployment tasks are the per-computer install and verification records Jamf Pro creates when a Jamf Protect plan (a configuration profile) deploys to devices through the tenant's Cloud Services Connection.

Plans and deployment tasks are one resource, pro jamf-protect (alias jp). pro jamf-protect-plans and pro jamf-protect-deployment-tasks are retiring aliases of it. Its verbs: list (plans), get, create, update, delete, sync, tasks, retry, retry-failed, history, add-history-note.

# Find the deployment UUID for a Protect plan (the "uuid" field)
jamf-cli pro jamf-protect list

# List raw deployment tasks for that deployment, all statuses
jamf-cli pro jamf-protect tasks <deployment-uuid>

retry-failed

retry-failed retries failed install tasks for a deployment, resolving a target device (or the whole deployment) to task IDs for you. The generated pro jamf-protect retry <deployment-id> takes task IDs directly and does no lookup.

# Retry the failed task for one computer, by serial number
jamf-cli pro jamf-protect retry-failed <deployment-uuid> --serial C02X1234ABCD

# Retry by management ID or UDID
jamf-cli pro jamf-protect retry-failed <deployment-uuid> --management-id 6f2c1e3a-...
jamf-cli pro jamf-protect retry-failed <deployment-uuid> --udid 00008030-001A2D...

# Retry every failed task in the deployment (requires --yes)
jamf-cli pro jamf-protect retry-failed <deployment-uuid> --all-failed --yes

# Retry specific deployment task IDs directly, skipping lookup
jamf-cli pro jamf-protect retry-failed <deployment-uuid> --task-ids 82,83

# Preview without executing
jamf-cli pro jamf-protect retry-failed <deployment-uuid> --all-failed --dry-run
Flag Description
--serial Target one computer by serial number
--management-id Target one computer by management ID (UUID)
--udid Target one computer by UDID
--all-failed Retry every failed task in the deployment (requires --yes)
--task-ids Retry specific deployment task IDs directly (advanced; skips lookup)
--include-succeeded With --serial/--management-id/--udid, match that computer's task regardless of status

Exactly one of --serial, --management-id, --udid, --all-failed, or --task-ids is required. <deployment-uuid> is the Protect plan's deployment UUID (the uuid field from pro jamf-protect list), not the config profile's numeric ID. Only --all-failed requires --yes; single-device targeting and --task-ids run without confirmation.

Note: the Jamf Pro API's status filter on this endpoint returns HTTP 500 for any value, so retry-failed fetches every task for the deployment unfiltered and matches failed tasks (status == GAVE_UP) client-side.

retry-failed --help still prints the retired spellings in its description and examples (jamf-cli pro jamf-protect-plans list, jamf-cli pro jamf-protect-deployment-tasks retry-failed …). Those resolve and warn, so the examples run. Use the names above.

Multi-Instance Commands

jamf-cli multi pro overview                                  # Interactive profile selection
jamf-cli multi --filter 'pro-*' -- pro computers list -o table    # Glob filter
jamf-cli multi --profiles a,b,c -- pro overview              # Explicit list
jamf-cli multi --from-file instances.txt -- pro computers list    # From file (names or URLs)
jamf-cli multi --filter 'pro-*' -- pro report patch-status   # Aggregated report across instances
jamf-cli multi --filter 'pro-*' --sequential -- pro overview # Per-instance output (no aggregation)

multi aggregates report commands (pro report *); see Report aggregation in multi mode.

Backup

pro backup exports configuration objects from a Jamf Pro instance to a local directory, one YAML or JSON file per object, stripping server-generated fields (IDs, timestamps) by default for clean version-control diffs. On a partial failure it records the failed objects in _failures.yaml and carries on.

# Export all resources to a directory
jamf-cli pro backup --output ./jamf-backup

# Export as JSON instead of YAML
jamf-cli pro backup --output ./jamf-backup --format json

# Export a subset of resources
jamf-cli pro backup --output ./jamf-backup --resources policies,scripts

# Retain server-generated IDs in output
jamf-cli pro backup --output ./jamf-backup --include-ids

# Control concurrency (default: 3, ceiling 10)
jamf-cli pro backup --output ./jamf-backup --concurrency 5

# Also download JCDS-hosted package binaries (not just metadata)
jamf-cli pro backup --output ./jamf-backup --download-packages

# List the tokens --resources accepts
jamf-cli pro backup list-resources

Backup flags

Flag Default Description
--output (required) Destination directory
--format yaml Output format: yaml or json
--resources (all) Comma-separated resource filter (e.g., policies,scripts). Tokens: pro backup list-resources. Shell-completes.
--include-ids false Retain server-generated IDs in output
--concurrency 3 Max parallel API requests (ceiling 10)
--download-packages false Also download package binaries hosted on JCDS to packages/files/ (v1.23.0+)

list-resources (v1.29.0+)

pro backup list-resources prints every token --resources accepts, on pro backup and on pro diff, from the same table the flag validates against. It needs no credentials and writes nothing to disk.

jamf-cli pro backup list-resources            # table: resource, source
jamf-cli pro backup list-resources -o json    # adds an `objects` column: the backing commands
jamf-cli pro backup list-resources -o csv --out-file tokens.csv

25 tokens as of v1.29.0:

Token Source
accounts classic api
advanced-searches classic api, pro api
blueprints platform sdk
buildings pro api
categories pro api
compliance-benchmarks platform sdk
departments pro api
disk-encryption classic api
dock-items classic api
extension-attributes classic api, pro api
inventory-preloads csv download
mac-apps classic api
mobile-apps classic api
network-segments classic api
packages classic api
patch-titles pro api
policies classic api
prestages pro api
printers classic api
profiles classic api
restricted-software classic api
scripts pro api
sites pro api
smart-groups pro api
static-groups pro api

--output on list-resources is the root format flag; the destination directory pro backup --output takes one level up is local to pro backup. An unrecognised value renders a table, so list-resources --output ./tokens.txt prints the listing and creates no file; use --out-file. --wide changes nothing: objects is not part of the table shape.

advanced-searches covers both halves in one token: advanced computer searches from the Classic API, advanced mobile device searches from the Pro API. Smart groups are a separate object, captured under smart-groups.

A wrong --resources value

A filter where nothing matches fails:

$ jamf-cli pro backup --output ./out --resources advanced-search
no resources match filter "advanced-search"    # exit 1

An unrecognised token alongside a recognised one is ignored: --resources policies,bogus backs up policies and says nothing about bogus. Check spelling against list-resources, or let completion fill it in.

A stray positional is refused: pro backup /tmp/out exits 2 and writes nothing, the destination directory belonging to --output. A subcommand typo is refused the same way, with a suggestion:

$ jamf-cli pro backup list-resourcez
unknown command "list-resourcez" for "jamf-cli pro backup"

Did you mean this?
	list-resources
hint: run jamf-cli pro backup --help to list its subcommands

jamf-cli pro backup --resources inventory-preloads exports inventory preload data as inventory-preloads/inventory-preload-all.csv, downloaded as a single CSV from /v2/inventory-preload/csv.

prestages backs up computer and mobile device prestage enrollments and, for each, fetches its per-ID device scope endpoint and embeds the sorted assigned serial numbers under a scope key, so a prestage's config and its device assignments land in one diff-friendly file.

--download-packages also downloads the package binaries backing the packages resource, into packages/files/. Only packages hosted on the Jamf Cloud Distribution Service (JCDS) can be downloaded this way; the backup skips packages on other distribution points (on-prem file share, third-party cloud) with a warning.

Diff

diff compares configuration objects between two sources. Each source can be a config profile name (live instance) or a local backup directory.

# Compare two live instances
jamf-cli pro diff --source staging --target production

# Compare a backup to a live instance
jamf-cli pro diff --source ./backup-2026-01 --target production

# Compare two backup directories
jamf-cli pro diff --source ./old-backup --target ./new-backup

# Filter to specific resource types
jamf-cli pro diff --source staging --target production --resources policies,scripts

Output shows added, removed, and modified resources. Modified entries include the field name, old value, and new value.

Note: diff needs no auth when both sources are local backup directories. With either source a profile name, auth resolves normally.

Diff flags

Flag Description
--source Source: config profile name or backup directory path (required)
--target Target: config profile name or backup directory path (required)
--resources Comma-separated resource filter (e.g., policies,scripts). Same vocabulary as pro backup: pro backup list-resources. Shell-completes.

pro diff takes no positionals. pro diff staging production exits 2 and names the unset flags: "jamf-cli pro diff" takes no positional arguments, but got "staging" (required flag(s) "source", "target" not set).

Bulk Delete

All generated delete commands support bulk mode via --from-file (a file of IDs, names, or alternate identifiers) or --group (computer-inventory and mobile-devices only).

# Delete computers from a file of serial numbers
jamf-cli pro computer-inventory delete --from-file decommissioned.txt --yes

# Delete all members of a computer group
jamf-cli pro computer-inventory delete --group "Decommissioned Macs" --yes

# Delete all members of a mobile device group
jamf-cli pro mobile-devices delete --group "Retired iPads" --yes

# Delete scripts by name from a file
jamf-cli pro scripts delete --from-file old-scripts.txt --yes

# Preview without executing
jamf-cli pro buildings delete --from-file buildings.txt --dry-run
# decommissioned.txt
C02X1234
C02Y5678
42
Neil's MacBook

For the full flag reference, see CLI Patterns#Bulk delete (--from-file and --group).

Note: mobile-devices delete uses the Classic API under the hood, because the modern mobile devices API has no DELETE operation.

Bulk Operations

pro bulk performs bulk mutations across policies, computer groups and MDM commands. It defaults to dry-run: nothing changes without --yes.

Enable/disable policies

Filters are combined with AND logic. Within a repeatable flag, values use OR logic.

# Preview which policies match the filter
jamf-cli pro bulk enable-policies --category Security

# Enable matching policies
jamf-cli pro bulk enable-policies --category Security --yes

# Disable policies matching a name pattern
jamf-cli pro bulk disable-policies --name-pattern "Legacy*" --yes

# Filter by target scope (group, building, department, or all-computers)
jamf-cli pro bulk disable-policies --scope-group "Test Machines" --yes
jamf-cli pro bulk disable-policies --scope-building "London" --yes
jamf-cli pro bulk disable-policies --scope-department "IT" --yes
jamf-cli pro bulk enable-policies --all-computers --yes

# Filter by limitation or exclusion scope
jamf-cli pro bulk enable-policies --limit-network-segment "Corporate" --yes
jamf-cli pro bulk disable-policies --exclude-group "Dev Macs" --yes

# Combine filters: AND logic (must match all)
jamf-cli pro bulk disable-policies \
  --category "Legacy" \
  --scope-group "Test Machines" \
  --exclude-group "Exclude Always" \
  --yes

Scope filter flags

Flag Section Description
--scope-group Target Computer group in targets (repeatable)
--scope-building Target Building in targets (repeatable)
--scope-department Target Department in targets (repeatable)
--all-computers Target Scoped to all computers
--limit-network-segment Limitations Network segment in limitations (repeatable)
--limit-user-group Limitations User group in limitations (repeatable)
--exclude-group Exclusions Computer group in exclusions (repeatable)
--exclude-building Exclusions Building in exclusions (repeatable)
--exclude-department Exclusions Department in exclusions (repeatable)

Add/remove from groups

--target-group names the static group to modify. The targets come from either --from-file (one computer ID or serial per line) or --group (the members of another computer group). The two are mutually exclusive.

# Add a list of computers to a static group (dry-run)
jamf-cli pro bulk add-to-group --target-group "Quarantine" --from-file serials.txt

# Execute
jamf-cli pro bulk add-to-group --target-group "Quarantine" --from-file serials.txt --yes

# Use another group's members as the targets
jamf-cli pro bulk add-to-group --target-group "Quarantine" --group "Failed Patch Macs" --yes

# Remove from a static group
jamf-cli pro bulk remove-from-group --target-group "Quarantine" --from-file serials.txt --yes

Serials in --from-file resolve to computer IDs first. Unresolved lines behave as under Bulk targeting: named on stderr, skipped, counted as failures, tolerated with --allow-partial-failure. When nothing resolves, or the file is empty, the command fails and changes nothing. send-command behaves the same.

Send MDM command to a group

# Preview: no changes
jamf-cli pro bulk send-command --command BlankPush --group "All Macs"

# Execute
jamf-cli pro bulk send-command --command BlankPush --group "All Macs" --yes

# Destructive command (requires --confirm-destructive)
jamf-cli pro bulk send-command --command EraseDevice --group "Decommissioned" --yes --confirm-destructive

File Uploads

Files are uploaded in two different ways, depending on the resource.

File-based fields on create / update / apply

Most file-carrying resources use a dedicated file flag on create, update and apply. The file's contents populate one body field; supply the metadata (name, category, scope) in the JSON/XML body. The CLI handles encoding, companion fields and name fallback. The file flag overrides a target field already in the body; companion fields and name fallback fill only when absent.

Resource Flag Field populated
scripts --script-file scriptContents
computer-extension-attributes --script-file scriptContents (only for SCRIPT inputType)
volume-purchasing-locations --token-file serviceToken (raw; .vpptoken is already base64-encoded JSON). Also works on patch.
device-enrollments --token-file encodedToken + auto-fills tokenFileName. Use apply --name <name> --token-file <path> to create or rotate a DEP token in one step; apply composes the token upload and the metadata PUT for you.
classic-macos-config-profiles --mobileconfig-file <general><payloads> (CDATA-wrapped; PayloadUUID preserved on update). Or use --custom-payload-file to build the payload from a preference plist; see Configuration Profiles.
classic-mobile-config-profiles --mobileconfig-file <general><payloads> (CDATA-wrapped; PayloadUUID preserved on update)
classic-mac-apps --appconfig-file <app_configuration><preferences> (fetch-merge-put; other fields untouched)
classic-mobile-apps --appconfig-file <app_configuration><preferences> (fetch-merge-put; other fields untouched)

Prefer apply for file-field workflows: it creates or replaces by name in one call, so you need no exists-check.

# Apply (upsert) a script by name: create or replace
echo '{"name":"Deploy Agent"}' \
  | jamf-cli pro scripts apply --script-file /path/to/deploy.sh --yes

# Extension attribute populated from a script file (SCRIPT inputType).
# --scaffold prints the JSON template for the rest of the body.
jamf-cli pro computer-extension-attributes apply --scaffold

# Apply (upsert) a VPP location by name
jamf-cli pro volume-purchasing-locations apply --name "ACME Engineering" \
  --token-file /path/to/acme.vpptoken --yes

# Patch a VPP location's token (merge-patch; other fields untouched)
jamf-cli pro volume-purchasing-locations patch --name "ACME Engineering" \
  --token-file /path/to/acme.vpptoken

# Create or rotate a DEP server token
jamf-cli pro device-enrollments apply --name "ACME Production" \
  --token-file /path/to/server_token.p7m --yes

# Apply (upsert) a macOS configuration profile from a .mobileconfig file.
# On replace, the existing PayloadUUID is preserved so the profile renews cleanly.
cat > profile-meta.xml <<EOF
<os_x_configuration_profile>
  <general>
    <name>Wi-Fi Settings</name>
    <category><name>Network</name></category>
  </general>
</os_x_configuration_profile>
EOF
jamf-cli pro classic-macos-config-profiles apply \
  --from-file profile-meta.xml \
  --mobileconfig-file /path/to/wifi.mobileconfig \
  --yes

# Update AppConfig for an existing Mac App by name: scope, category and
# every other attribute are preserved (fetch-merge-put)
jamf-cli pro classic-mac-apps apply \
  --name "Slack" \
  --appconfig-file /path/to/slack-appconfig.plist \
  --yes

create and update are generated for each of these resources. patch changes one field without re-submitting the whole body: rotating a VPP token without re-sending the location name and country code, say.

Upload subcommands (attachments and binary files)

Resources with a dedicated upload endpoint expose an upload subcommand, which sends a binary file to a separate upload URL.

A .pkg upload through a platform gateway profile is refused. The gateway sits behind a CDN/WAF that inspects .pkg content and blocks it before it reaches Jamf Pro. Upload through an instance profile (auth-method: oauth2 or token). The CLI reports it as:

This is the gateway's CDN/WAF, not Jamf and not your API privileges, so no role change
will help. Known triggers: "file://" anywhere in the request body (a legitimate value in
some Classic payloads), .pkg upload content, and a burst of writes. The response cannot
say which one fired. There is no client-side fix — retry a single request cold, and
report it to Jamf.

The response is CloudFront's own error page: it names no rule and carries no trace ID. There is no client-side workaround.

# Upload a package file
jamf-cli pro packages upload --file /path/to/app.pkg

# Upload with a custom display name
jamf-cli pro packages upload --file /path/to/app.pkg --name "My App 1.2"

# Upload to a specific category
jamf-cli pro packages upload --file /path/to/app.pkg --category-id 5

# Set installation priority (1–20; default: 10)
jamf-cli pro packages upload --file /path/to/app.pkg --priority 1

# Replace existing package with the same name (skip confirmation)
jamf-cli pro packages upload --file /path/to/app.pkg --yes

# Other file-bearing resources with dedicated upload endpoints
jamf-cli pro icon upload --file /path/to/icon.png
jamf-cli pro inventory-preload upload --file /path/to/preload.csv
jamf-cli pro computer-inventory upload --serial C02X1234 --file /path/to/attachment.pdf
jamf-cli pro computer-extension-attributes upload --file /path/to/script.sh
jamf-cli pro enrollment-customization-images upload --file /path/to/branding.png
jamf-cli pro mobile-device-prestages upload --name "Standard iPad" --file /path/to/image.png
jamf-cli pro self-service upload --file /path/to/logo.png

Note: For scripts and configuration profiles, use the file-based field flags (--script-file, --mobileconfig-file) on create/update/apply; they give you control over each body field (category, scope, priority). pro scripts upload and pro classic-*-config-profiles upload no longer exist.

Sync packages from cloud storage

packages sync (also available as jcds sync) mirrors the Jamf Cloud Distribution Service (JCDS) to a local directory. It downloads missing files, re-downloads files whose SHA3-512 checksum has changed, and with --delete removes local files no longer on JCDS. Built for scheduled runs on file-share distribution points.

# Sync to /Volumes/Packages (no local file is deleted)
jamf-cli pro packages sync --dir /Volumes/Packages

# Sync and remove local files not present on JCDS
jamf-cli pro packages sync --dir /Volumes/Packages --delete

# Preview what would change without modifying anything
jamf-cli pro packages sync --dir /Volumes/Packages --delete --dry-run

# Increase parallel download concurrency (default: 4)
jamf-cli pro packages sync --dir /Volumes/Packages --concurrency 8

Output is a JSON report with a summary and a files array:

# Show summary counts
jamf-cli pro packages sync --dir /Volumes/Packages | jq '.summary'

# Show only failed files
jamf-cli pro packages sync --dir /Volumes/Packages \
  | jq '[.files[] | select(.status == "failed")]'
Flag Default Description
--dir (required) Local directory to sync into
--delete false Delete local files not present on JCDS
--dry-run / -n false Preview changes without executing
--concurrency 4 Number of parallel downloads

File status values in the report: downloaded, updated, skipped, deleted, failed (live run); would-download, would-update, would-delete (dry run).

Download a single JCDS file

jcds download retrieves a single file from the Jamf Cloud Distribution Service by fetching a pre-signed URL and streaming it to disk.

# Download by filename (output path defaults to the filename)
jamf-cli pro jcds download MyPackage.pkg

# Download to an explicit output path
jamf-cli pro jcds download MyPackage.pkg --output /tmp/MyPackage.pkg

# Equivalent using --name
jamf-cli pro jcds download --name MyPackage.pkg
Flag Description
--name Name of the file to download (alternative to positional argument)
--output / -O Output file path (default: <fileName> in the current directory)

Additional MDM Commands

pro computers settings, pro computers set-auto-admin-password and pro mobile-devices settings are refused on a gateway profile. See the table under Device Actions. They need an instance (oauth2 or token) profile.

Computer settings & admin password

# Configure device settings (bluetooth, timezone, software update cadence)
jamf-cli pro computers settings --serial C02X1234 --yes

# Set the auto admin password (LAPS); password via --password-file or interactive prompt
jamf-cli pro computers set-auto-admin-password --serial C02X1234 --password-file /path/to/pw --yes

Mobile device commands

# Device settings (default browser, bluetooth, device name, roaming, timezone, etc.)
jamf-cli pro mobile-devices settings --serial F4K3SER1AL --yes

# AirPlay mirroring
jamf-cli pro mobile-devices request-mirroring --serial F4K3SER1AL --yes
jamf-cli pro mobile-devices stop-mirroring --serial F4K3SER1AL --yes

# eSIM management
jamf-cli pro mobile-devices refresh-cellular-plans --serial F4K3SER1AL --yes

# App Store
jamf-cli pro mobile-devices apply-redemption-code --serial F4K3SER1AL --yes

# Shared iPad user management
jamf-cli pro mobile-devices delete-user --serial F4K3SER1AL --yes
jamf-cli pro mobile-devices log-out-user --serial F4K3SER1AL --yes
jamf-cli pro mobile-devices unlock-user-account --serial F4K3SER1AL --yes

Classic API Commands

Resources with no modern API equivalent are available via the Classic API (/JSSResource/), under the classic- prefix.

Classic resources support list, get, create, update and delete, with --name for name-based lookups and apply for name-based upsert where available. Some carry a subset: classic-accounts is list-only, classic-computer-history and classic-mobile-history are get-only. classic-computer-app-usage (see above) carries its date range in the URL path and has no list/get pair.

Run jamf-cli commands -o table for the full list. Areas covered: computer and mobile device management, policies, configuration profiles, packages, patch management, network segments, printers, removable MAC addresses, administration.

Removable MAC Addresses

# List all removable MAC address entries
jamf-cli pro classic-removable-mac-addresses list

# Get a specific entry by ID
jamf-cli pro classic-removable-mac-addresses get 1

# Get by name
jamf-cli pro classic-removable-mac-addresses get --name "Corporate Dock"

# Create or update by name
cat entry.xml | jamf-cli pro classic-removable-mac-addresses apply --yes

# Delete by name
jamf-cli pro classic-removable-mac-addresses delete --name "Old Dock" --yes

eBooks (Classic API)

classic-ebooks exposes full CRUD plus scope management for eBook resources.

# List all eBooks
jamf-cli pro classic-ebooks list -o table

# Get an eBook by ID or name
jamf-cli pro classic-ebooks get 5
jamf-cli pro classic-ebooks get --name "iPad User Guide"

# Create or replace by name
cat ebook.xml | jamf-cli pro classic-ebooks apply --yes

# Delete by name
jamf-cli pro classic-ebooks delete --name "Outdated Guide" --yes

# Scope management: an id, or --name (see Scope Management)
jamf-cli pro classic-ebooks scope get 5
jamf-cli pro classic-ebooks scope get --name "iPad User Guide"
jamf-cli pro classic-ebooks scope add --name "iPad User Guide" --mobile-device-group "All iPads"
jamf-cli pro classic-ebooks scope remove 5 --section exclusion --building "London"

classic-ebooks is the one resource whose scope holds both computers and mobile devices, and the only one carrying --class. See Scope Management for the category matrix and for why --class sends two requests.

JSS User Groups (Classic API)

classic-user-groups manages JSS user groups. These groups are referenced in policy and eBook scope limitations.

# List all JSS user groups
jamf-cli pro classic-user-groups list -o table

# Get a group by ID or name
jamf-cli pro classic-user-groups get 3
jamf-cli pro classic-user-groups get --name "Staff"

# Create or replace by name
cat user-group.xml | jamf-cli pro classic-user-groups apply --yes

# Delete by name
jamf-cli pro classic-user-groups delete --name "Old Group" --yes

LDAP Servers (Classic API)

classic-ldap-servers manages on-prem LDAP server connections (connection settings plus attribute mappings) via the Classic API, and is the only route to create, update or delete an LDAP server from the CLI. classic-ldaps get is a read-only mappings view.

# List all LDAP servers
jamf-cli pro classic-ldap-servers list -o table

# Get by ID or name
jamf-cli pro classic-ldap-servers get 3
jamf-cli pro classic-ldap-servers get --name "Corporate AD"

# Create or replace by name
cat ldap-server.xml | jamf-cli pro classic-ldap-servers apply --yes

# Delete by name
jamf-cli pro classic-ldap-servers delete --name "Decommissioned LDAP" --yes

Optimistic Locking (Prestages)

Prestage resources (computer-prestages, mobile-device-prestages) carry a versionLock field in their JSON body. On update, apply and the scope operations (create-scope, update-scope, delete-multiple), the CLI GETs the current resource and injects every versionLock it finds, nested ones under locationInformation, purchasingInformation and accountSettings included. A create through apply gets 0. You set no versionLock yourself.

pro computer-prestages create-scope 1 sends POST /v2/computer-prestages/1/scope and update-scope sends the PUT, each carrying the fetched versionLock. On the mobile side the two bulk deletes are scope-delete-multiple (POST /v2/mobile-device-prestages/{id}/scope/delete-multiple) and attachments-delete-multiple (POST /v3/mobile-device-prestages/{id}/attachments/delete-multiple); a bare delete-multiple refuses and names both replacements.

Export a prestage, edit it and pipe it back to update or apply: the CLI replaces the stale versionLock in your export with the current server value. On a concurrent modification between the CLI's GET and PUT, the server returns HTTP 409 Conflict.

Auth Token

Print a valid Jamf Pro access token, refreshing it if it has expired. Use it to debug API calls with curl, or to feed a token to another tool without running the OAuth2 flow yourself.

# Print token as JSON (includes expiry for oauth2/platform auth)
jamf-cli pro auth token

# Extract just the token string for scripting
jamf-cli pro auth token --field token

# Use in a curl command
curl -H "Authorization: Bearer $(jamf-cli pro auth token --field token)" \
  https://jamf.company.com/api/v1/buildings

# Force a fresh token exchange (ignores cache)
jamf-cli pro auth token --refresh

Output:

{
  "token": "eyJhbGciOi...",
  "expires_at": "2026-04-15T15:30:00Z"
}

With --auth-method token (pre-existing bearer token) the CLI omits expires_at: no expiry information is available.

Flag Description
--refresh Force a new token exchange, ignoring any cached token

Gateway Profiles: Refused Commands

On a platform gateway profile (auth-method: platform), 59 pro command entries are refused before a request is sent, with exit code 8 (Refused by policy). They sit outside the gateway's published Jamf Pro or Classic API. Several still answer today, on transitional routes.

List them from the binary in hand:

jamf-cli commands -o json \
  | jq -r '.[] | select(.gateway=="unserved") | .command + "\t" + .gatewayBasis'

A refused row carries gateway ("unserved"), gatewayBasis (unpublished for every current entry, meaning the published spec does not declare it) and gatewayDetail (the spec version that omits it, or the method it omits).

Verified on a live gateway profile (-p platform-mockingbird, EU, environment-scoped):

Command Result
pro api-roles list exit 8, not part of the Jamf Platform gateway's published API
pro environment-type get exit 8, same
pro mdm commands exit 8, detail "The gateway's Jamf Pro API 11.32.0 declares GET on this path but not POST."
pro mobile-devices lock --serial <serial> --yes --confirm-destructive exit 8, same method-specific detail
pro static-computer-groups list exit 0, returns the groups with a warning that the name retires on 2027-03-09
pro classic-patch-titles list exit 0, returns XML
pro classic-patch-policies list exit 0, returns XML
pro policy-properties get exit 0, returns JSON

The refusal names the resolved credentials in its hint, so an env-var CI job gets the same answer as a profile:

$ jamf-cli -p platform-mockingbird pro api-roles list
hint:    auth-method platform against the gateway, from profile "platform-mockingbird"

The command's own --help carries the caveat, so --help is enough to check before writing a script:

$ jamf-cli -p platform-mockingbird pro api-roles list --help
Get roles with Search Criteria

Through the Jamf Platform gateway: outside the published API and refused. Not declared by
the gateway's Jamf Pro API 11.32.0. The gateway may still route it today, but that is
transitional. Requires a profile pointed at a Jamf Pro instance (auth-method oauth2 or
token).

No refused command names a successor today, so no refusal or --help line carries a use this instead sentence, and no refused row in commands -o json carries a gatewaySuccessor key.

The families affected, all 59:

Refused Count Notes
pro mobile-devices MDM actions 16 apply-redemption-code, clear-passcode, clear-restrictions-password, delete-user, disable-lost-mode, enable-lost-mode, lock, log-out-user, play-lost-mode-sound, refresh-cellular-plans, request-mirroring, restart, settings, shutdown, stop-mirroring, unlock-user-account. See Device Actions
pro api-integrations 7 apply, client-credentials, create, delete, get, list, update
pro classic-computer-configs 7 The resource and its six verbs. Dead resource; the instance 404s it too
pro api-authentication 6 current, invalidate-token, keep-alive, list, oauth-token, token. The instance's own /v1/auth endpoints
pro api-roles 6 apply, create, delete, get, list, update. Use Jamf Pro's own UI or an instance profile
pro computer-inventory MDM actions 8 disable-remote-desktop, enable-remote-desktop, lock, restart, set-auto-admin-password, set-recovery-lock, settings, shutdown. erase, remove-mdm and flush-commands still work
pro jamf-pro-initialization 3 initialize, initialize-database-connection, platform-initialize. Bootstraps an on-prem Jamf Pro from an activation code; not applicable to a cloud tenant
pro api-role-privileges 2 list, search
pro environment-type get 1
pro macos-managed-software-updates list 1
pro mdm commands 1 The generic command-issuing endpoint
pro sso-oauth-session-tokens list 1 The instance's own session-token endpoint

pro auth token and platform auth token are hand-written and are not refused; use those to print a token.

Seven of the 59 are Classic (pro classic-computer-configs), whose gatewayDetail reads "not declared by the gateway's Classic API 11.32.0". The gateway's published Classic API is now at the same version as its Jamf Pro API, so the resource is absent at the current version rather than a stale one: it is dead, not pending. The instance 404s it too.

Keep the gateway profile for Platform API work and add an oauth2 profile pointed at the Jamf Pro instance for the refused commands:

jamf-cli -p my-instance pro computers lock --serial C02X1234 --yes --confirm-destructive

JAMF_CLI_ALLOW_UNPUBLISHED=1 downgrades an unpublished refusal to a stderr warning and sends the request. Neither --quiet nor --no-hints silences that warning, and the route it depends on is going away, so treat it as a stopgap. It has no effect on a probe refusal: there is no route to reach.

The reverse direction is refused the same way: a Platform-only command on an instance profile exits 8, naming the profile, its resolved auth method and platform setup.

Full detail, including the base URL change and the three scope levels, is in Platform API GA Migration.

403 permission vocabulary

A 403 names the permission it wanted in the vocabulary of the API that answered:

  • An instance request (oauth2 / token profile) names Jamf Pro API-role privileges as Jamf Pro's own picker spells them: Read Categories.
  • A gateway request names Jamf Account capability permissions plus the section and permission name Jamf Account's Platform API integration picker shows, so categories:read renders as Organizational context > Categories: Read.

Neither vocabulary converts to the other, so commands -o json carries both: privileges (Jamf Pro API-role names), gatewayPrivileges (capability slugs), gatewayPermissions (the picker's words) and api naming the serving API.

# Required permissions, and the serving API
jamf-cli commands -o json \
  | jq '.[] | select(.command=="pro categories list")
        | {api, privileges, gatewayPrivileges, gatewayPermissions}'

Diagnostic Command

doctor diagnoses local config, credentials and server reachability without auth, so it works when auth is broken.

# Diagnose the active profile
jamf-cli doctor

# Diagnose a specific profile by name
jamf-cli doctor my-profile

# Machine-readable output
jamf-cli doctor -o json

Output sections:

  • CONFIG: path to the config file and whether it exists
  • ACTIVE PROFILE: name, URL, auth method, and credential references (secrets shown as abcd•••• fingerprints only). Flags any credential shadowed by an environment variable at runtime.
  • ENVIRONMENT: all credential-related env vars and their state
  • CONNECTIVITY: unauthenticated HEAD probe of the profile URL with HTTP status and latency

The connectivity probe is a bare HEAD request testing DNS/TLS reachability.

Utility Commands

jamf-cli config list --status              # List profiles with health check
jamf-cli config show                       # Show resolved configuration
jamf-cli pro setup                         # Bootstrap OAuth2 credentials (see below)
jamf-cli platform setup                    # Configure platform gateway profile
jamf-cli completion install                # Install shell completions
jamf-cli version                           # Print version info
jamf-cli version -v                        # Include spec provenance (file + SHA256 for each generated command source)
jamf-cli version -o json                   # Structured version report

version reports the CLI version, build commit and date, and the Jamf Pro spec version the commands were generated against (specProVersion, 11.32.0 in v1.30.0). -v adds the source file and SHA256 hash per generated command group (pro, proClassic, platform), which helps when a command 404s against an older instance. -o json or -o yaml gives a machine-readable report.

pro setup credential sources (v1.29.0+)

pro setup writes OAuth2 client credentials for a Jamf Pro instance as a config profile, from one of two sources. You type each credential at an interactive prompt; no flag, environment variable or stdin route accepts one, so nothing reaches shell history, ps output or a CI log.

Flag Default Description
--credentials existing existing: you supply the client ID and secret for an API client you already created in Jamf Pro (Settings > API roles and clients); its own API role decides what the CLI can do. create: authenticates with a Jamf Pro account, creates an API role and client, and generates credentials. The username and password are never stored.
--scope standard read-only, standard or full-admin. Scopes the role --credentials create builds. Refused with existing.
--rotate-credentials false Regenerate client credentials for an existing integration. --credentials create only.
--profile-name default Profile name to write. Ignored with --from-file.
--url Jamf Pro server URL
--from-file File of one Jamf Pro URL per line, for multi-instance setup. Profiles are auto-named pro-<subdomain>.

With --credentials existing, the CLI exchanges the pair for a token before writing anything, so a mistyped secret fails at setup. With --from-file, existing asks for a client per instance: Jamf Pro issues an API client against the instance it lives on.

A flag that cannot apply to the chosen source is refused, exit 2, with nothing written:

$ jamf-cli pro setup --credentials existing --scope full-admin
--scope cannot be used with --credentials existing: the privileges come from the API role
already attached to your client in Jamf Pro

$ jamf-cli pro setup --credentials existing --rotate-credentials
--rotate-credentials cannot be used with --credentials existing: jamf-cli did not issue the
client and cannot rotate its secret; generate a new secret in Jamf Pro and re-run setup

$ jamf-cli pro setup --credentials bogus
invalid --credentials "bogus": must be one of: existing, create

--no-input is refused on both sources: both read their secrets from a prompt.

--credentials create depends on an authentication path Jamf is retiring. Local Jamf Pro accounts, SAML and LDAP/directory administrator authentication are deprecated for cloud-hosted Jamf Pro, estimated removal in the second half of 2027; self-hosted instances are unaffected. Prefer an existing API client, or jamf-cli platform setup. pro setup --help carries the notice and the Jamf link.

Full walkthrough: Setup Guide.

Tenant version compatibility check

On the first authenticated command against a profile, the CLI probes /api/v1/jamf-pro-version and compares it against the spec version the binary was built from. A tenant on an older Jamf Pro than the spec version earns a one-line warning on stderr:

warning: tenant is on Jamf Pro 11.20.0; this CLI was built against 11.32.0 — some commands may not be available

The CLI caches the result for 24 hours per profile (~/.config/jamf-cli/.version-cache.json), so the probe costs at most one extra request per profile per day. Commands still run. Skip it with --no-version-check or JAMF_NO_VERSION_CHECK=1, for CI/CD or tenants without the version endpoint.

This warns when the tenant is behind the CLI. For the CLI itself being behind its latest release, see Configuration & Profiles#Release Update Notice (v1.26.0+), which follows the same conventions: stderr only, 24-hour cache, three opt-outs, suppressed in CI.

jamf-cli Wiki


Products

  • Jamf Pro: jamf-cli pro
  • Jamf Platform API: jamf-cli pro (blueprints, benchmarks, DDM reports)
  • Jamf Platform: jamf-cli platform (AI Governance, Jamf Account, audit)
  • Jamf Protect: jamf-cli protect
  • Jamf School: jamf-cli school
  • Jamf Security Cloud: jamf-cli security

Clone this wiki locally