Register and install

Register app definitions with the API, how names are namespaced, installing into an org and tracking progress, disabling and enabling, the read endpoints, and every registration error with its fix.

Registering makes a version of your app available. Installing creates its components in an org. You register once per version; each org installs separately.

POST /v1/apps/available

fails

passes

POST /v1/apps/install

fails

passes

acme_orders.json

Validate every component

400 / 409, nothing created

Registered version

Validate in the org

install_failed, previous version kept

Components created

Admin configures settings and OAuth

Diagram source
flowchart LR
  A[acme_orders.json] -->|POST /v1/apps/available| B{Validate every component}
  B -->|fails| X[400 / 409, nothing created]
  B -->|passes| C[Registered version]
  C -->|POST /v1/apps/install| D{Validate in the org}
  D -->|fails| Y[install_failed, previous version kept]
  D -->|passes| E[Components created]
  E --> F[Admin configures settings and OAuth]

Before you start

  • Use a sandbox org. Registered versions are permanent and installed apps can't be fully uninstalled, so iterate somewhere disposable.
  • Create an API key in Settings › Security › API Keys with the org.admin.apps role. Registration and installs run as that key.
  • Use the starter repo. kustomer/kustomer-apps-starter-configs has example definitions and an npm run register-new-version script. Put API_BASE_URL and API_TOKEN in .env.

Register a version

post/v1/apps/available

The body is the whole app definition.

curl -X POST "https://acme.api.kustomerapp.com/v1/apps/available" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  --data @dist/acme_orders.json

Kustomer validates the definition against its schema, then dry-runs every component against the service that will own it: each workflow, KView, Klass and hook. If anything fails, nothing is registered. The 201 response is an available_app whose id is <app>-<version>, with the final name in attributes.name and the OAuth client ID in attributes.clientId.

Names are namespaced

Kustomer rewrites component names so apps can't collide with each other or with an org's own configuration. You can write the full names yourself or leave them short; registration applies the scheme either way.

ComponentFieldRegistered as
Actionsnamekustomer.app.<app>.<name>
HookseventNamekustomer.app.<app>.<name>
KViewsnamekustomer.app.<app>.<name>
TriggerseventNamekustomer.app.<app>.<name>
Workflow stepsaction, appVersionkustomer.app.<app>.<name>, <app>-<version range>
Workflow triggerseventName, appVersionkustomer.app.<app>.<name>, <app>-<version range>
Commandsname<app>.app.<name>
Settings referencesappSettings keys<app>.<category>.<name>
KlassesnameNot rewritten. Use a prefix such as acme_orders-order yourself.
Standard-object attributesattribute name@<identifier><Name>. See Klasses.

Private apps get your org ID

Private app names must be unique across Kustomer, so registration appends your org ID: acme_orders becomes acme_orders_<orgId>, and every rewritten name above uses the suffixed form, such as kustomer.app.acme_orders_<orgId>.refund.

Keep writing the short name in your definition and read the final one from attributes.name in the registration response. Use the final name in API calls that take an app name: installs, version IDs (acme_orders_<orgId>-1.5.0) and the redirect URI for third-party OAuth.

The identifier

identifier is a short, lowercase code (2 to 8 characters, a-z, 0-9, _) used to prefix your attributes on standard objects. Kustomer generates one if you leave it out. It can't change after your first registration, so pick it deliberately if you add attributes to customers or conversations.

Install in an org

Admins install from Settings › Apps › App Directory. To install with the API, use a key from the target org:

post/v1/apps/install
cURL
curl -X POST "https://acme.api.kustomerapp.com/v1/apps/install" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"app": "acme_orders_<orgId>", "version": "1.5.0"}'
Body
appstringrequired
versionstring

The response is an app_install_state with status: "installing". Installs run in the background, so poll until they finish:

get/v1/apps/install-state/{app}-{version}
Response (excerpt)
{
  "data": {
    "type": "app_install_state",
    "attributes": {
      "app": "acme_orders_<orgId>",
      "version": "1.5.0",
      "previousVersion": "1.4.3",
      "status": "installed",
      "startedAt": "2026-10-21T15:02:11.204Z",
      "endedAt": "2026-10-21T15:02:19.871Z"
    }
  }
}
statusMeaning
installingComponents are being created or updated
installedDone. The version is now the org's current version.
install_failedSomething failed. The org keeps its previous version; fix the cause and install again.

Installing runs the same dry-run as registration, this time in the org. It catches conflicts the org brings, such as a Klass or KView name the org already uses.

After installing, admins fill in settings and connect any OAuth account. Upgrades use the same endpoint with a newer version; see Versioning and updates.

Read apps

EndpointReturns
GET /v1/apps/availableApps your org can install
GET /v1/apps/available/{appVersion}One registered version, as <app>-<version>
GET /v1/appsApps installed in your org
GET /v1/apps/{appVersion}One installed app. Pass <app> for the current version, or <app>-<range> for the highest installed version matching a semver range.

Disable and enable

Disabling an app turns it off in the org without removing it.

post/v1/apps/{app}/disable
post/v1/apps/{app}/enable
DisableEnable
ComponentsTurned offTurned back on
Third-party OAuth connectionsDeleted. Kustomer calls your revokeUrl if you set one.Not restored. Admins connect again.
Tokens Kustomer issued to the app's OAuth clientRevokedNot restored. Users sign in again.
DataKept: KObjects, attribute values and settings stay
UpgradesBlocked until enabled

The response is 204, or 200 with data.warning when revoking a third-party token failed. The disable still succeeded.

Uninstalling

There's no API to fully uninstall an app. Disable it to turn it off; its Klasses and their records stay in the org. That's the main reason to develop in a sandbox org, and to iterate on a definition there before you install it anywhere that matters.

Troubleshooting

ErrorCauseFix
400 using deprecated fields! use appDetails.(appDeveloper | externalPlatform).(name | website) insteadTop-level vendor or websiteMove them into appDetails.appDeveloper or appDetails.externalPlatform
400 naming an unknown propertyA top-level key that isn't in the schemaCheck spelling against App definition; unknown keys are rejected, not ignored
400 on app or identifierInvalid characters or lengthapp: letters, digits, _, up to 128 characters. identifier: lowercase letters, digits, _, 2 to 8 characters.
400 on visibility"public"Register with "private". Public apps go through the marketplace.
409 The app name is already taken. Please choose a different name.Another app uses the namePick another app
409 This version has already been registered. Please increment the version.Re-registering a versionBump version
400 Cannot register outdated version. Version must be greater than X.version is lower than the latestUse a version above X
400 Roles do not match that of the previous version, a major version bump is requiredroles changed in a minor or patch releaseBump the major version
400 with a source inside a componentThat workflow, KView, hook or Klass failed its dry runFix the component. Building workflows in the editor first and exporting them avoids most of these; see Workflows, actions and triggers.
install_failed on installUsually a name clash with the org's own resources, such as an existing KlassCheck attributes.components in the install state to find the component, then rename it in a new version
409 Apps cannot be updated while they are deactivatedInstalling into an org where the app is disabledEnable it, then install
409 currently installingAn install of that version is still runningWait for the install state to finish