
For this post, I wanted to share how to get important notifications that may be on your Jamf Pro server without having to actually open a web browser, navigating to your Jamf Pro server, logging in, and viewing the notifications.
This task can be done utilizing the Jamf Pro API. The endpoint we will be focusing on is /api/v1/notifications
Why is this important? Jamf Pro will notify if there are issues that need to be addressed. These notifications can include things like the ADE token expiring soon, the APNS certificate is expired, or errors with the connected Jamf Infrastructure Manager. There is a need to get alerted on these notifications so the issues can be addressed before they become part of a much larger issue.
Let’s dig in…
When using the Jamf Pro API, a valid Jamf Pro account is needed. When creating Jamf Pro User Accounts for the purpose of using with the API, refer to the following link on what permissions are needed for each specific task:
https://developer.jamf.com/jamf-pro/docs/privileges-and-deprecations
For the endpoint /v1/notifications with the get operation, there are no privileges needed. This service can be created with no privileges and it will still work for this purpose.


Now that the account is created in Jamf Pro, we can start building the script that we will use.
When using the Jamf Pro API, we need to use an account to authenticate, we will use the account created in the previous step and we will look at how to generate a bearer token with the API. Supply the correct username, password, and Jamf Pro URL and our script will use those variables to generate a bearer token that will be used to authorize our API calls.
#!/bin/bash # API Credentials jamfUser="api_Notifications" jamfPass="jamf1234" jssURL="https://techitout.jamfcloud.com" # Encode Credentials encodedCredentials=$( printf "${jamfUser}:${jamfPass}" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - ) # Generate an auth token authToken=$( /usr/bin/curl "${jssURL}/uapi/auth/token" \ --silent \ --request POST \ --header "Authorization: Basic ${encodedCredentials}" ) # Parse authToken for token, omit expiration token=$( /usr/bin/awk -F \" '{ print $4 }' <<< "${authToken}" | /usr/bin/xargs )
If we then do an echo $token we can see our Bearer Token value:
username@computername ~ % echo $token eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiY2JhMDU5ZDYtZGNiNC00ZDg2LTg5MjYtMTc0NGZkOGY4YzI0IiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxMyIsImV4cCI6MTY0ODY4MjkzN30.FF5s6LcNJue-tkCcMSf7zlOWRTf-Kv30K-eGwVVddF0
Now that we have the authentication portion of our script taken care of, we can now build the rest of our script. There are several possible notifications that Jamf Pro can give us, we can take these values and store them in an array of possibilities like this:
# Build an array of possible important notifications from Jamf Pro noticationsArr=( APNS_CERT_REVOKED APNS_CONNECTION_FAILURE APPLE_SCHOOL_MANAGER_T_C_NOT_SIGNED BUILT_IN_CA_EXPIRED BUILT_IN_CA_EXPIRING BUILT_IN_CA_RENEWAL_FAILED BUILT_IN_CA_RENEWAL_SUCCESS CLOUD_LDAP_CERT_EXPIRED CLOUD_LDAP_CERT_WILL_EXPIRE COMPUTER_SECURITY_SSL_DISABLED DEP_INSTANCE_EXPIRED DEP_INSTANCE_WILL_EXPIRE DEVICE_ENROLLMENT_PROGRAM_T_C_NOT_SIGNED EXCEEDED_LICENSE_COUNT FREQUENT_INVENTORY_COLLECTION_POLICY GSX_CERT_EXPIRED GSX_CERT_WILL_EXPIRE HCL_BIND_ERROR HCL_ERROR INSECURE_LDAP INVALID_REFERENCES_EXT_ATTR INVALID_REFERENCES_POLICIES INVALID_REFERENCES_SCRIPTS JAMF_CONNECT_UPDATE JAMF_PROTECT_UPDATE JIM_ERROR LDAP_CONNECTION_CHECK_THROUGH_JIM_FAILED LDAP_CONNECTION_CHECK_THROUGH_JIM_SUCCESSFUL MDM_EXTERNAL_SIGNING_CERTIFICATE_EXPIRED MDM_EXTERNAL_SIGNING_CERTIFICATE_EXPIRING MDM_EXTERNAL_SIGNING_CERTIFICATE_EXPIRING_TODAY MII_HEARTBEAT_FAILED_NOTIFICATION MII_INVENTORY_UPLOAD_FAILED_NOTIFICATION MII_UNATHORIZED_RESPONSE_NOTIFICATION PATCH_EXTENTION_ATTRIBUTE PATCH_UPDATE POLICY_MANAGEMENT_ACCOUNT_PAYLOAD_SECURITY_MULTIPLE POLICY_MANAGEMENT_ACCOUNT_PAYLOAD_SECURITY_SINGLE PUSH_CERT_EXPIRED PUSH_CERT_WILL_EXPIRE PUSH_PROXY_CERT_EXPIRED SSO_CERT_EXPIRED SSO_CERT_WILL_EXPIRE TOMCAT_SSL_CERT_EXPIRED TOMCAT_SSL_CERT_WILL_EXPIRE USER_INITIATED_ENROLLMENT_MANAGEMENT_ACCOUNT_SECURITY_ISSUE USER_MAID_DUPLICATE_ERROR USER_MAID_MISMATCH_ERROR USER_MAID_ROSTER_DUPLICATE_ERROR VPP_ACCOUNT_EXPIRED VPP_ACCOUNT_WILL_EXPIRE VPP_TOKEN_REVOKED DEVICE_COMPLIANCE_CONNECTION_ERROR CONDITIONAL_ACCESS_CONNECTION_ERROR AZURE_AD_MIGRATION_REPORT_GENERATED )
We now know what the possible notifications are, so let’s get the notifications from our Jamf Pro server. Notice this is where we use our Bearer token that was generated earlier:
# Returns important notifications from Jamf Pro in JSON data=$( curl --request GET \ --url "${jssURL}/api/v1/notifications" \ --header "Accept: application/json" \ --header "Authorization: Bearer ${token}" \ )
The next step we need to do is set up a loop to find our notifications match a notification in the array that we set earlier. The logic for this loop will look like:
for str in ${notificationsArr[@]}; do if [[ " ${data} " =~ ${str} ]]; then # Notification in list matches notification from $data fi done
Now, I’m sure that we want to do more with this data than just compare to a list. The next step is to populate our loop with useful steps that will give us the information that we want to know in a readable form.
# Loop each notification possibility and if it exists in the results, grab information on the notification using jq and notify for str in ${noticationsArr[@]}; do if [[ " ${data} " =~ ${str} ]]; then # Create a clean string for read ability cleanString=$( echo $str | sed 's/_/ /g' ) name=$( echo ${data} | jq --arg v "${str}" '.[]|select(.type==$v).params.name') # Create a clean name for read ability cleanname=$( echo $name | sed 's/"//g') days=$( echo ${data} | jq --arg v "${str}" '.[]|select(.type==$v).params.days') id=$( echo ${data} | jq --arg v "${str}" '.[]|select(.type==$v).params.id') if [[ ${name} != "null" ]]; then if [[ ${days} != "null" ]]; then if [[ ${id} != "null" ]]; then message=$( echo "$JPInstance Notification: $cleanString for $cleanname in $days days" ) else message=$( echo "$JPInstance Notification: $cleanString for $cleanname in $days days" ) fi else message=$( echo "$JPInstance Notification: $cleanString for $name" ) fi else message=$( echo "$JPInstance Notification: $cleanString" ) fi # Echo the message for each alert echo ${message} fi done
Putting it all together:
#!/bin/bash # API Credentials jamfUser="api_Notifications" jamfPass="jamf1234" jssURL="https://techitout.jamfcloud.com" # Encode Credentials encodedCredentials=$( printf "${jamfUser}:${jamfPass}" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - ) # Generate an auth token authToken=$( /usr/bin/curl "${jssURL}/uapi/auth/token" \ --silent \ --request POST \ --header "Authorization: Basic ${encodedCredentials}" ) # Parse authToken for token, omit expiration token=$( /usr/bin/awk -F \" '{ print $4 }' <<< "${authToken}" | /usr/bin/xargs ) # Build an array of possible important notifications from Jamf Pro noticationsArr=( APNS_CERT_REVOKED APNS_CONNECTION_FAILURE APPLE_SCHOOL_MANAGER_T_C_NOT_SIGNED BUILT_IN_CA_EXPIRED BUILT_IN_CA_EXPIRING BUILT_IN_CA_RENEWAL_FAILED BUILT_IN_CA_RENEWAL_SUCCESS CLOUD_LDAP_CERT_EXPIRED CLOUD_LDAP_CERT_WILL_EXPIRE COMPUTER_SECURITY_SSL_DISABLED DEP_INSTANCE_EXPIRED DEP_INSTANCE_WILL_EXPIRE DEVICE_ENROLLMENT_PROGRAM_T_C_NOT_SIGNED EXCEEDED_LICENSE_COUNT FREQUENT_INVENTORY_COLLECTION_POLICY GSX_CERT_EXPIRED GSX_CERT_WILL_EXPIRE HCL_BIND_ERROR HCL_ERROR INSECURE_LDAP INVALID_REFERENCES_EXT_ATTR INVALID_REFERENCES_POLICIES INVALID_REFERENCES_SCRIPTS JAMF_CONNECT_UPDATE JAMF_PROTECT_UPDATE JIM_ERROR LDAP_CONNECTION_CHECK_THROUGH_JIM_FAILED LDAP_CONNECTION_CHECK_THROUGH_JIM_SUCCESSFUL MDM_EXTERNAL_SIGNING_CERTIFICATE_EXPIRED MDM_EXTERNAL_SIGNING_CERTIFICATE_EXPIRING MDM_EXTERNAL_SIGNING_CERTIFICATE_EXPIRING_TODAY MII_HEARTBEAT_FAILED_NOTIFICATION MII_INVENTORY_UPLOAD_FAILED_NOTIFICATION MII_UNATHORIZED_RESPONSE_NOTIFICATION PATCH_EXTENTION_ATTRIBUTE PATCH_UPDATE POLICY_MANAGEMENT_ACCOUNT_PAYLOAD_SECURITY_MULTIPLE POLICY_MANAGEMENT_ACCOUNT_PAYLOAD_SECURITY_SINGLE PUSH_CERT_EXPIRED PUSH_CERT_WILL_EXPIRE PUSH_PROXY_CERT_EXPIRED SSO_CERT_EXPIRED SSO_CERT_WILL_EXPIRE TOMCAT_SSL_CERT_EXPIRED TOMCAT_SSL_CERT_WILL_EXPIRE USER_INITIATED_ENROLLMENT_MANAGEMENT_ACCOUNT_SECURITY_ISSUE USER_MAID_DUPLICATE_ERROR USER_MAID_MISMATCH_ERROR USER_MAID_ROSTER_DUPLICATE_ERROR VPP_ACCOUNT_EXPIRED VPP_ACCOUNT_WILL_EXPIRE VPP_TOKEN_REVOKED DEVICE_COMPLIANCE_CONNECTION_ERROR CONDITIONAL_ACCESS_CONNECTION_ERROR AZURE_AD_MIGRATION_REPORT_GENERATED ) # Returns important notifications from Jamf Pro in JSON data=$( curl --request GET \ --url "${jssURL}/api/v1/notifications" \ --header "Accept: application/json" \ --header "Authorization: Bearer ${token}" \ ) # Loop each notification possibility and if it # exists in the results, grab information on the # notification using jq and notify for str in ${noticationsArr[@]}; do if [[ " ${data} " =~ ${str} ]]; then cleanString=$( echo $str | sed 's/_/ /g' ) name=$( echo ${data} | jq --arg v "${str}" '.[]|select(.type==$v).params.name') cleanname=$( echo $name | sed 's/"//g') days=$( echo ${data} | jq --arg v "${str}" '.[]|select(.type==$v).params.days') id=$( echo ${data} | jq --arg v "${str}" '.[]|select(.type==$v).params.id') if [[ ${name} != "null" ]]; then if [[ ${days} != "null" ]]; then if [[ ${id} != "null" ]]; then message=$( echo "$JPInstance Notification: $cleanString for $cleanname in $days days" ) else message=$( echo "$JPInstance Notification: $cleanString for $cleanname in $days days" ) fi else message=$( echo "$JPInstance Notification: $cleanString for $name" ) fi else message=$( echo "$JPInstance Notification: $cleanString" ) fi echo $message fi done exit 0
This will yield results similar to:
TECHITOUT Notification: DEP INSTANCE WILL EXPIRE for TIO ABM in 17 days TECHITOUT Notification: USER INITIATED ENROLLMENT MANAGEMENT ACCOUNT SECURITY ISSUE
And thats it. I’ve also put it up on my GitHub: https://github.com/robjschroeder/Jamf-API-Scripts/blob/main/api-GetJPNotifications.sh
Thanks for checking it out!