In our previous write-ups, we covered the fundamentals of Infrastructure as Code and how to manage Jamf Pro and Jamf Protect configuration with Terraform. If you haven't read those yet, they're worth starting with as they cover installation, project structure and state management.
This time we're turning our attention to the Jamf Platform APIs, which have recently entered public beta. These APIs power a new generation of Jamf microservices that sit alongside Jamf Pro and Jamf Protect, including blueprints, Compliance Benchmarks and device groups. We've built a dedicated Terraform provider that lets you manage these resources as code.
Why a dedicated provider for Jamf Platform microservices?
The Jamf Platform APIs are a separate API surface from the Jamf Pro Classic and Pro APIs. They power capabilities that are distinct from the resources you manage with the Jamf Pro provider, and they authenticate through a different set of credentials against a regional API gateway (us.apigw.jamf.com, eu.apigw.jamf.com or apac.apigw.jamf.com).
Blueprints let you define the desired configuration state for a group of devices and deploy it in a single operation. Compliance Benchmarks let you apply industry-standard security baselines from the macOS Security Compliance Project (mSCP) and monitor how your fleet measures up. Device groups give you a way to organize and target devices at the platform level. These are all first-class capabilities that deserve their own provider, with schemas purpose-built for the resources they manage.
If you've been managing blueprints and benchmarks through the Jamf Pro console, you already know that recreating a setup across tenants or tracking changes over time means a lot of clicking. With this provider, your entire configuration lives in Git, goes through code review and gets applied consistently wherever you need it.
What does it cover?
The provider ships with 3 resources, 12 data sources, 3 list resources and 4 device actions. Here are the key areas:
Compliance Benchmark Engine - Create and manage compliance benchmarks against mSCP baselines including CIS Level 1 and 2, NIST 800-53 (Low, Moderate, High) and DISA STIG. You can enable or disable individual rules, customize Organization-Defined Values (ODV) and target benchmarks to specific device groups. Data sources let you query available baselines, rules and existing benchmarks.
Blueprints - Define device configuration blueprints covering software update enforcement, software update settings, passcode policies, Safari settings, bookmarks and extensions, disk management, audio accessory settings, math settings, service background tasks, service configuration files, legacy payloads and custom DDM declarations. Each blueprint targets one or more device groups and can be deployed or undeployed as code. Data sources let you query existing blueprints and available blueprint components.
Device Groups - Create smart and static device groups for computers and mobile devices, with criteria-based membership rules for smart groups and explicit member lists for static groups. These groups are referenced by benchmarks and blueprints for targeting.
Unified Inventory - Read-only data sources for querying individual devices and device lists at the platform level.
Device Actions - Terraform 1.14+ actions for erase, restart, shutdown and unmanage operations against managed devices.
All resources support full CRUD operations and terraform import.
Getting started
If you followed along with the introduction article, you should already have Terraform installed and a basic understanding of how projects are structured. The steps here are similar.
1. Create API credentials for the Jamf Platform API
Head to Jamf Account and create an API client with the permissions you need for the resources you want to manage. The Getting Started guide on the Jamf Developer portal walks through this process.
2. Configure the provider
Add the provider to your Terraform configuration:
terraform {
required_providers {
jamfplatform = {
source = "Jamf-Concepts/jamfplatform"
version = "~> 0.15.0"
}
}
}
provider "jamfplatform" {
base_url = var.jamfplatform_base_url
client_id = var.jamfplatform_client_id
client_secret = var.jamfplatform_client_secret
tenant_id = var.jamfplatform_tenant_id
}
You can also use environment variables to keep the provider block clean:
export JAMFPLATFORM_BASE_URL="https://us.apigw.jamf.com"
export JAMFPLATFORM_CLIENT_ID="your-client-id"
export JAMFPLATFORM_CLIENT_SECRET="your-client-secret"
export JAMFPLATFORM_TENANT_ID="your-tenant-id"
As always, make sure these credentials don't end up in your Git repository. Use a terraform.tfvars file and add it to your .gitignore.
3. Define your resources
Let's walk through some high-impact examples.
Device groups
Before creating benchmarks or blueprints you'll want a device group to target them at. Here's a smart computer group that targets devices running macOS 26 or later:
resource "jamfplatform_device_group" "macos_26_plus" {
name = "macOS 26+"
group_type = "smart"
device_type = "computer"
criteria = [
{
criteria = "Operating System Version"
operator = "greater than or equal"
value = "26.0"
}
]
}
This group can then be referenced by ID in your benchmark and blueprint resources, so Terraform understands the dependency graph and creates everything in the right order.
Compliance benchmarks
Compliance benchmarks use the mSCP baselines that are built into the Compliance Benchmark Engine. First, use a data source to fetch the rules for the baseline you want. Then create a benchmark that references those rules and targets your device group:
data "jamfplatform_cbengine_rules" "cis_lvl1" {
baseline_id = "cis_lvl1"
}
resource "jamfplatform_cbengine_benchmark" "cis_level_1" {
title = "CIS macOS Level 1"
description = "CIS Level 1 benchmark - Managed by Terraform"
source_baseline_id = "cis_lvl1"
sources = [
for s in data.jamfplatform_cbengine_rules.cis_lvl1.sources : {
branch = s.branch
revision = s.revision
}
]
rules = [
for r in data.jamfplatform_cbengine_rules.cis_lvl1.rules : {
id = r.id
enabled = r.enabled
}
]
target_device_group = jamfplatform_device_group.macos_26_plus.id
enforcement_mode = "MONITOR"
}
This creates a CIS Level 1 benchmark in monitor mode, scoped to the smart group we defined earlier. Every rule from the baseline is included, and you can selectively disable individual rules or set custom ODV values to match your organization's requirements. For example, to customize a specific rule:
rules = [
{
id = "system_settings_time_server_configure"
enabled = true
odv_value = "ntp.example.com"
},
{
id = "system_settings_critical_update_install_enforce"
enabled = true
}
]
Setting enforcement_mode to "MONITOR_AND_ENFORCE" will remediate non-compliant settings automatically.
It's worth noting that the Compliance Benchmark Engine API does not currently offer a PUT or PATCH method for benchmarks. This means that any change to a benchmark resource in your Terraform configuration will result in a destroy-and-recreate (force replacement) rather than an in-place update. Keep this in mind when making changes to benchmarks that are already deployed and actively reporting on devices.
The real power here is that you can version-control the exact set of rules and customizations you've applied, review changes through pull requests and replicate the same benchmark across multiple tenants with zero manual effort.
Blueprints
Blueprints compose multiple configuration components into a single unit that gets deployed to one or more device groups. Each component type maps to a Declarative Device Management (DDM) declaration configuration domain. Here's a blueprint that configures software update settings with deferrals and automatic installation:
resource "jamfplatform_blueprints_blueprint" "software_update_settings" {
name = "Software Update Settings"
description = "Managed by Terraform"
deployed = true
device_groups = [jamfplatform_device_group.macos_26_plus.id]
software_update_settings = {
allow_standard_user_os_updates = true
automatic_download = "AlwaysOn"
automatic_install_os_updates = "AlwaysOn"
automatic_install_security_updates = "AlwaysOn"
deferral_major_period_days = 30
deferral_minor_period_days = 14
deferral_system_period_days = 3
notifications_enabled = true
rapid_security_response_enabled = true
rapid_security_response_rollback_enabled = false
recommended_cadence = "Newest"
}
}
And here's a blueprint that enforces a passcode policy:
resource "jamfplatform_blueprints_blueprint" "passcode_policy" {
name = "Passcode Policy"
description = "Managed by Terraform"
deployed = true
device_groups = [jamfplatform_device_group.macos_26_plus.id]
passcode_policy = {
require_passcode = true
require_alphanumeric_passcode = true
minimum_length = 12
minimum_complex_characters = 1
maximum_failed_attempts = 10
maximum_inactivity_in_minutes = 5
maximum_passcode_age_in_days = 90
passcode_reuse_limit = 5
}
}
Each blueprint maps to a single set of device groups via the device_groups attribute, which takes a set of Platform UUIDs. Setting deployed to true tells the provider to deploy the blueprint (and redeploy it if it falls out of date). Setting it to false undeploys it.
You can also use the software_update component to enforce a specific OS version by a specific date, the legacy_payloads attribute for traditional configuration profile payloads, or custom_declarations for arbitrary DDM declarations that the provider doesn't yet have a dedicated component for.
4. Apply your configuration
The same commands you know from the other providers apply here:
terraform init # Initialize and download the provider
terraform plan # Review what will be created, changed or destroyed
terraform apply # Apply the changes (you'll be asked to confirm)
Bringing an existing tenant under management
If you already have blueprints, benchmarks and device groups configured in your tenant, you don't need to start from scratch. Every resource in the provider supports terraform import, so you can bring existing resources under Terraform management without recreating them.
Discovering existing resources with Terraform query
If you're running Terraform 1.14+, the provider supports list resources, a feature that lets you query your existing infrastructure directly from Terraform. This is useful for discovering what's already in your tenant before importing it.
Create a query file (e.g. discover.tfquery.hcl):
list "jamfplatform_blueprints_blueprint" "software_update" {
provider = jamfplatform
include_resource = true
config {
search = "software update"
}
}
list "jamfplatform_cbengine_benchmark" "cis_benchmarks" {
provider = jamfplatform
include_resource = true
config {
search = "CIS"
}
}
list "jamfplatform_device_group" "smart_computer_groups" {
provider = jamfplatform
include_resource = true
config {
filter {
selector = "deviceType"
argument = "COMPUTER"
}
filter {
join_with = "and"
selector = "groupType"
argument = "SMART"
}
}
}
Then run:
terraform query -generate-config-out=generated.tf
Terraform will query your tenant, return the matching resources along with their IDs, and generate both the resource blocks and import blocks into generated.tf. The blueprint and benchmark list resources support a search filter for case-insensitive substring matching against names and descriptions. The device group list resource supports filter blocks with RSQL selectors for name, description, deviceType and groupType, giving you precise control over which groups are returned.
Importing resources
Once you have the IDs you need, you can use import blocks to bring them under management. With Terraform 1.5+:
import {
to = jamfplatform_blueprints_blueprint.software_update_settings
id = "your-blueprint-uuid"
}
import {
to = jamfplatform_cbengine_benchmark.cis_level_1
id = "your-benchmark-uuid"
}
Run terraform plan to generate the configuration for the imported resources, refine it as needed, and from that point forward they're managed as code. For more on bulk importing resources, see HashiCorp's bulk import documentation.
Using alongside other Jamf providers
This provider is designed to work alongside the other Terraform providers in the Jamf ecosystem. You can use it together with:
- Jamf Protect Provider - for managing resources in Jamf Protect (endpoint security plans, threat prevention, telemetry and more) via the Jamf Protect GraphQL API.
- Deployment Theory Jamf Pro Provider - for managing resources in Jamf Pro (policies, scripts, configuration profiles, groups and more) via its Classic and Pro APIs.
Between these providers, you can manage your full Jamf environment as code from a single Terraform project. A common pattern is to use the Jamf Pro provider to create resources like smart groups and policies, then reference the resulting Platform IDs in the Jamf Platform provider for benchmarks and blueprints.
Getting involved
The provider is open-source under the MPL license and published on both the HashiCorp and OpenTofu registries. It's built on HashiCorp's Terraform Plugin Framework (Protocol v6) and includes integration tests throughout. The provider uses our open source jamfplatform-go-sdk that you can import into your own Go projects for scripting and automation against the Jamf Platform API.
We welcome contributions. Bug reports, feature requests and pull requests are all appreciated. The provider will continue to grow in lockstep with the Jamf Platform APIs as more capabilities are added.