Skip to content

API Custom Apps

kandji-trent edited this page Jun 23, 2026 · 1 revision

Custom Apps Resource

Methods

The resource wraps the /api/v1/library/custom-apps endpoint and exposes five methods:

Method Description Returns
list() All custom apps PayloadList[CustomAppPayload]
get(id) A single app by ID CustomAppPayload
create(...) Upload an installer and create an app CustomAppPayload
update(id, ...) Update an app (optionally with a new installer) CustomAppPayload
delete(id) Delete an app None

Payload model

Most methods return a CustomAppPayload:

Attribute Type Description
id str The library item ID (UUID)
name str Display name
sha256 str Installer checksum (use it to verify downloads)
file_key str The installer's storage key
file_url str A URL to download the installer
file_size int Installer size in bytes
file_updated str Timestamp the installer was last updated
file_name str Property: the installer file name with the upload token stripped
install_type str package, zip, or image
install_enforcement str install_once, continuously_enforce, or no_enforcement
unzip_location str | None Destination for zip installs
audit_script str Audit script (with continuously_enforce)
preinstall_script str Script run before installation
postinstall_script str Script run after installation
restart bool Whether to restart after installation
active bool Whether the app is active
show_in_self_service bool Whether the app appears in Self Service
self_service_category_id str | None Self Service category ID, when shown
self_service_recommended bool | None Whether the app is recommended in Self Service
created_at str Creation timestamp
updated_at str Last-updated timestamp

Install type and enforcement

create() and update() take two enums:

InstallType.PACKAGE  # "package"
InstallType.ZIP      # "zip"
InstallType.IMAGE    # "image"

InstallEnforcement.INSTALL_ONCE           # "install_once"
InstallEnforcement.CONTINUOUSLY_ENFORCE   # "continuously_enforce"
InstallEnforcement.NO_ENFORCEMENT         # "no_enforcement"

List apps

Listed apps are returned in a PayloadList with count and results properties.

with CustomAppsResource(config) as apps:
    for app in apps.list().results:
        print(f"{app.id}  {app.name}  ({app.install_type})")

Get an app

with CustomAppsResource(config) as apps:
    app = apps.get("d528d739-39e4-44b4-89fd-54bef6b3b25e")
    print(app.file_name, app.file_size)

Create an app

Creating an app uploads the installer to storage and then registers the library item. The file argument must be a pathlib.Path to the installer. The lifecycle scripts (audit_script, preinstall_script, postinstall_script) are required arguments — pass an empty string when you don't need one.

The create method returns the created app, including the id.

with CustomAppsResource(config) as apps:
    created = apps.create(
        name="Example.pkg",
        file=Path("payloads/Example.pkg"),
        install_type=InstallType.PACKAGE,
        install_enforcement=InstallEnforcement.INSTALL_ONCE,
        audit_script="",
        preinstall_script="",
        postinstall_script="",
        restart=False,
        active=True,
    )
    print(f"Created {created.name} ({created.id})")

Note

Large installers can take a while to process server-side and may briefly return HTTP 503 while Iru finalizes the upload. The client retries automatically with backoff (up to about five minutes) before giving up.

Argument rules

create() and update() raise ValueError when options conflict:

Rule Requirement
audit_script is provided install_enforcement must be CONTINUOUSLY_ENFORCE.
install_type is ZIP unzip_location must be provided.
install_enforcement is NO_ENFORCEMENT show_in_self_service must be set.
show_in_self_service is True self_service_category_id must be provided.

A missing installer file raises FileNotFoundError. To make an app available in Self Service, supply a self_service_category_id — see Self Service Categories.

Reporting upload progress (optional)

create() and update() accept a reporter argument for surfacing upload progress. It is optional — omit it and the installer still streams to storage directly from disk. The reporter type is an internal detail; most integrations don't need it.

Update an app

Pass only the fields you want to change. Include file to replace the installer binary.

with CustomAppsResource(config) as apps:
    # Change metadata only.
    updated = apps.update("d528d739-39e4-44b4-89fd-54bef6b3b25e", active=False)
    print(f"Updated {updated.name} ({updated.id})")
    print(f"active={updated.active}")

    # Replace the installer.
    apps.update(
        "d528d739-39e4-44b4-89fd-54bef6b3b25e",
        file=Path("payloads/Example-2.0.pkg"),
    )

Delete an app

Deleting an app only requires the id and does not return anything.

with CustomAppsResource(config) as apps:
    apps.delete("d528d739-39e4-44b4-89fd-54bef6b3b25e")

Download an installer

App payloads carry a file_url and a sha256 checksum. Use S3Client to stream the installer to disk; download_file verifies the checksum and only commits the file if it matches.

with CustomAppsResource(config) as apps:
    app = apps.get("d528d739-39e4-44b4-89fd-54bef6b3b25e")

with S3Client() as transport:
    try:
        transport.download_file(
            app.file_url,
            Path(app.file_name),
            expected_sha=app.sha256,
        )
    except PayloadIntegrityError:
        print("Downloaded file failed its checksum.")
    except PayloadTransferError as error:
        print(f"Download failed: {error}")

Tip

S3Client is an unauthenticated transport — the file_url carries its own credentials — so it does not take an ApiConfig. Use it inside its own with block.

See also

Clone this wiki locally