Skip to content

Platform API GA Migration

Neil Martin edited this page Sep 4, 2026 · 9 revisions

Platform API GA Migration

The Jamf Platform API reached general availability on 2026-09-03, and the public beta is over. This page covers what you have to change coming from the beta, and what the CLI now does for you.

Do I need this page?

Yes, if either of these is true:

  • You have a config profile with auth-method: platform, a platform gateway profile.
  • You used the Platform API public beta, from the CLI or anywhere else. Beta credentials were revoked at GA.

No, if every profile you use is auth-method: oauth2 or token against https://your.jamfcloud.com. Nothing about a Jamf Pro instance profile changes. Everything below is about the gateway.

Setting up a platform profile for the first time is not a migration. Run jamf-cli platform setup and read Scope to pick the level. See Setup Guide for the wizard walkthrough.

The numbers on this page are a snapshot. The gateway's published surface still moves at GA (endpoints are added, superseded versions are withdrawn), so the refused-command list below is what this documented build refuses. jamf-cli commands -o json always reports the current answer for the binary you have.

The console-side instructions (registering an integration, the permission picker) are at Getting started with the Platform API. The Terraform provider's Preparing for the Platform API GA guide covers the same GA in more depth on the Jamf Account side, including the permission model and the scope levels.

Migration checklist

Three changes per platform profile, all of them now due. GA has happened, and a profile still carrying beta values does not work.

# 1. Find every profile still pointed at the retired gateway host.
jamf-cli config list -o json | jq -r '.[] | select(.url | test("apigw\\.jamf\\.com")) | .name'

# ...and see the whole picture: NAME | AUTH-METHOD | DEFAULT | ENVIRONMENT-ID | URL
jamf-cli config list
jamf-cli config path        # where the file lives, if you prefer to edit it directly

# 2. Register a replacement API integration in Jamf Account (account.jamf.com).
#    Beta credentials were revoked at GA and a beta client cannot be migrated.
#    Record what the old integration could reach BEFORE replacing it, so you can
#    pick the equivalent permissions.

# 3. Re-run setup. It does all three changes interactively and writes a fresh
#    profile: GA host, new credentials, and the scope level.
jamf-cli platform setup --profile-name platform-prod

# 4. Confirm.
jamf-cli config validate --connectivity
jamf-cli -p platform-prod pro categories list
jamf-cli -p platform-prod pro blueprints list

# 5. Check whether anything you script is now refused on a gateway profile.
jamf-cli commands -o json | jq -r '.[] | select(.gateway=="unserved") | .command'

Editing the profile by hand instead of re-running setup means three keys:

Change From To
url https://{region}.apigw.jamf.com https://{region}.api.jamfcloud.com
client-id / client-secret beta integration a GA integration registered in Jamf Account
scope key tenant-id environment-id, unless you want single-product access

There is no state to migrate and no config-file schema change beyond those keys.

Two things about reading config list while you audit. The table, CSV and plain output carry an environment-id column and no tenant-id one, so a tenant-scoped profile shows its scope only under -o json, where tenant-id is still reported. And the JSON fields are omitted when empty, so environment-id appears only for a profile that has one: its absence from a row means that profile has no environment scope, not that none is configured anywhere.

Base URL

The GA gateway is https://{region}.api.jamfcloud.com, with us, eu or apac for the region. Supply the host only. Every namespace is served at the root and the beta's /api path segment is gone: a request carrying it gets the gateway's plain-text 404 page not found rather than a JSON error.

profiles:
  platform-prod:
    url: https://eu.api.jamfcloud.com   # was https://eu.apigw.jamf.com
    auth-method: platform
    client-id: keychain:jamf-cli/platform-prod-client-id
    client-secret: keychain:jamf-cli/platform-prod-client-secret

The old host is refused by name, from the profile's stored URL, before any token exchange:

$ jamf-cli -p platform-eu-old pro categories list
{
  "error": "usage",
  "exitCode": 2,
  "exitCodeName": "usage",
  "message": "https://eu.apigw.jamf.com is the retired Jamf Platform gateway and does not serve the GA API paths.\nSet url: https://eu.api.jamfcloud.com in this profile (jamf-cli config path prints the file), or re-run `jamf-cli platform setup`."
}

Note the exit code: 2 (usage), not 8. A retired host is a configuration mistake in the profile you invoked, the same class as a bad flag or a missing URL. Exit 8 is reserved for a policy refusal, where the invocation and the profile are both fine and only the published API does not carry the endpoint.

That refusal exists because the wire symptom is useless: the failure lands in the token exchange, before your command is sent, as an edge-level 403 with an HTML body that names neither the host nor the reason. The CLI does not rewrite the URL for you: the profile on disk stays wrong for every other tool reading it, and a URL you did not type is a bad thing to send a credential to.

jamf-cli config validate reports the retired host too, so a stale profile is findable without running a command against it.

The GA host is the only host now. apigw.jamf.com is retired rather than deprecated, so this change and the credential swap have to land together.

Scope

An API integration is created at one of three levels in Jamf Account, and its credential only works with that level. This is a choice between integrations, not between ways of naming the same access:

Level Profile key What it reaches
Organization neither key Jamf Account (platform account-licenses, platform sso-connections, platform sso-domains, the distributor-* commands) and AI Governance
Platform environment environment-id a group of tenants across product types. Prefer this; also the only level platform audit accepts
Tenant tenant-id one Jamf Pro / School / Protect / Security Cloud tenant (the legacy level)

Public-beta integrations were tenant-scoped, so most existing profiles carry tenant-id. A GA replacement will usually be environment-scoped:

    # tenant-id: 0f9c…          # remove
    environment-id: 4b21…

Both are also settable per-invocation (--environment-id, --tenant-id) and by environment variable (JAMF_ENVIRONMENT_ID, JAMF_TENANT_ID), which is the CI/CD route (see CI/CD & Scripting). An explicitly supplied level replaces the profile's rather than joining it, so JAMF_ENVIRONMENT_ID=… jamf-cli -p tenant-profile … means "use this environment".

jamf-cli platform setup asks for the environment ID first and stops there if it gets one, because the levels are mutually exclusive and offering both would offer a combination no credential can use.

Three things to know:

  • Supplying both levels at once is refused, in the environment as well as on the command line, before any request is sent. Precedence would hide the mistake, and the wire symptom is a 403 from whichever half the credential does not match. Exit code 2:

    --environment-id and --tenant-id are mutually exclusive: an API integration is created at
    one level, and its credential only works with that level's header
    

    The message names the flags whichever way the conflict arrived, so a CI job that set JAMF_ENVIRONMENT_ID and JAMF_TENANT_ID together sees flag names it did not pass. Unset one of the variables.

  • Sending the wrong level for the credential is a 403 OWNERSHIP_FORBIDDEN, even when both identifiers belong to you. Sending neither, on a credential that expects one, is 400 REQUEST_CONTEXT_NOT_PROVIDED, and the CLI adds a note naming the level that endpoint wants.

  • Organization scope has no ID, so the gateway host itself is what selects platform auth for it. JAMF_URL=https://us.api.jamfcloud.com plus a client ID and secret, with no scope ID at all, is a valid organization-scoped setup.

A customer with several tenants and no platform environment keeps a profile per tenant, as before.

The scope travels in a header

At the beta, the scope was a path segment: /api/{namespace}/{version}/tenant/{tenantId}/{resource}. At GA it is a request header: X-Environment-Id or X-Tenant-Id, and nothing at all for organization scope. The CLI handles this; there is no flag or profile key for it and nothing to change in a profile beyond the scope key itself.

It matters for two reasons. Anything of your own that built gateway URLs by hand needs the same change. And an organization-scoped credential sends no scope header at all, which is what the gateway objects to when an endpoint requires one.

One wrinkle before you read one of those errors: the gateway calls an organization credential's absent context 'tenant'. An organization-scoped credential against platform audit, which accepts environment scope only, gets

[INVALID_REQUEST_CONTEXT_TYPE] Request context type 'tenant' is invalid. Expected any of 'environment'.

That is exit code 1, not 8, because it is the gateway's answer to a sent request rather than a CLI policy refusal. The word 'tenant' there does not mean a tenant ID was sent; it is how the gateway labels the default context. Set an environment ID on the profile (or JAMF_ENVIRONMENT_ID).

Credentials and permissions

Credentials are registered in Jamf Account's Platform API integrations UI. One OAuth 2.0 model now covers the platform, replacing one credential set per product.

jamf-cli platform setup

It prompts for region, client ID and secret, and the scope, validates them against the gateway, and stores the secrets in the system keychain (see Secrets & Keychain). It also reports whether the credential reaches Jamf Security Cloud rather than passing or failing on it, since a Jamf Pro tenant may have no access to it.

jamf-cli security setup still owns the three Jamf Security Cloud Radar credential pairs (Risk, Device Lifecycle, SSE), which are unrelated to the gateway and unaffected by GA. Both setup commands merge into a profile rather than replacing it, so one profile can carry both credential sets and running them in either order is safe.

Permissions are organised by capability and action (device-groups:read, compliance-benchmarks:create) rather than by Jamf Pro privilege name. The two vocabularies are independent: the GA consolidation folded several Jamf Pro privileges into one capability, the computer and mobile pairs collapsed into single device-level permissions, and Jamf Account no longer offers the old names. The Jamf Pro permissions map is the reference for the full mapping.

Two consequences before you grant anything: an action covers only itself, so a command that reads a record before writing it needs both; and erase, unmanage and pro platform-devices delete sit under destructive-device-actions:execute, split out of device-actions:execute, so those three can 403 on a credential that executes every other device action.

What a 403 tells you

A 403 now names the permission in the vocabulary of the API that answered. On a gateway profile:

$ jamf-cli -p platform-prod security content-categories list
permission denied (HTTP 403): {"httpStatus":403,"errors":[…]}
hint: grant the Jamf Platform API integration these permissions in Jamf Account — Secure
enterprise access > Content categories: Read (content-categories:read). Names are as the
permission picker shows them:
https://developer.jamf.com/platform-api/reference/jamf-pro-permissions-map
$ echo $?
5

The section and permission name are what Jamf Account's picker prints. You search that picker by name, and the names differ from the slugs (computer-inventory-collection-settings is "Device inventory collection settings"). The slug is printed beside each one because that is what the gateway's own errors and the command catalog use.

The same command against a Jamf Pro instance profile still reports Jamf Pro privilege names (Required privilege(s): Read Categories, Read Self Service), because that is what you would grant there. Neither vocabulary is printed for the other credential.

A platform 403 has a second cause that is not about permissions at all. When the gateway answers OWNERSHIP_FORBIDDEN, the hint says so instead: the credential's scope level does not match the header sent, the grants are already correct, and the fix is one line of the profile.

Platform command 403s now exit 5, not 1. permission_denied has a documented exit code; platform commands previously returned the SDK's error untouched and exited 1. Scripts keying on exit codes should expect 5 from a platform permission failure. See Error Handling & Exit Codes.

Sizing an integration without provoking 403s

jamf-cli commands -o json carries the requirement per command, in both forms:

Field What it holds
privileges Jamf Pro API-role privilege names (Read Categories) for a Pro or Classic command; for a Platform command this is already the capability list
gatewayPrivileges the capability permissions the gateway requires for that Pro or Classic endpoint (categories:read)
gatewayPermissions the same requirement in Jamf Account's own words, as Organizational context > Categories: Read (categories:read)
api which API serves it: pro, pro-classic, platform-gateway or radar

gatewayPermissions is the field to work from when creating the integration, because the Platform API integrations UI is the only place an integration can be created and its picker lists named permissions with a checkbox per action. The capability slug appears nowhere in it.

The permissions for a set of commands are a query:

jamf-cli commands -o json | jq -r '
  .[] | select(.command | test("^pro blueprints ")) | .gatewayPermissions[]?
' | sort -u
Deployment > Blueprints: Delete (blueprints:delete)
Deployment > Blueprints: Deploy (blueprints:deploy)
Deployment > Blueprints: Read (blueprints:read)
Deployment > Blueprints: Update (blueprints:update)

Each row is per command, so widening the filter can list one permission twice with different actions ticked: Categories: Create from create, Categories: Create, Read, Update from apply. Tick the union.

Three things the fields do not claim:

  • Absent is not "needs none". A command outside the published API declares no capability, as do the unauthenticated Jamf Pro endpoints (pro health-checks, pro jamf-pro-versions) and the hand-written commands, which assemble their own paths. pro platform-devices delete is the one to watch: the catalog lists nothing for it, while the endpoint it calls requires destructive-device-actions:execute.
  • A --name, --serial or --udid lookup resolves the identifier through the resource's collection first, so those invocations also need its read permission. Only the permissions a command always uses are listed, so that delete <id> does not ask you to grant a read it never makes. apply is the exception that always reads, and its row says so.
  • One permission, one row. Categories: Create, Read, Update is one row of the picker with three boxes ticked, not three permissions.

Commands refused on a gateway profile

Some Jamf Pro and Classic endpoints are not part of the gateway's published API. Those commands are refused before a request is sent on a gateway profile, with exit code 8.

Regenerate the list for the binary you have. No profile or credential is needed, since the verdict is baked into the binary:

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

# With the reason each one carries:
jamf-cli commands -o json | jq -r '.[] | select(.gateway=="unserved") | .command + "\t" + .gatewayBasis'

A refused row carries three fields: gateway (unserved), gatewayBasis (unpublished, meaning the endpoint is absent from the published API but may still answer, or probe, a recorded wire probe that found no route) and gatewayDetail, the sentence the refusal prints. In this build every refused command is unpublished, which is what makes JAMF_CLI_ALLOW_UNPUBLISHED apply to all of them.

75 entries in this build (two of them a bare group node for a wholly-refused resource, so 73 runnable commands):

Refused Subcommands Count
pro mobile-devices 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 16
pro computers-inventory disable-remote-desktop, enable-remote-desktop, lock, restart, set-auto-admin-password, set-recovery-lock, settings, shutdown 8
pro api-integrations apply, client-credentials, create, delete, get, list, update 7
pro classic-computer-configs the group itself, plus apply, create, delete, get, list, update 7
pro api-roles apply, create, delete, get, list, update 6
pro authentications current, invalidate-token, keep-alive, list, oauth-token, token 6
pro static-computer-groups apply, create, delete, get, list, update 6
pro classic-patch-titles apply, delete, get, list, update. create still works 5
pro api-roles-privileges api-role-privileges, search 2
pro classic-patch-reports the group itself, plus get 2
pro policy-properties policy-properties, update-policy-properties 2
pro systems initialize, platform-initialize 2
pro classic-patch-policies list. get, create, update, delete still work 1
pro database-connections initialize-database-connection 1
pro environment-type get 1
pro mac-os-managed-software-updates list 1
pro mdm-commands commands 1
pro oauth-token-sessions list 1

Nothing else changes for the ~1,700 other commands. Pro and Classic still route through the gateway as before.

For all but one of these, the remedy is a second profile. The endpoints exist on your Jamf Pro instance; keep an oauth2 profile alongside the platform one and select it with -p for the affected commands. See Configuration & Profiles.

The exception is pro static-computer-groups, which has a working replacement in the same binary. The gateway's published surface withdrew 122 superseded Jamf Pro endpoints, every one of them a version with a published higher-version replacement. In almost every case the CLI moved onto the successor and you notice nothing: pro computers-inventory now sends /v4, and gained device-action endpoints in the process. Static computer groups are the exception, because the CLI ships the two versions under two different command names:

pro static-computer-groups            # v2 — refused on a gateway profile
pro computer-groups-static-groups     # v3 — use this

Both still work against a Jamf Pro instance profile. Where a replacement is known, the refusal and the command's --help both name it, so --help is the place to check rather than a field in the catalog.

MDM device actions are refused

The first two rows of that list are the most operationally significant entry, and nothing about the command names suggests it. The gateway carries the resource but not the write method, and the refusal says so:

The gateway's Jamf Pro API 11.31.0 declares GET on this path but not POST.

So 24 device actions are refused on a gateway profile:

Product Lost on a gateway profile
Mobile devices lock, restart, shutdown, enable/disable lost mode, play lost mode sound, clear passcode, clear restrictions password, unlock user account, log out user, delete user, apply redemption code, refresh cellular plans, request/stop mirroring, send settings
Computers lock, restart, shutdown, enable/disable remote desktop, set recovery lock, set auto admin password, send settings

Move a workflow to a gateway profile and you lose all of them, with no sign of it until you run the command. Keep an oauth2 profile against the instance for anything that locks, wipes, restarts or unlocks a device.

Two things this does not cover:

  • pro computers-inventory erase and pro computers-inventory remove-mdm still work on a gateway profile. They send a different, published path. Their targeting, confirmation, -n, --dry-run support and Find My PIN body are unchanged.
  • pro mdm-commands commands, the generated command that posts to that path directly, is refused for the same reason and appears separately in the table.

A withdrawal can take part of a command group

The classic-patch-titles and classic-patch-policies rows are the pattern to expect from here on: pro classic-patch-titles create is served and list is not, because the gateway still publishes the one call that mints a software title ID and nothing else on that resource. Check --help on the individual subcommand rather than on the group; a refused one says so in its first paragraph, and a wholly-refused group says so on the group.

What the refusal looks like

Every endpoint on the list today may still answer. That is transitional, and the refusal says so rather than claiming the endpoint is gone:

$ jamf-cli -p platform-prod pro api-roles list
jamf-cli pro api-roles list is not part of the Jamf Platform gateway's published API

Not declared by the gateway's Jamf Pro API 11.31.0.

The gateway still routes some endpoints its published API omits, so this one may answer
today — that is transitional, and a workflow built on it will break without notice. It is
refused here rather than later, when the gateway's 403 BAD_PERMISSIONS would be
indistinguishable from a missing API-role privilege.

Run it against a Jamf Pro instance directly — a profile whose url is your instance and whose
auth-method is oauth2 or token.

Published surface: Jamf Pro API 11.31.0, Classic API 11.28.0.
hint: auth-method platform against the gateway, from profile "platform-prod"
$ echo $?
8

pro api-roles has no successor in the CLI, so the only remedy offered is the instance profile. A command that does have one names it first, that being the cheaper fix: a different command name on the profile already in hand.

$ jamf-cli -p platform-prod pro static-computer-groups list
jamf-cli pro static-computer-groups list is not part of the Jamf Platform gateway's published API

Not declared by the gateway's Jamf Pro API 11.31.0.

...

Use `jamf-cli pro computer-groups-static-groups` instead — the same resource on the v3
endpoint the gateway publishes, where this command is the withdrawn v2. It ships in this
binary and is served by the gateway.

Failing that, run it against a Jamf Pro instance directly — a profile whose url is your
instance and whose auth-method is oauth2 or token.

--help carries the same caveat, so you do not have to run a command to find out:

$ jamf-cli 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.31.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).

Exit code 8, not 2. A refusal is a policy answer about the credentials in hand rather than a malformed invocation, and exit 2 is also what a bad flag, an unknown subcommand, a missing URL, a missing credential, the retired gateway host and a scope conflict all return. A wrapper that wants to skip refused commands and fail on everything else keys on 8:

jamf-cli -p platform-prod pro api-roles list
case $? in
  0) ;;
  8) echo "refused on this credential — skipping" >&2 ;;
  *) exit 1 ;;
esac

Refusing something that works is the point: every day a workflow keeps running against a route that is going away, the eventual failure gets more expensive, and it arrives as a bare 403 BAD_PERMISSIONS with nothing saying a withdrawal caused it.

The list moves in both directions. App Installers used to head it, at 17 commands and instance-only, and no longer appears at all. Those endpoints were absent from every published spec, so nothing routed them. They were published on 2026-09-03 and the gateway opened them the same day, so pro app-installer-titles, pro app-installer-deployments, pro app-installer-global-settings and the new pro app-installers now work on a gateway profile like any other Pro command. pro policy-properties moved the other way at the same time, and still answers on the wire, which is the case the transitional wording exists for.

The reverse direction

A Platform-only command (pro blueprints, platform ai-policies, security ztna-apps, …) on an instance profile is refused the same way, also with exit 8. It names the profile, its resolved auth method, and jamf-cli platform setup, rather than reading as a credential problem:

jamf-cli pro blueprints list is served by the Jamf Platform API, which the active
credentials do not reach

The resolved credentials authenticate against a Jamf Pro instance (auth-method oauth2, from
profile "prod"). Platform API commands need a platform gateway credential: a client ID and
secret from a Jamf Platform API integration, against https://{region}.api.jamfcloud.com.

Set one up with `jamf-cli platform setup`, then re-run with -p <that profile>.

Both directions key on the resolved credentials, not on a profile, so a CI job using JAMF_* environment variables gets the same refusal, and the hint names the environment variables rather than a profile that was not used:

hint: auth-method platform against the gateway, from the JAMF_CLIENT_ID / JAMF_CLIENT_SECRET
environment variables

The stopgap: JAMF_CLI_ALLOW_UNPUBLISHED

Some of these endpoints answer today. pro policy-properties returns real data, and the build that withdrew it from the published spec did not stop routing it, so an upgrade can refuse a command that was working an hour earlier, with no route back until a release moves it.

Setting JAMF_CLI_ALLOW_UNPUBLISHED=1 downgrades that refusal to a warning and sends the request:

$ JAMF_CLI_ALLOW_UNPUBLISHED=1 jamf-cli -p platform-prod pro policy-properties policy-properties
warning: jamf-cli pro policy-properties policy-properties is not part of the Jamf Platform
gateway's published API, and JAMF_CLI_ALLOW_UNPUBLISHED is set — sending it anyway.
Not declared by the gateway's Jamf Pro API 11.31.0.
The gateway routes it today; that is transitional and it will stop answering without notice,
at which point the failure arrives as a bare 403 BAD_PERMISSIONS. This is a stopgap, not a
supported mode — move the workflow onto a Jamf Pro instance profile.

Read the terms before using it:

  • It is a stopgap, not a supported mode. Nothing has committed to keeping these routes. When one is withdrawn, the command starts failing with a bare 403 BAD_PERMISSIONS and the variable will not help.
  • The warning cannot be silenced. It goes to stderr on every affected invocation, and neither --quiet nor --no-hints suppresses it. Being told is what the variable trades the refusal for.
  • It covers only endpoints that are merely unpublished, which is all 75 in this build. An endpoint a wire probe found genuinely unrouted would be refused regardless, because letting it through buys a 403 and nothing else.
  • It is value-parsed, like JAMF_CLI_NO_HINTS: JAMF_CLI_ALLOW_UNPUBLISHED=0 leaves the refusal in place, so a runner that exports it unconditionally can turn it off without unsetting it.
  • It changes nothing about the endpoint. Prefer a second oauth2 profile for anything scheduled; use the variable for a one-off job you are already planning to migrate.

What GA added

Additive: no action needed, and all of it is live now.

  • A new platform top-level namespace, for surfaces that are not Jamf Pro surfaces at all: Jamf AI Governance (platform ai-policies, platform ai-tools), Jamf Account (platform account-licenses, platform deal-registrations, platform distributor-configuration, platform distributor-purchase-orders, platform distributor-quotes, platform sso-connections, platform sso-domains) and the platform audit event log (platform audit). Jamf Account is organization-scoped and US-only; a non-US profile is refused before sending, permanently. platform audit accepts environment scope only. See Jamf Platform Commands.
  • App Installers on the gateway: pro app-installer-titles, pro app-installer-deployments, pro app-installer-global-settings and a new pro app-installers (whether the feature is available, and which features the Cloud Services Connection enables). Previously instance-only and refused on a gateway profile.
  • Jamf Security Cloud through the gateway: security dns-zones, dns-search-domains, dns-custom-hostname-mappings, the ztna-* family, content-categories, device-groups, the uem-* family and enrollment-activation-profiles. These take gateway credentials, not the Radar application pairs. Every security command says which API serves it in its short description, as (Security Cloud · platform gateway) or (Security Cloud · Radar API), so security <TAB> shows it. See Jamf Security Cloud Commands.
  • Platform API commands under pro are unchanged in name: pro blueprints, pro compliance-benchmarks, pro platform-devices, pro platform-device-groups, pro ddm-reports. See Platform API Commands.
  • -n, --dry-run is now honoured on platform and Security Cloud writes, printing the method, resolved path and body to stderr.
  • -v labels retried requests with the attempt number and the wait, so a slow call is distinguishable from a retry sequence.

Troubleshooting

Symptom Cause and resolution
… is the retired Jamf Platform gateway (exit 2) Set url: to https://{region}.api.jamfcloud.com, or re-run jamf-cli platform setup.
404 page not found, no JSON body A path reached the gateway that it does not route, usually a url carrying a path segment. Supply the host only.
403 OWNERSHIP_FORBIDDEN environment-id supplied for a tenant-scoped integration, or the reverse. The hint says the grants are not the problem.
400 REQUEST_CONTEXT_NOT_PROVIDED No scope sent, on an endpoint that expects one (an organization-scoped credential). Add environment-id or tenant-id. Exit 1.
400 INVALID_REQUEST_CONTEXT_TYPE The level sent is not the level that endpoint accepts; the message names both. platform audit accepts environment only, and an organization-scoped credential earns this too, because the gateway labels its absent context 'tenant'. Exit 1.
permission denied (HTTP 403) with a permission hint The integration lacks that permission. Grant it in Jamf Account by the section and name the hint prints. Exit 5.
403 BAD_PERMISSIONS with no permission named The endpoint has no recorded capability, or the namespace is not entitled for this tenant. Check the integration's permissions and the tenant's entitlements.
request blocked at the Jamf gateway edge (HTTP 403) CloudFront's WAF refused the request before the gateway saw it, so your credential and permissions are not the cause. Known triggers: file:// anywhere in the request body (legitimate in some Classic payloads), .pkg upload content, and a burst of writes. Retry a single request cold; a .pkg upload needs an instance profile. Exit 5.
… is not part of the Jamf Platform gateway's published API (exit 8) Expected. Use the successor the message names, if it names one, or run it against a Jamf Pro instance profile (see Commands refused on a gateway profile). JAMF_CLI_ALLOW_UNPUBLISHED=1 is the stopgap.
… is served by the Jamf Platform API, which the active credentials do not reach (exit 8) A platform command on an instance profile. Use -p <platform profile>.
--environment-id and --tenant-id are mutually exclusive (exit 2) Both levels supplied. Pass only the one this credential was created for. The message names the flags even when the pair came from JAMF_ENVIRONMENT_ID / JAMF_TENANT_ID.
Authentication fails outright with credentials that used to work Beta credentials, revoked at GA. Register a replacement integration in Jamf Account and re-run jamf-cli platform setup.

See also

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