Retrieving Data for Reporting from Jamf: Python SDK vs API vs CLI
READER BEWARE: THE FOLLOWING WRITTEN ENTIRELY BY AI WITHOUT HUMAN EDITING.
Introduction
Jamf Pro is a comprehensive management solution for Apple devices, widely used by enterprises to manage macOS, iOS, iPadOS, and tvOS devices. As organizations scale their Apple device fleets, the need for robust reporting and analytics becomes critical. Whether you’re tracking compliance, monitoring device inventory, or analyzing configuration drift, extracting data from Jamf Pro is essential for informed decision-making.
This guide explores three primary methods for retrieving data from Jamf Pro for reporting and analytics purposes:
- Jamf Pro Python SDK - Official Python library for programmatic access
- Jamf Pro API - RESTful API for direct HTTP integration
- Jamf Pro CLI (jamf binary) - Command-line interface for local operations
Each approach has distinct advantages, use cases, and authentication mechanisms. We’ll compare and contrast these methods to help you choose the right tool for your reporting needs.
Overview: Three Approaches to Jamf Data Retrieval
Jamf Pro Python SDK
The official Python SDK provides a Pythonic interface to the Jamf Pro API, making it ideal for:
- Building automated reporting scripts
- Creating data pipelines and ETL processes
- Integrating with Python-based analytics tools
- Rapid prototyping and development
Jamf Pro API
The REST API offers direct HTTP access to Jamf Pro data, suitable for:
- Language-agnostic integrations
- Webhook and event-driven architectures
- Custom applications in any language
- Fine-grained control over requests
Jamf Pro CLI (jamf binary)
The jamf binary is a command-line tool installed on managed devices, useful for:
- Local device reporting
- On-device configuration checks
- Inventory collection scripts
- Extension attribute data collection
Important Note: The jamf binary is primarily designed for client-side operations on managed devices, not for server-side reporting. For comprehensive server-side reporting and analytics, the Python SDK or API are more appropriate choices.
Method 1: Jamf Pro Python SDK
Installation
The official Jamf Pro SDK can be installed via pip:
# Install the official Jamf Pro SDK
pip install jamf-pro-sdk
# Or install with development dependencies
pip install jamf-pro-sdk[dev]
Authentication
The Python SDK supports multiple authentication methods:
1. API Client Credentials (Recommended)
from jamf_pro_sdk import JamfProClient, BasicAuthProvider
# Using API client credentials (OAuth)
client = JamfProClient(
server="https://your-instance.jamfcloud.com",
credentials=BasicAuthProvider(
username="your-username",
password="your-password"
)
)
# The SDK handles token refresh automatically
2. Bearer Token Authentication
from jamf_pro_sdk import JamfProClient, BearerTokenAuthProvider
# Using a pre-obtained bearer token
client = JamfProClient(
server="https://your-instance.jamfcloud.com",
credentials=BearerTokenAuthProvider(
token="your-bearer-token"
)
)
3. API Role-Based Access
For production environments, create dedicated API roles and clients:
- Navigate to Settings → System → API Roles and Clients
- Create a new API role with read-only permissions for reporting
- Create an API client and assign the role
- Use the client credentials in your scripts
from jamf_pro_sdk import JamfProClient, BasicAuthProvider
# Using dedicated API client
client = JamfProClient(
server="https://your-instance.jamfcloud.com",
credentials=BasicAuthProvider(
username="reporting-api-client",
password="secure-api-client-secret"
)
)
Retrieving Data for Reporting
Computer Inventory Report
from jamf_pro_sdk import JamfProClient, BasicAuthProvider
import csv
from datetime import datetime
def generate_computer_inventory_report():
"""Generate comprehensive computer inventory report"""
# Initialize client
client = JamfProClient(
server="https://your-instance.jamfcloud.com",
credentials=BasicAuthProvider(
username="api-user",
password="api-password"
)
)
try:
# Retrieve all computers
computers = client.pro_api.get_computers_inventory()
# Prepare CSV report
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f'jamf_inventory_report_{timestamp}.csv'
with open(filename, 'w', newline='') as csvfile:
fieldnames = [
'Computer ID',
'Computer Name',
'Serial Number',
'Model',
'OS Version',
'Last Check-in',
'Managed',
'Department',
'Building'
]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for computer in computers.results:
writer.writerow({
'Computer ID': computer.id,
'Computer Name': computer.general.name,
'Serial Number': computer.general.serial_number,
'Model': computer.hardware.model,
'OS Version': computer.operating_system.version,
'Last Check-in': computer.general.last_contact_time,
'Managed': computer.general.remote_management.managed,
'Department': computer.location.department if computer.location else 'N/A',
'Building': computer.location.building if computer.location else 'N/A'
})
print(f"Report generated: {filename}")
print(f"Total computers: {len(computers.results)}")
finally:
# Close the client connection
client.close()
# Run the report
generate_computer_inventory_report()
Configuration Profile Compliance Report
from jamf_pro_sdk import JamfProClient, BasicAuthProvider
import json
def generate_profile_compliance_report():
"""Check configuration profile deployment status"""
client = JamfProClient(
server="https://your-instance.jamfcloud.com",
credentials=BasicAuthProvider(
username="api-user",
password="api-password"
)
)
try:
# Get all configuration profiles
profiles = client.classic_api.get_os_x_configuration_profiles()
compliance_data = []
for profile in profiles:
# Get detailed profile information
profile_detail = client.classic_api.get_os_x_configuration_profile_by_id(profile.id)
deployed_count = len(profile_detail.computers) if profile_detail.computers else 0
scope = profile_detail.scope
compliance_data.append({
'profile_name': profile_detail.general.name,
'profile_id': profile_detail.general.id,
'deployed_computers': deployed_count,
'description': profile_detail.general.description,
'level': profile_detail.general.level,
'enabled': profile_detail.general.enabled
})
# Save report
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
report_file = f'profile_compliance_{timestamp}.json'
with open(report_file, 'w') as f:
json.dump(compliance_data, f, indent=2)
print(f"Profile compliance report saved: {report_file}")
# Print summary
print(f"\nSummary:")
print(f"Total profiles: {len(compliance_data)}")
total_deployments = sum(p['deployed_computers'] for p in compliance_data)
print(f"Total deployments: {total_deployments}")
finally:
client.close()
generate_profile_compliance_report()
Mobile Device Analytics Report
from jamf_pro_sdk import JamfProClient, BasicAuthProvider
import pandas as pd
from collections import Counter
def generate_mobile_device_analytics():
"""Generate analytics report for mobile devices"""
client = JamfProClient(
server="https://your-instance.jamfcloud.com",
credentials=BasicAuthProvider(
username="api-user",
password="api-password"
)
)
try:
# Get all mobile devices
devices = client.pro_api.get_mobile_devices()
# Extract analytics data
os_versions = []
models = []
supervised_count = 0
managed_count = 0
for device in devices.results:
if device.ios:
os_versions.append(device.ios.version)
models.append(device.ios.model)
supervised_count += 1 if device.ios.supervised else 0
managed_count += 1 if device.general.managed else 0
# Create analytics summary
analytics = {
'total_devices': len(devices.results),
'supervised_devices': supervised_count,
'managed_devices': managed_count,
'os_version_distribution': dict(Counter(os_versions)),
'model_distribution': dict(Counter(models)),
}
# Create DataFrame for detailed analysis
df = pd.DataFrame({
'OS Version': os_versions,
'Model': models
})
# Save analytics
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Save summary
with open(f'mobile_analytics_summary_{timestamp}.json', 'w') as f:
json.dump(analytics, f, indent=2)
# Save detailed CSV
df.to_csv(f'mobile_analytics_detail_{timestamp}.csv', index=False)
print("Mobile Device Analytics Report")
print("=" * 50)
print(f"Total Devices: {analytics['total_devices']}")
print(f"Supervised: {analytics['supervised_devices']}")
print(f"Managed: {analytics['managed_devices']}")
print(f"\nTop 5 OS Versions:")
for version, count in sorted(
analytics['os_version_distribution'].items(),
key=lambda x: x[1],
reverse=True
)[:5]:
print(f" {version}: {count}")
finally:
client.close()
generate_mobile_device_analytics()
Advantages of Python SDK
- Type Safety: Proper Python typing and IDE autocomplete
- Error Handling: Built-in exception handling and retry logic
- Abstraction: No need to manage HTTP requests and response parsing
- Documentation: Comprehensive docstrings and examples
- Maintenance: Official support and regular updates from Jamf
- Pythonic: Follows Python conventions and best practices
Limitations
- Python Only: Requires Python runtime environment
- Dependencies: Additional package management overhead
- Abstraction Layer: Less control over raw HTTP requests
- Learning Curve: Need to understand SDK-specific patterns
Method 2: Jamf Pro REST API
Authentication
The Jamf Pro API supports several authentication methods:
1. Basic Authentication (Legacy)
# Get a bearer token using Basic Auth
curl -X POST \
"https://your-instance.jamfcloud.com/api/v1/auth/token" \
-H "Content-Type: application/json" \
-u "username:password"
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires": "2025-11-19T10:10:00Z"
}
2. Bearer Token Authentication
# Use the token for subsequent requests
curl -X GET \
"https://your-instance.jamfcloud.com/api/v1/computers-inventory" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
3. API Client Credentials
# Using API client credentials (OAuth 2.0)
curl -X POST \
"https://your-instance.jamfcloud.com/api/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=your-client-id" \
-d "client_secret=your-client-secret" \
-d "grant_type=client_credentials"
Retrieving Data with cURL
Computer Inventory
#!/bin/bash
# get-computer-inventory.sh
JAMF_URL="https://your-instance.jamfcloud.com"
USERNAME="api-user"
PASSWORD="api-password"
# Get bearer token
TOKEN=$(curl -s -X POST \
"${JAMF_URL}/api/v1/auth/token" \
-H "Content-Type: application/json" \
-u "${USERNAME}:${PASSWORD}" | \
jq -r '.token')
# Get computer inventory
curl -s -X GET \
"${JAMF_URL}/api/v1/computers-inventory" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Accept: application/json" | \
jq '.results[] | {
id: .id,
name: .general.name,
serialNumber: .general.serialNumber,
model: .hardware.model,
osVersion: .operatingSystem.version,
lastContact: .general.lastContactTime
}' > computer_inventory.json
# Invalidate token
curl -s -X POST \
"${JAMF_URL}/api/v1/auth/invalidate-token" \
-H "Authorization: Bearer ${TOKEN}"
echo "Computer inventory saved to computer_inventory.json"
Configuration Profile Report
#!/bin/bash
# get-profiles-report.sh
JAMF_URL="https://your-instance.jamfcloud.com"
USERNAME="api-user"
PASSWORD="api-password"
# Authenticate
TOKEN=$(curl -s -X POST \
"${JAMF_URL}/api/v1/auth/token" \
-u "${USERNAME}:${PASSWORD}" | \
jq -r '.token')
# Get all macOS configuration profiles (Classic API)
curl -s -X GET \
"${JAMF_URL}/JSSResource/osxconfigurationprofiles" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Accept: application/json" | \
jq '.os_x_configuration_profiles[] | {
id: .id,
name: .name
}' | jq -s '.' > profiles_list.json
# Get detailed info for each profile
for profile_id in $(jq -r '.[].id' profiles_list.json); do
curl -s -X GET \
"${JAMF_URL}/JSSResource/osxconfigurationprofiles/id/${profile_id}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Accept: application/json" | \
jq '.os_x_configuration_profile | {
id: .general.id,
name: .general.name,
description: .general.description,
level: .general.level,
computerCount: (.computers | length),
enabled: .general.enabled
}' >> profile_details.json
sleep 0.5 # Rate limiting
done
# Invalidate token
curl -s -X POST \
"${JAMF_URL}/api/v1/auth/invalidate-token" \
-H "Authorization: Bearer ${TOKEN}"
echo "Profile reports generated"
Using Python with Requests Library
import requests
import json
from datetime import datetime
class JamfAPIClient:
"""Simple Jamf API client using requests"""
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip('/')
self.username = username
self.password = password
self.token = None
self.token_expiry = None
def authenticate(self):
"""Get bearer token"""
response = requests.post(
f"{self.base_url}/api/v1/auth/token",
headers={"Content-Type": "application/json"},
auth=(self.username, self.password)
)
response.raise_for_status()
data = response.json()
self.token = data['token']
self.token_expiry = data['expires']
return self.token
def invalidate_token(self):
"""Invalidate the current token"""
if self.token:
requests.post(
f"{self.base_url}/api/v1/auth/invalidate-token",
headers={"Authorization": f"Bearer {self.token}"}
)
self.token = None
def get_computers_inventory(self, page=0, page_size=100):
"""Get computer inventory with pagination"""
if not self.token:
self.authenticate()
response = requests.get(
f"{self.base_url}/api/v1/computers-inventory",
headers={
"Authorization": f"Bearer {self.token}",
"Accept": "application/json"
},
params={
"page": page,
"page-size": page_size
}
)
response.raise_for_status()
return response.json()
def get_mobile_devices(self):
"""Get all mobile devices"""
if not self.token:
self.authenticate()
response = requests.get(
f"{self.base_url}/api/v2/mobile-devices",
headers={
"Authorization": f"Bearer {self.token}",
"Accept": "application/json"
}
)
response.raise_for_status()
return response.json()
# Usage example
def generate_api_inventory_report():
"""Generate inventory report using direct API calls"""
client = JamfAPIClient(
base_url="https://your-instance.jamfcloud.com",
username="api-user",
password="api-password"
)
try:
# Get computer inventory
computers_data = client.get_computers_inventory()
# Process and save
report = []
for computer in computers_data.get('results', []):
report.append({
'id': computer.get('id'),
'name': computer.get('general', {}).get('name'),
'serial': computer.get('general', {}).get('serialNumber'),
'model': computer.get('hardware', {}).get('model'),
'os': computer.get('operatingSystem', {}).get('version'),
'last_contact': computer.get('general', {}).get('lastContactTime')
})
# Save report
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f'api_inventory_report_{timestamp}.json'
with open(filename, 'w') as f:
json.dump(report, f, indent=2)
print(f"Report generated: {filename}")
print(f"Total computers: {len(report)}")
finally:
client.invalidate_token()
generate_api_inventory_report()
Advantages of Direct API
- Language Agnostic: Use any language with HTTP support
- Full Control: Complete control over request/response handling
- Flexibility: Can implement custom retry logic, caching, etc.
- Documentation: Comprehensive API documentation from Jamf
- No Dependencies: Just need HTTP client (curl, requests, etc.)
- Latest Features: Access to newest API endpoints immediately
Limitations
- Manual Implementation: Need to handle authentication, pagination, errors
- Maintenance: API changes require manual updates
- Boilerplate: More code for common operations
- Error Handling: Need to implement comprehensive error handling
- Type Safety: No built-in type checking or validation
Method 3: Jamf Pro CLI (jamf binary)
Overview
The jamf binary is a command-line tool installed on managed Mac computers. It’s primarily designed for client-side operations rather than server-side reporting. However, it can be useful for:
- Collecting local device information
- Running recon to update inventory
- Checking local policy compliance
- Creating extension attributes
Important Limitation: The jamf binary is not suitable for centralized reporting of multiple devices. It operates on the local machine only.
Authentication
The jamf binary authenticates using the computer’s management framework:
# The jamf binary uses the computer's existing enrollment
# No separate authentication is required on enrolled devices
# Check enrollment status
sudo jamf checkJSSConnection
# Output:
# Checking availability of https://your-instance.jamfcloud.com/...
# The JSS is available.
Local Data Collection
Basic Inventory Collection
#!/bin/bash
# local-inventory-check.sh - Run on individual Macs
# Collect and submit updated inventory
sudo jamf recon
# Get computer ID
COMPUTER_ID=$(sudo jamf getComputerName | grep "Computer ID" | awk '{print $3}')
# Check policies
sudo jamf policy -verbose
# Get installed applications
system_profiler SPApplicationsDataType -json > applications.json
# Get hardware info
system_profiler SPHardwareDataType -json > hardware.json
echo "Local inventory collected for Computer ID: ${COMPUTER_ID}"
Extension Attributes for Reporting
Extension attributes allow you to collect custom data:
#!/bin/bash
# Extension Attribute: Check FileVault Status
FILEVAULT_STATUS=$(fdesetup status | head -1)
if [[ "$FILEVAULT_STATUS" == "FileVault is On." ]]; then
echo "<result>Enabled</result>"
else
echo "<result>Disabled</result>"
fi
#!/bin/bash
# Extension Attribute: Check Last Backup Date
LAST_BACKUP=$(tmutil latestbackup 2>/dev/null)
if [[ -n "$LAST_BACKUP" ]]; then
BACKUP_DATE=$(GetFileInfo -m "$LAST_BACKUP" 2>/dev/null | awk '{print $1, $2}')
echo "<result>$BACKUP_DATE</result>"
else
echo "<result>No Backup Found</result>"
fi
Limitations of jamf Binary for Reporting
- Local Only: Operates on individual devices, not suitable for fleet-wide reporting
- Requires Root: Most commands require sudo/root privileges
- No Aggregation: Cannot aggregate data from multiple devices
- Enrolled Devices: Only works on Jamf-enrolled computers
- Limited Query: Cannot query Jamf server for other devices’ data
Recommendation: Use the jamf binary for extension attributes and local checks, but use the Python SDK or API for centralized reporting and analytics.
Comparison Matrix
| Feature | Python SDK | REST API | jamf Binary |
|---|---|---|---|
| Use Case | Python automation | Any language | Local device ops |
| Authentication | API credentials | Bearer tokens | Device enrollment |
| Data Scope | All devices | All devices | Local device only |
| Programming Model | Object-oriented | HTTP requests | Command-line |
| Type Safety | Yes (typing) | No | N/A |
| Error Handling | Built-in | Manual | Exit codes |
| Documentation | SDK docs | API docs | Man pages |
| Learning Curve | Medium | Low-Medium | Low |
| Flexibility | Medium | High | Low |
| Dependencies | Python packages | HTTP client | Enrolled device |
| Maintenance | Automatic updates | Manual | OS updates |
| Best For | Python scripts | Custom apps | Extension attrs |
| Multi-device | Yes | Yes | No |
| Rate Limiting | Built-in | Manual | N/A |
| Pagination | Automatic | Manual | N/A |
When to Use Each Method
Choose Python SDK When:
- Building Python-based reporting tools
- Need rapid development with less boilerplate
- Want automatic handling of pagination and rate limiting
- Prefer object-oriented programming
- Have Python expertise on your team
- Building complex data pipelines with Python libraries (pandas, numpy)
Example Use Cases:
- Daily automated inventory reports
- Compliance dashboards
- Data export to BI tools
- Integration with Python analytics frameworks
Choose REST API When:
- Working with non-Python languages
- Need fine-grained control over HTTP requests
- Building microservices or serverless functions
- Creating language-agnostic tools
- Want minimal dependencies
- Need to integrate with specific HTTP clients/frameworks
Example Use Cases:
- Node.js, Go, or Java applications
- Webhook integrations
- AWS Lambda functions
- Custom web dashboards
- Integration with other APIs
Choose jamf Binary When:
- Collecting device-specific data
- Building extension attributes
- Running local compliance checks
- Triggering policies on devices
- Need device-side operations
Example Use Cases:
- Extension attribute scripts
- Local compliance verification
- Custom inventory data collection
- Self-service policy triggers
Complete Reporting Example: Multi-Method Approach
Here’s a comprehensive example that combines multiple methods:
#!/usr/bin/env python3
"""
Comprehensive Jamf Reporting Script
Combines Python SDK for data collection with various output formats
"""
from jamf_pro_sdk import JamfProClient, BasicAuthProvider
import json
import csv
import os
from datetime import datetime
from collections import Counter
import argparse
class JamfReporter:
"""Comprehensive Jamf reporting tool"""
def __init__(self, server, username, password):
self.client = JamfProClient(
server=server,
credentials=BasicAuthProvider(
username=username,
password=password
)
)
self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
def generate_executive_summary(self, output_dir='reports'):
"""Generate executive summary report"""
os.makedirs(output_dir, exist_ok=True)
# Collect data
computers = self.client.pro_api.get_computers_inventory()
mobile_devices = self.client.pro_api.get_mobile_devices()
# Calculate metrics
summary = {
'report_date': datetime.now().isoformat(),
'computers': {
'total': len(computers.results),
'managed': sum(1 for c in computers.results if c.general.managed),
'os_versions': dict(Counter(
c.operating_system.version for c in computers.results
))
},
'mobile_devices': {
'total': len(mobile_devices.results),
'supervised': sum(
1 for d in mobile_devices.results
if d.ios and d.ios.supervised
)
}
}
# Save JSON
json_file = f'{output_dir}/executive_summary_{self.timestamp}.json'
with open(json_file, 'w') as f:
json.dump(summary, f, indent=2)
# Create readable text report
txt_file = f'{output_dir}/executive_summary_{self.timestamp}.txt'
with open(txt_file, 'w') as f:
f.write("JAMF PRO EXECUTIVE SUMMARY\n")
f.write("=" * 50 + "\n")
f.write(f"Report Date: {summary['report_date']}\n\n")
f.write("COMPUTERS\n")
f.write(f" Total: {summary['computers']['total']}\n")
f.write(f" Managed: {summary['computers']['managed']}\n")
f.write(f"\n OS Distribution:\n")
for version, count in sorted(
summary['computers']['os_versions'].items(),
key=lambda x: x[1],
reverse=True
):
f.write(f" {version}: {count}\n")
f.write(f"\nMOBILE DEVICES\n")
f.write(f" Total: {summary['mobile_devices']['total']}\n")
f.write(f" Supervised: {summary['mobile_devices']['supervised']}\n")
return summary
def generate_detailed_inventory(self, output_dir='reports'):
"""Generate detailed CSV inventory"""
os.makedirs(output_dir, exist_ok=True)
computers = self.client.pro_api.get_computers_inventory()
csv_file = f'{output_dir}/detailed_inventory_{self.timestamp}.csv'
with open(csv_file, 'w', newline='') as f:
fieldnames = [
'ID', 'Name', 'Serial Number', 'Model',
'OS Version', 'IP Address', 'Last Contact',
'Managed', 'Department', 'Building', 'Username'
]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for computer in computers.results:
writer.writerow({
'ID': computer.id,
'Name': computer.general.name,
'Serial Number': computer.general.serial_number,
'Model': computer.hardware.model,
'OS Version': computer.operating_system.version,
'IP Address': computer.general.last_ip_address,
'Last Contact': computer.general.last_contact_time,
'Managed': computer.general.remote_management.managed,
'Department': computer.location.department if computer.location else '',
'Building': computer.location.building if computer.location else '',
'Username': computer.user_and_location.username if computer.user_and_location else ''
})
print(f"Detailed inventory saved: {csv_file}")
def generate_compliance_report(self, output_dir='reports'):
"""Generate compliance report"""
os.makedirs(output_dir, exist_ok=True)
computers = self.client.pro_api.get_computers_inventory()
# Define compliance criteria
compliance_data = []
for computer in computers.results:
# Example: Check OS version compliance (macOS 13.0+)
os_major = int(computer.operating_system.version.split('.')[0])
os_compliant = os_major >= 13
# Example: Check FileVault status
filevault_enabled = (
computer.security and
computer.security.filevault_status == 'Enabled'
)
compliance_data.append({
'computer_name': computer.general.name,
'serial_number': computer.general.serial_number,
'os_version': computer.operating_system.version,
'os_compliant': os_compliant,
'filevault_enabled': filevault_enabled,
'overall_compliant': os_compliant and filevault_enabled
})
# Save compliance report
json_file = f'{output_dir}/compliance_report_{self.timestamp}.json'
with open(json_file, 'w') as f:
json.dump(compliance_data, f, indent=2)
# Calculate statistics
total = len(compliance_data)
compliant = sum(1 for c in compliance_data if c['overall_compliant'])
print(f"\nCompliance Summary:")
print(f" Total Devices: {total}")
print(f" Compliant: {compliant} ({compliant/total*100:.1f}%)")
print(f" Non-Compliant: {total-compliant} ({(total-compliant)/total*100:.1f}%)")
def close(self):
"""Clean up resources"""
self.client.close()
def main():
parser = argparse.ArgumentParser(
description='Generate Jamf Pro reports'
)
parser.add_argument(
'--server',
required=True,
help='Jamf Pro server URL'
)
parser.add_argument(
'--username',
required=True,
help='API username'
)
parser.add_argument(
'--password',
required=True,
help='API password'
)
parser.add_argument(
'--output-dir',
default='reports',
help='Output directory for reports'
)
parser.add_argument(
'--reports',
nargs='+',
choices=['summary', 'inventory', 'compliance', 'all'],
default=['all'],
help='Reports to generate'
)
args = parser.parse_args()
reporter = JamfReporter(
server=args.server,
username=args.username,
password=args.password
)
try:
reports = args.reports
if 'all' in reports:
reports = ['summary', 'inventory', 'compliance']
if 'summary' in reports:
print("Generating executive summary...")
reporter.generate_executive_summary(args.output_dir)
if 'inventory' in reports:
print("Generating detailed inventory...")
reporter.generate_detailed_inventory(args.output_dir)
if 'compliance' in reports:
print("Generating compliance report...")
reporter.generate_compliance_report(args.output_dir)
print("\nAll reports generated successfully!")
finally:
reporter.close()
if __name__ == '__main__':
main()
Run the comprehensive reporter:
# Generate all reports
python3 jamf_reporter.py \
--server "https://your-instance.jamfcloud.com" \
--username "api-user" \
--password "api-password" \
--output-dir "./jamf-reports"
# Generate specific reports
python3 jamf_reporter.py \
--server "https://your-instance.jamfcloud.com" \
--username "api-user" \
--password "api-password" \
--reports summary compliance
Best Practices
Security
Never Hardcode Credentials: Use environment variables or secure vaults
import os username = os.environ.get('JAMF_API_USER') password = os.environ.get('JAMF_API_PASSWORD')Use API Roles: Create dedicated API roles with minimum required permissions
Token Management: Always invalidate tokens when done
try: # Use client data = client.get_computers() finally: client.invalidate_token()Secure Storage: Encrypt sensitive reports and use secure file permissions
Performance
Pagination: Use pagination for large datasets
page = 0 all_computers = [] while True: result = client.get_computers_inventory(page=page, page_size=100) all_computers.extend(result.results) if len(result.results) < 100: break page += 1Rate Limiting: Implement delays to avoid API rate limits
import time for item in items: process_item(item) time.sleep(0.5) # 500ms delayCaching: Cache frequently accessed data
from functools import lru_cache @lru_cache(maxsize=100) def get_computer_details(computer_id): return client.get_computer_by_id(computer_id)
Error Handling
from jamf_pro_sdk.exceptions import JamfProAPIError
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def safe_api_call(func, *args, **kwargs):
"""Wrapper for safe API calls with retry logic"""
max_retries = 3
retry_delay = 5
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except JamfProAPIError as e:
logger.error(f"API error on attempt {attempt + 1}: {e}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
raise
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise
# Usage
computers = safe_api_call(client.get_computers_inventory)
Conclusion
Choosing the right method for retrieving Jamf Pro data depends on your specific needs:
- Python SDK: Best for Python-based automation and rapid development
- REST API: Ideal for custom applications in any language
- jamf Binary: Limited to local device operations and extension attributes
For comprehensive reporting and analytics, the Python SDK offers the best balance of ease of use, features, and maintainability. The REST API provides maximum flexibility for non-Python environments. The jamf binary serves a specific purpose for local device operations but should not be relied upon for centralized reporting.
Recommended Approach
For most reporting needs:
- Use Python SDK for centralized data collection and analysis
- Use REST API for custom integrations and non-Python applications
- Use jamf binary only for extension attributes and local device checks
Start with the Python SDK for your reporting infrastructure, and you’ll have a solid foundation for building comprehensive Jamf Pro analytics and reporting capabilities.