github Jamf-Concepts/jamf-cli v1.28.0

2 hours ago

The Jamf Platform API reached general availability on 2026-09-03, and most of this release is that migration. If you use a auth-method: platform profile, read the migration guide before upgrading — it carries the detail, the error messages verbatim, and the reasoning behind each refusal.

If you only use oauth2 or token profiles against a Jamf Pro instance, the short version is: four small breaking changes (below), and Classic writes finally have --scaffold and --set.

Breaking — platform gateway profiles

  • The gateway base URL is now https://{region}.api.jamfcloud.com. The pre-GA https://{region}.apigw.jamf.com is retired, and the /api path segment it required is gone. A profile still naming the old host is refused by name before any request is sent — the CLI does not rewrite it for you, because the profile on disk stays wrong for every other tool reading it. The wire symptom without that guard is an edge-level 403 during the token exchange naming neither the host nor the reason.

  • Public-beta credentials were revoked at GA. A beta client cannot be migrated — register a replacement API integration in Jamf Account and run jamf-cli platform setup.

  • Three scope levels, one per profile: organization, platform environment, tenant. environment-id is new (--environment-id, JAMF_ENVIRONMENT_ID) and is the level to prefer; tenant-id is the legacy one; organization scope carries no ID at all and is selected by the gateway host. Supplying two levels together is refused, in the environment as well as in a profile. The scope now travels in an X-Environment-Id / X-Tenant-Id header instead of a /tenant/{tenantId} URL segment.

  • 75 Jamf Pro and Classic commands are refused on a gateway profile, before a request is sent, with the new exit code 8 (Refused by policy). They sit outside the gateway's published API. Several still answer today — that is transitional, and refusing now is cheaper than the eventual bare 403 BAD_PERMISSIONS with nothing saying a withdrawal caused it.

    The 24 that will bite hardest are device actions: all 16 pro mobile-devices MDM subcommands (lock, clear-passcode, enable-lost-mode, restart, shutdown, …) and 8 pro computers-inventory ones (lock, restart, set-recovery-lock, enable-remote-desktop, …). They POST /v2/mdm/commands, which the gateway routes but does not publish. pro comp erase and pro comp remove-mdm are unaffected.

    The rest: pro api-roles, pro api-integrations, pro api-roles-privileges, pro authentications, pro oauth-token-sessions, pro environment-type, pro database-connections, pro systems initialize / platform-initialize, pro mac-os-managed-software-updates list, pro mdm-commands commands, pro static-computer-groups (use pro computer-groups-static-groups), pro classic-computer-configs, pro classic-patch-reports, five pro classic-patch-titles subcommands, pro classic-patch-policies list, and pro policy-properties.

    The remedy is a second oauth2 profile against the instance. For the current list for the binary in hand:

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

    JAMF_CLI_ALLOW_UNPUBLISHED=1 downgrades an unpublished refusal to a stderr warning and sends the request anyway — a stopgap for one job, not a mode to settle into. The warning it substitutes cannot be silenced by --quiet or --no-hints.

  • The reverse direction is refused too, the same way: a Platform-only command on an instance profile exits 8 naming the profile, its resolved auth method and platform setup, rather than reading as a credential problem.

  • A platform command's 403 now exits 5, not 1. Platform commands previously returned the SDK's error untouched, so the one failure with a specific remedy exited with the generic code.

Breaking — everything else

  • pro ddm-reports declaration get and pro ddm-reports device get are removed. Both endpoints were deprecated upstream in favour of a sibling the CLI already shipped: declaration devices <id> --filter … and device declarations <id> --filter …. The successors declare filter required, so there is no unfiltered read left — use --filter 'active=in=(true,false)' where you want everything.
  • pro comp erase and pro comp remove-mdm send /v4/computers-inventory/{id}/…, up from /v1/computer-inventory/{id}/…. Neither has a version fallback, so an instance not serving v4 answers 404. Flags, targeting and confirmation are unchanged.
  • jamf-cli config list no longer has a tenant-id column in table, csv and plain output; it has environment-id and default instead. -o json and -o yaml are unchanged and still carry tenant-id. Parse the JSON, not the table.
  • Building from source needs Go 1.27 (go.mod declares go 1.27.0, up from 1.26.6). The default GOTOOLCHAIN=auto fetches it; a pinned older toolchain fails. Binary releases are unaffected.

Added

  • Classic writes gained --scaffold and --set, plus required-field and enum lists in --help — 111 of 117 create/update/apply commands, across 43 of the 54 Classic resources. --set builds the whole body and is mutually exclusive with --from-file; it refuses an unknown field, an out-of-enum value and a credential field, because the Classic API answers 201 and silently drops or defaults the first two. Classic create and update also accept --from-file, not just apply.
  • --file accepts YAML on generated Platform and Security Cloud commands, matching Pro's --from-file.
  • Jamf AI Governanceplatform ai-policies (aip), platform ai-tools (ait).
  • Jamf Accountplatform account-licenses, deal-registrations, distributor-configuration, distributor-purchase-orders, distributor-quotes, sso-connections, sso-domains. Organization scope, and US-only: a non-US profile is refused before sending.
  • Platform auditplatform audit, environment scope only. Not to be confused with pro audit, which runs health checks against a Jamf Pro instance.
  • Jamf Security Cloud through the gatewaysecurity dns-*, ztna-*, content-categories, device-groups, uem-*, enrollment-activation-profiles. The two halves of Security Cloud take different credentials, so every security command's Short now says which API serves it (platform gateway or Radar API), and commands -o json carries it as api.
  • App Installers on the gateway. The endpoints are published upstream now, so the commands are generated from that spec rather than a reverse-engineered one, and are no longer refused on a gateway profile. New: pro app-installers get, app-installer-titles versions, app-installer-global-settings deployment-controls / history / add-history-note.
  • Jamf Protect backup and restore, capturing and replaying a whole tenant in the portable form each export produces, resolving every cross-resource reference by name. Plus protect analytics overrides for the tenant overlay (tenantSeverity / tenantActions) on Jamf-managed analytics, which analytics export silently dropped.
  • pro ddm-reports declaration devices and device declarations gained --page; only the first page was reachable before.

Changed

  • An empty list prints [], not null — Pro's list --all and every Platform and Security Cloud list. jq pipelines previously failed with "Cannot iterate over null" on exactly the tenants where a collection was empty.
  • pro computers-inventory sends /v4 instead of /v3, and get reads the v4 detail endpoint. The two v4-only operations (erase, remove-mdm-profile) exist at all for the first time. Generated subcommands retry the /v1 path on a 404 and warn on stderr.
  • A 403 names the permission in the vocabulary of the API that answered it — capability permissions with Jamf Account's own section and permission names for a gateway request, Jamf Pro API-role privilege names for an instance request. The two do not convert to each other, so printing the wrong one sent you to a console where the grant it named does not exist. commands -o json carries both (privileges, gatewayPrivileges, gatewayPermissions).
  • A CDN/WAF refusal is reported as one, rather than as permission denied (HTTP 403) with an HTML page in the message and a hint about API roles. Known triggers: file:// anywhere in a request body, .pkg upload content, a burst of writes. A .pkg upload through a gateway profile is currently refused — upload through an instance profile.
  • -n, --dry-run is honoured on Platform and Security Cloud writes, printing method, resolved path and body to stderr, and now prints before the confirmation prompt rather than after it. Hand-written platform writes with no per-command preview are refused under -n rather than executed.
  • -v labels retried requests with the attempt number and the wait, so a slow call is distinguishable from a retry sequence.

Fixed

  • A YAML request body carrying a timestamp scalar or a non-string mapping key — both legal YAML, neither expressible in JSON — was reported as malformed input.
  • pro platform-device-groups name lookups built a stale /tenant/{id}/ path, which collapsed to /tenant// under environment or organization scope.
  • JAMF_ENVIRONMENT_ID and --tenant-id now override a profile's scope, the way every other credential input here does. Previously only the profile was read on some paths, so a documented override silently acted against the wrong tenant.
  • pro diff read one directory level and hid every nested resource; it also mis-keyed every compliance benchmark.
  • --scaffold shows what an array holds instead of an empty [], and enum-constrained fields — including array elements — name their allowed values in --help.

Full changelog: v1.27.0...v1.28.0

Don't miss a new jamf-cli release

NewReleases is sending notifications on new releases.