# 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.

Source: https://developer.kustomer.com/docs/apps/register-and-install

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

```mermaid
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`](https://github.com/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.

```bash title="cURL"
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
```

```bash title="kusty"
kusty api POST /v1/apps/available --data @dist/acme_orders.json
```

```js title="Node"
const definition = JSON.parse(await readFile('dist/acme_orders.json', 'utf8'))

const res = await fetch(`${process.env.API_BASE_URL}/v1/apps/available`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(definition),
})
if (!res.ok) {
  const { errors } = await res.json()
  for (const e of errors) console.error(e.status, e.title, e.source)
  process.exit(1)
}
```

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`.

> **For agents fixing a failed registration:** Read each error's `title` and `source`: `source` names the field or component that failed. Fix that one thing, bump nothing, and register again. A rejected version was never registered, so you can reuse its number.

## 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.

| Component                  | Field                     | Registered as                                                                                                     |
| -------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Actions                    | `name`                    | `kustomer.app.<app>.<name>`                                                                                       |
| Hooks                      | `eventName`               | `kustomer.app.<app>.<name>`                                                                                       |
| KViews                     | `name`                    | `kustomer.app.<app>.<name>`                                                                                       |
| Triggers                   | `eventName`               | `kustomer.app.<app>.<name>`                                                                                       |
| Workflow steps             | `action`, `appVersion`    | `kustomer.app.<app>.<name>`, `<app>-<version range>`                                                              |
| Workflow triggers          | `eventName`, `appVersion` | `kustomer.app.<app>.<name>`, `<app>-<version range>`                                                              |
| Commands                   | `name`                    | `<app>.app.<name>`                                                                                                |
| Settings references        | `appSettings` keys        | `<app>.<category>.<name>`                                                                                         |
| Klasses                    | `name`                    | **Not rewritten.** Use a prefix such as `acme_orders-order` yourself.                                             |
| Standard-object attributes | attribute name            | `@<identifier><Name>`. See [Klasses](https://developer.kustomer.com/docs/apps/klasses#attributes-are-namespaced). |

### 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](https://developer.kustomer.com/docs/apps/oauth#call-third-party-apis-with-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`

```bash title="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**

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}`

```json title="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"
    }
  }
}
```

| `status`         | Meaning                                                                                |
| ---------------- | -------------------------------------------------------------------------------------- |
| `installing`     | Components are being created or updated                                                |
| `installed`      | Done. The version is now the org's current version.                                    |
| `install_failed` | Something 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](https://developer.kustomer.com/docs/apps/settings) and connect any [OAuth account](https://developer.kustomer.com/docs/apps/oauth#call-third-party-apis-with-oauth). Upgrades use the same endpoint with a newer version; see [Versioning and updates](https://developer.kustomer.com/docs/apps/versioning#upgrade-an-installed-app).

## Read apps

| Endpoint                                                                                                               | Returns                                                                                                                                |
| ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /v1/apps/available`](https://developer.kustomer.com/api/apps-platform/get-available-apps)                        | Apps your org can install                                                                                                              |
| [`GET /v1/apps/available/{appVersion}`](https://developer.kustomer.com/api/apps-platform/get-available-app)            | One registered version, as `<app>-<version>`                                                                                           |
| [`GET /v1/apps`](https://developer.kustomer.com/api/apps-platform/get-installed-apps)                                  | Apps installed in your org                                                                                                             |
| [`GET /v1/apps/{appVersion}`](https://developer.kustomer.com/api/apps-platform/get-installed-apps-by-specific-version) | 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`

|                                                  | Disable                                                  | Enable                              |
| ------------------------------------------------ | -------------------------------------------------------- | ----------------------------------- |
| Components                                       | Turned off                                               | Turned back on                      |
| Third-party OAuth connections                    | Deleted. Kustomer calls your `revokeUrl` if you set one. | Not restored. Admins connect again. |
| Tokens Kustomer issued to the app's OAuth client | Revoked                                                  | Not restored. Users sign in again.  |
| Data                                             | Kept: KObjects, attribute values and settings stay       |                                     |
| Upgrades                                         | Blocked 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

| Error                                                                                                      | Cause                                                                        | Fix                                                                                                                                                                                                                                                |
| ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` using deprecated fields! use appDetails.(appDeveloper \| externalPlatform).(name \| website) instead | Top-level `vendor` or `website`                                              | Move them into `appDetails.appDeveloper` or `appDetails.externalPlatform`                                                                                                                                                                          |
| `400` naming an unknown property                                                                           | A top-level key that isn't in the schema                                     | Check spelling against [App definition](https://developer.kustomer.com/docs/apps/app-definition#identity-and-listing); unknown keys are rejected, not ignored                                                                                      |
| `400` on `app` or `identifier`                                                                             | Invalid characters or length                                                 | `app`: 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](https://developer.kustomer.com/docs/apps/marketplace).                                                                                                                         |
| `409` The app name is already taken. Please choose a different name.                                       | Another app uses the name                                                    | Pick another `app`                                                                                                                                                                                                                                 |
| `409` This version has already been registered. Please increment the version.                              | Re-registering a version                                                     | Bump `version`                                                                                                                                                                                                                                     |
| `400` Cannot register outdated version. Version must be greater than X.                                    | `version` is lower than the latest                                           | Use a version above X                                                                                                                                                                                                                              |
| `400` Roles do not match that of the previous version, a major version bump is required                    | `roles` changed in a minor or patch release                                  | Bump the major version                                                                                                                                                                                                                             |
| `400` with a `source` inside a component                                                                   | That workflow, KView, hook or Klass failed its dry run                       | Fix the component. Building workflows in the editor first and exporting them avoids most of these; see [Workflows, actions and triggers](https://developer.kustomer.com/docs/apps/workflows-actions-triggers#build-workflows-in-the-editor-first). |
| `install_failed` on install                                                                                | Usually a name clash with the org's own resources, such as an existing Klass | Check `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 deactivated                                                    | Installing into an org where the app is disabled                             | Enable it, then install                                                                                                                                                                                                                            |
| `409` currently installing                                                                                 | An install of that version is still running                                  | Wait for the install state to finish                                                                                                                                                                                                               |
