kst
(pronounced kast) is a utility for managing resources via the Kandji API.
- Features
- Installation
- Quick Start
- Getting Help
- Creating Your Local Repository
- Authenticate with Your Kandji Tenant
- Populating Your Local Repository
- Edit a Local Resource
- Making a Custom Script Available in Self Service
- Pushing Changes to Kandji
- Syncing Changes with Kandji
- List Resource Sync Statuses
- Show Resource Details
- Local Resource Directory Structure
- Manually Creating Resources
- Create and sync a local repository of custom profiles or scripts with your Kandji tenant
- Import existing profiles and scripts without copy/pasting
- List or show details of local and remote resources directly from the command line
- Format output as structured YAML, plist, or JSON for use with other tools
- Build your own integration for managing custom profiles or scripts with a fully featured Python client module
Tip
brew
the missing (yet ubiquitous) macOS package manager for CLI tools and
native macOS applications.
More information at brew.sh.
brew tap kandji-inc/kst https://github.com/kandji-inc/kst.git
brew install kst
Tip
uv
is a single binary solution for managing Python environments with a wide
range of features.
More information at docs.astral.sh/uv.
uv tool install kst
Tip
pipx
is a 100% Python solution to install and run Python tools in isolated
environments.
More information at pipx.pypa.io.
pipx install kst
Follow the steps below to quickly get up and running with a local copy of your Kandji Custom Resources.
- Create a new local repository:
kst new my-repo
- Change directories:
cd my-repo
- Set your credentials:
export KST_TENANT=<YOUR_API_URL_HERE>
export KST_TOKEN=<YOUR_API_TOKEN_HERE>
- Download your custom profiles from Kandji:
kst profile pull --all
- Download your custom scripts from Kandji
kst script pull --all
Note
Replace <YOUR_API_URL_HERE>
and <YOUR_API_TOKEN_HERE>
with your API URL and token. See the authentication section
below for more details.
Each command and subcommand has its own help screen, which can be accessed by appending --help
. For example,
kst --help
will display usage information, available options, a list of root-level commands, and a short description
of each command (see below). Help screens for each subcommand can be accessed in the same way (e.g., kst new --help
).
Usage: kst [OPTIONS] COMMAND [ARGS]...
Kandji Sync Toolkit, a utility for local management of Kandji resources.
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --version Show the version. │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or customize the installation. │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Logging ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --log-path PATH Path to the log file. │
│ --debug Enable debug logging. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ profile Interact with Kandji Custom Profiles │
│ script Interact with Kandji Custom Scripts │
│ new Create a new kst repository │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Before doing anything else, you'll need to create a local repository. A kst
repository can be created in any directory
using kst new
. Under the hood, a kst
repository just a git
repository with a .kst
file at its root, but using
the command will include a handy README.md
to get you started.
❯ kst new kst-repo
Created a new kst repository at kst-repo
Check out the included README.md file for more information on getting started.
With no configuration, you will be prompted for a Kandji tenant API URL and a token when running a command that requires it.
$ kst profile sync --all
Enter Kandji Tenant URL: https://mysubdomain.api.kandji.io
Enter API Token: ************
However, it's generally a better idea to store the credentials in environment variables. This removes the need to enter
credentials for every command. The following environment variables are automatically checked by kst
before prompting
the user.
KST_TENANT
: Thehttps
API URL of your Kandji tenant (e.g., https://mysubdomain.api.kandji.io).KST_TOKEN
: The API token with permissions to your Kandji tenant (see documentation: Generate an API Token).
Tip
These values can be added to your shell's startup file to be exported automatically.
Example ~/.profile
, ~/.zshrc
, or ~/.bashrc
export KST_TENANT="https://mysubdomain.api.kandji.io"
export KST_TOKEN="not-a-real-token"
If you already have resources in Kandji, you can have kst
pull all or some of them down to your local repository with
a single command. Use the --all
option to pull all of a resource type into the local repo or select specific resources
with the --id
option. --id
can be specified more than one to include multiple resources.
kst profile pull [OPTIONS]
kst script pull [OPTIONS]
This will create a profiles
or scripts
directory in your repository, if one doesn't exist, and download each Kandji
resource and its metadata into a subdirectory.
The pull
command can also be used to update existing resources with changes from your Kandji tenant or to delete
resources that no longer exist. By default, nothing is deleted and only resources without local updates are pulled. This
behavior can be modified by passing --force
(to overwrite local changes) or --clean
(to delete local resources which
are not present in Kandji).
Note
The --clean
option can only be used with --all
. If you would like to delete only specific resources use the
delete
command described below.
If you would like to see what would happen (without making any changes), you can use the --dry-run
flag. This will
print a full list of actions which would have occurred.
The simplest means of creating a new local script or profile is using the new
command.
kst profile new [OPTIONS]
kst script new [OPTIONS]
Without any options, this will create a new resource in the respective directory and populate it with default metadata
and content. The defaults match Kandji's defaults for creating a new script or profile in the user interface (where
possible). It's generally recommended to at least specify a name (via the --name
option) since the resource directory
is named accordingly. Other options can be included to modify the default settings. For a full list of options, see the
--help
message for each new command.
Tip
You can rename or move a resource's directory anywhere within the main profiles/scripts subdirectories you see fit
without affecting the resource. The resources's name is determined by the name
key in the info file.
kst profile new --name "My Profile" --runs-on mac --active
kst script new --name "My Script" --execution-frequency every_15_min --include-remediation
If you already have a mobileconfig or a script you want to deploy in Kandji, you can import the content
into a new resource instead of creating one from scratch. For profiles, use the --import
option to specify the path to
the mobileconfig file. For scripts, use the --import-audit
or --import-remediation
options. These options can be
used with other new
command options to specify additional metadata.
kst profile new --import /path/to/MyProfile.mobileconfig
kst script new --name "My Script" --import-audit /path/to/audit.sh --import-remediation /path/to/remediation.sh
Editing a script or profile can be done in the text editor of your choice. Simply open the file, make your desired
modifications, and save the file. Any changes will be recognized the next time kst
is run. This includes moving a
resource directory, renaming, etc.
Important
Do not edit the id
and sync_hash
keys.
If an id
changes then the resource will be disconnected from its Kandji tenant counterpart and identified as a new
resource. The sync_hash
is used to automatically identify remote vs local changes to a resource. If it is missing
or invalid, the resource will show "Conflicting Changes" and a forced push/pull will be required.
You can use the kst script new --self-service
option to make a custom script Library Item available via Kandji
Self Service. By default, if the --category
option is not also provided, kst
will attempt to add the script to
the Utilities
category. The Utilities
category is present by default on all Kandji tenants. If the Utilities
category was deleted or renamed, an error will be raised and you will need to provide a category.
Tip
To see or add additional Self Service categories, head to your Kandji tenant and go to Settings > Self Service
.
More info - Self Service Settings KB
kst script new --name ss_script_no_category --self-service
The above command tells kst
to create a new custom script Library Item called ss_script_no_category and make that
custom script available in Self Service under the Utilities
category.
If we take a look at the associated info file, we see that show_in_self_service
is set to true
and the
self_service_category
is set to Utilities
.
<dict>
<key>active</key>
<false/>
<key>execution_frequency</key>
<string>once</string>
<key>id</key>
<string>2b95fd99-06ec-4492-a934-e44ab8e113e9</string>
<key>name</key>
<string>ss_script_option</string>
<key>restart</key>
<false/>
<key>self_service_category_id</key>
<string>Utilities</string>
<key>self_service_recommended</key>
<false/>
<key>show_in_self_service</key>
<true/>
</dict>
kst script new --name ss_script_option --self-service --category Apps
The above command tells KST to create a new custom script Library Item called ss_script_option and make that custom
script available in Self Service under the Apps
category.
Looking at the associated info file, we see that show_in_self_service
is set to true
and the
self_service_category
is set to Apps
.
<dict>
<key>active</key>
<false/>
<key>execution_frequency</key>
<string>once</string>
<key>id</key>
<string>2b95fd99-06ec-4492-a934-e44ab8e113e9</string>
<key>name</key>
<string>ss_script_option</string>
<key>restart</key>
<false/>
<key>self_service_category_id</key>
<string>Apps</string>
<key>self_service_recommended</key>
<false/>
<key>show_in_self_service</key>
<true/>
</dict>
Check out kst script new --help
for additional usage details.
Use the push
command to upload new or updated resources to your Kandji tenant.
kst profile push [OPTIONS]
kst script push [OPTIONS]
You can choose which resources to push using the --all
, --id
, and --path
options. --id
and --path
can be
specified more than once to include more than one resource.
By default, only new local resources and resources with only local updates will be pushed. Nothing will be deleted or
overwritten. In order to overwrite changes in the Kandji tenant, use the --force
option. To delete resources in Kandji
which are not present in the local repository, use the --clean
option.
Note
The --clean
option can only be used with --all
. If you would like to delete only specific resources use the
delete
command described below.
kst profile push --id "de6cf090-cf14-4517-bc8e-110f2e4ed56a"
kst script push --all --clean
The sync
command can be used to push and pull changes simultaneously.
kst profile sync [OPTIONS]
kst script sync [OPTIONS]
In order to make updates more seamless, the sync
command will create missing resources in both the local repository
and the Kandji tenant as well as updating either with changes. In cases where update direction cannot be definitively
determined, a conflicting change is shown and the resource is skipped.
The sync
command includes similar options to the push
and pull
commands with the addition of --force-mode
to
to resolve conflicts. the --force-mode
option can be set to push
or pull
in order to automatically overwrite
conflicting changes when they arise.
kst profile sync --id "18f75e5f-5a79-45ef-a354-314c517b9280"
kst profile sync --all --force-mode push
The list
commands makes it simple to print a quick view of all your profiles or scripts as well as their sync status.
kst profile list [OPTIONS]
kst script list [OPTIONS]
If you would like to limit the list to remote or local resources only you can pass the --local
or --remote
flags
respectively. Similarly, if you want to to only show resources with a specific status you can use the --include
or
--exclude
flags. Include and exclude can be used multiple times to specify more than one status. Available statuses
include: no_changes, new_remote, updated_remote, new_local, updated_local, conflict
The list output format can also be modified. By default, a simple human readable table is shown. However, if you
prefer more structured output, use the --format
option to choose plist
, json
, or yaml
. This can be especially
useful when piping kst
output to other command line tools.
Use the show
command to display the full metadata and content of a custom resource.
kst profile show [OPTIONS] PROFILE
kst script show [OPTIONS] SCRIPT
The first argument should be the ID or path of a profile or script.
By default, the local resource is shown in a human readable table format. This can be modified by passing the --remote
flag to show the Kandji tenant's version. The --format
option can be passed with plist
, json
, or yaml
to display
the content and metadata in a specific structured format.
Additionally, for profiles you can pass the --profile
flag to show only the profile mobileconfig content. For scripts,
you can pass --audit
or --remediation
to show their respective content.
$ kst profile show a5f20d99-315e-40b5-a3d5-7cd96f5f2ae4
Custom Profile Details (Local)
┌────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ID │ a5f20d99-315e-40b5-a3d5-7cd96f5f2ae4 │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Name │ New Profile │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ MDM Identifier │ com.kandji.profile.custom.a5f20d99-315e-40b5-a3d5-7cd96f5f2ae4 │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Active │ False │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Runs On │ Mac, iPhone, iPad, TV, and Vision │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created At │ 2025-04-22T14:48:36.481752Z │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Updated At │ 2025-04-22T14:48:36.481768Z │
├────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Profile │ <?xml version="1.0" encoding="UTF-8"?> │
│ │ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.d… │
│ │ <plist version="1.0"> │
│ │ <dict> │
│ │ <key>PayloadContent</key> │
│ │ <array> │
│ │ <dict/> │
│ │ </array> │
│ │ <key>PayloadDisplayName</key> │
│ │ <string>New Profile</string> │
│ │ <key>PayloadIdentifier</key> │
│ │ <string>com.kandji.profile.custom.a5f20d99-315e-40b5-a3d5-7cd96f5f2ae4</string> │
│ │ <key>PayloadScope</key> │
│ │ <string>System</string> │
│ │ <key>PayloadType</key> │
│ │ <string>Configuration</string> │
│ │ <key>PayloadUUID</key> │
│ │ <string>e6291f0f-f825-51b8-9c95-faad71e73540</string> │
│ │ <key>PayloadVersion</key> │
│ │ <integer>1</integer> │
│ │ </dict> │
│ │ </plist> │
│ │ │
└────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────┘
$ kst script show cb3260ce-006a-4985-b06e-d7ea52c09151
Custom Script Details (Local)
┌──────────────────────────┬────────────────────────────────────────────────────────┐
│ ID │ cb3260ce
A3E2
-006a-4985-b06e-d7ea52c09151 │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Name │ New Script │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Active │ False │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Execution Frequency │ Once │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Restart │ False │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Show in Self Service │ False │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Self Service Category ID │ │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Self Service Recommended │ │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Created At │ 2025-04-23T15:00:47.370018Z │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Updated At │ 2025-04-23T15:00:47.370018Z │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Audit Script │ #!/bin/zsh -f │
│ │ # https://support.kandji.io/kb/custom-scripts-overview │
│ │ │
│ │ echo "Hello, World!" │
│ │ exit 0 │
│ │ │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Remediation Script │ │
└──────────────────────────┴────────────────────────────────────────────────────────┘
Since Kandji resources must also contain metadata (e.g. active
or name
), each resources's on disk representation is
actually a directory of associated files. Certain files are required for a directory to be recognized as a resource.
Tip
Additional files, such as a README.md
, can be added without causing issues so long as they cannot be confused with
the required files.
Each profile requires:
- A directory within a kst repository
profiles
directory containing the profile's files - Exactly one
info.[plist|yaml|json]
file containing the profile's associated metadata - Exactly one
.mobileconfig
file containing the profile's content
$ lsd --tree profiles/MyFancyProfile
MyFancyProfile
├── info.yaml
└── profile.mobileconfig
info.yaml
id: 54bef6b3-b25e-44b4-89fd-d528d73939e4
name: MyFancyProfile
active: false
mdm_identifier: com.kandji.profile.custom.54bef6b3-b25e-44b4-89fd-d528d73939e4
runs_on_mac: true
runs_on_iphone: true
runs_on_ipad: true
runs_on_tv: true
runs_on_vision: true
profile.mobileconfig
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict/>
</array>
<key>PayloadDisplayName</key>
<string>MyFancyProfile</string>
<key>PayloadIdentifier</key>
<string>com.kandji.profile.custom.54bef6b3-b25e-44b4-89fd-d528d73939e4</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>54bef6b3-b25e-44b4-89fd-d528d73939e4</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
Each script requires:
- A directory within a kst repository
scripts
directory containing the script's files - Exactly one
info.[plist|yaml|json]
file containing the scripts's associated metadata - Exactly one audit script file (filename must start with
audit
) - Optionally one remediation script file (filename must start with
remediation
)
$ lsd --tree scripts/MyFancyScript
MyFancyScript
├── audit.zsh
├── info.plist
└── remediation.zsh
info.json
{
"id": "b27aa82f-4a6d-4a3b-8097-68ca39eb705b",
"name": "MyFancyScript",
"active": false,
"execution_frequency": "once",
"restart": false
}
audit.zsh
#!/bin/zsh -f
# https://support.kandji.io/kb/custom-scripts-overview
echo "Hello, World!"
exit 0
remediation.zsh
#!/bin/zsh -f
# https://support.kandji.io/kb/custom-scripts-overview
echo "Hello, World!"
exit 0
If you are unable to use the new
command for any reason, resources can also be created manually
- Create a new directory (named however you like) to hold your resource in the correct repository subdirectory
(
profiles
orscripts
) - Create an info file in the directory named
info
with one of the following extensions (.yaml
,.json
,.plist
) - Populate the info file with at least an
id
andname
key/value pair - Optionally include additional metadata in the info file
- Optionally include content in the directory
- For profiles, a single profile with a
.mobileconfig
extension - For scripts, an audit script (and optional remediation script) with filenames starting with
audit
andremediation
, respectively
- For profiles, a single profile with a