# Publish, review, install (/apps/lifecycle)

This page is the reference behind [Ship to merchants](/apps/ship). Publishing and review happen in the Poltura
admin. Your organization cannot review its own app, and a merchant cannot grant access until a Poltura reviewer
has approved that exact production release.

## Admin workspaces [#admin-workspaces]

| Workspace           | Path                       | Who                                                      |
| ------------------- | -------------------------- | -------------------------------------------------------- |
| Developer apps      | `/developer/apps`          | You (developer mode)                                     |
| App detail / upload | `/developer/apps/{appId}`  | You                                                      |
| Merchant approval   | Poltura ID                 | Store administrator                                      |
| Connected apps      | `/connected-apps`          | Store administrator                                      |

## Publish [#publish]



```bash
poltura app publish --api "$POLTURA_API" --app <app id> --token "$POLTURA_APP_TOKEN"
```

`poltura app publish` builds, then POSTs multipart to `{api}/v1/public-apps/apps/{appId}/releases` with `Authorization: Bearer pltd_…` (or a personal `pltu_…`). Fields: `manifest`, `channel`, `worker:<path>`, `style:<path>`, optional `extension:<path>`. Default channel is `production`. `--api` is the Poltura API origin (also read from `POLTURA_API`, default `https://api.poltura.com`). The app id and the developer token both come from **Developer apps** in the admin: create the app there, then create a token under Developer tokens. A developer token can submit and read releases only; it never creates apps or manages the organization. It stops working when it expires, when it is revoked, or when the person who created it leaves the developer organization or loses the publisher role. A refused token answers `401 invalid_token` or `403 permission_denied` with a link to [the error reference](/api/errors). Publishing does not use your `poltura login` sign-in, which is for store commands.

Without the CLI, the Developer apps upload form accepts the same files. Paths must match the manifest.

### Production and development [#production-and-development]

|                                      | Production         | Development                      |
| ------------------------------------ | ------------------ | -------------------------------- |
| Reviewed                             | Yes                | Yes                              |
| Becomes the release new installs get | Yes, once approved | No                               |
| Installable                          | Once approved      | Never (`release_not_production`) |

## Review [#review]

The reviewer sees the permissions and their purposes, the backend origin, the hosted page if there is one, the redirect URLs, the manifest digests, every worker (path, target, size and digest) and the stylesheet. Every decision carries a reason and applies to the exact manifest the reviewer read, so a release cannot change under a decision. Nobody in your organization can approve your submission. A release is `submitted`, `in_review`, `changes_requested`, `approved` or `rejected`.

## Install [#install]

The merchant installs the approved production release in Poltura ID and sees each permission with your `purpose` text; see [Auth](/apps/auth#installing).

| Status      | Webhook           |
| ----------- | ----------------- |
| `preparing` | snapshot only     |
| `ready`     | `app.installed`   |
| `removing`  | `app.suspended`   |
| `removed`   | `app.uninstalled` |

The public status type also names `needs_setup` and `needs_attention`; installations do not enter them today.

A store has a limit on installed apps, set by its plan. An install past it is refused with `resource_limit_reached`. If a store's limit drops and the merchant does not make room in time, the newest apps are removed, and your app gets the same webhooks as any other removal from that store.

A repeated install request with the same request key returns the installation it already created, even a removed one, and never brings a removed installation back.

## Update [#update]

An update keeps the installation id. After the merchant approves the new production release, the installation's `generation` goes up and its release is replaced; access tokens issued before fail with `403`. Store what `installation.retrieve()` returns after each connection. Webhook: `app.updated`.

## Remove [#remove]

The merchant removes the installation. Its permissions are cleared and `generation` goes up. A new install cannot read the old installation's app storage. Removing the app does not erase data on your servers; delete it when you receive `app.uninstalled`.

## IDs [#ids]

Every id Poltura gives you is a lowercase UUIDv7, such as `0199f3a2-5b1c-7d4e-9f20-3a4b5c6d7e8f`.
An id never says what it names, so keep each one with the field it came in. UUIDv7s sort by the
time they were created.

| Id               | Meaning                                                                      |
| ---------------- | ---------------------------------------------------------------------------- |
| `appId`          | Public app id, from **Developer apps**                                       |
| `releaseId`      | Release id, printed by `poltura app publish`                                 |
| `installationId` | Installation id: the `Poltura-Installation` header, webhooks, session tokens |
| `eventId`        | Public event id; deduplicate deliveries on it                                |
| `requestId`      | Public API request id, in every error body                                   |

## Next [#next]

- [Examples](/apps/examples): apps to start from.
- [CLI](/apps/cli): every publish option.
