> ## Documentation Index
> Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
> Use this file to discover all available pages before exploring further.

# App Versions

> Manage app versions — create, publish, deprecate, and view install counts

# App Versions

LaunchMyStore apps are versioned independently of the underlying
`App` record. Each version captures a snapshot of the app's
`extensions`, `functions`, and per-function `wasmPaths`, plus a free-form
`releaseNotes` body. Versions move through a three-state lifecycle:

```
draft ─► published ─► deprecated
```

* **`draft`** — created but not visible to merchants. Cannot be installed.
* **`published`** — the currently active version. Auto-installs to
  merchants with `autoUpdate = true` and no `pinnedVersion`.
* **`deprecated`** — superseded by a newer published version, or
  explicitly deprecated. Still installable via rollback, but new
  installs go to the latest published version.

There is at most **one** `published` version per app at any time —
publishing a new version automatically demotes the previously
published one to `deprecated`.

All endpoints in this group require merchant JWT auth and verify that
the caller's `storeId` matches `App.developerId`. Calls from
non-owners return `404 App not found`.

## Endpoints

| Method | Path                                                 | Purpose                        |
| ------ | ---------------------------------------------------- | ------------------------------ |
| `POST` | `/apps/developer/:appId/versions`                    | Create a new draft version.    |
| `GET`  | `/apps/developer/:appId/versions`                    | List all versions for the app. |
| `GET`  | `/apps/developer/:appId/versions/stats`              | Per-version install counts.    |
| `POST` | `/apps/developer/:appId/versions/:version/publish`   | Promote draft → published.     |
| `POST` | `/apps/developer/:appId/versions/:version/deprecate` | Promote → deprecated.          |

For per-installation rollback and resume-auto-update, see
[Rollback](/api-reference/apps/rollback).

## Create Version

### `POST /apps/developer/:appId/versions`

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/developer/lms_app_xxx/versions" \
  -H "Authorization: Bearer DEVELOPER_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "1.2.0",
    "releaseNotes": "Added shipping zone customization. Fixed cart-transform null guard.",
    "extensions": { "blocks": [...], "snippets": [...] },
    "functions": { "cart-transform-bundle": { "type": "cart_transform" } },
    "wasmPaths": { "cart-transform-bundle": "r2://apps/lms_app_xxx/1.2.0/cart-transform.wasm" }
  }'
```

<ParamField body="version" type="string" required>
  Semver-valid version string (e.g. `1.0.0`, `2.3.1-beta.4`). Validated
  with `semver.valid()`. Must be **strictly greater** than the current
  `published` version. Invalid versions return
  `400 Invalid version format. Use semver (e.g. 1.0.0)`. Versions less
  than or equal to the latest published return
  `400 Version must be greater than X.Y.Z`.
</ParamField>

<ParamField body="releaseNotes" type="string">
  Free-form changelog body. Displayed to merchants when they review the
  update and to staff in the review queue.
</ParamField>

<ParamField body="extensions" type="object">
  Snapshot of the app's `extensions` config for this version. Defaults
  to `App.extensions` (the live config) when omitted.
</ParamField>

<ParamField body="functions" type="object">
  Snapshot of function definitions for this version.
</ParamField>

<ParamField body="wasmPaths" type="object">
  Map of `functionHandle → wasmPath` for this version's compiled
  artifacts. The artifacts themselves must already be uploaded via
  `POST /apps/developer/:appId/functions`.
</ParamField>

Response (201):

```json theme={null}
{
  "status": 201,
  "state": "success",
  "data": {
    "versionId": "9f8e7d6c-...",
    "appId": "lms_app_xxx",
    "version": "1.2.0",
    "releaseNotes": "Added shipping zone customization...",
    "extensions": { "...": "..." },
    "functions": { "...": "..." },
    "wasmPaths": { "...": "..." },
    "status": "draft",
    "publishedAt": null,
    "deprecatedAt": null,
    "createdBy": "<developerId>",
    "createdAt": "2026-05-16T12:00:00.000Z",
    "updatedAt": "2026-05-16T12:00:00.000Z"
  }
}
```

A `created` entry is also appended to the version changelog.

## List Versions

### `GET /apps/developer/:appId/versions`

Returns every version (any status), newest-first by `createdAt`.

```json theme={null}
{
  "status": 200,
  "state": "success",
  "data": [
    { "version": "1.2.0", "status": "published", "publishedAt": "2026-05-16T12:34:00.000Z" },
    { "version": "1.1.0", "status": "deprecated", "publishedAt": "2026-04-01T...", "deprecatedAt": "2026-05-16T12:34:00.000Z" },
    { "version": "1.0.0", "status": "deprecated", "publishedAt": "2026-03-01T...", "deprecatedAt": "2026-04-01T..." }
  ]
}
```

## Version Stats

### `GET /apps/developer/:appId/versions/stats`

Per-version install counts (counts installations by their
installed version).

```json theme={null}
{
  "status": 200,
  "state": "success",
  "data": {
    "totalInstallations": 1284,
    "versions": [
      { "version": "1.2.0", "status": "published",  "publishedAt": "2026-05-16T...", "deprecatedAt": null, "installCount": 1102 },
      { "version": "1.1.0", "status": "deprecated", "publishedAt": "2026-04-01T...", "deprecatedAt": "2026-05-16T...", "installCount": 171 },
      { "version": "1.0.0", "status": "deprecated", "publishedAt": "2026-03-01T...", "deprecatedAt": "2026-04-01T...", "installCount": 11 }
    ]
  }
}
```

`installCount` includes installs that are pinned to that version
(merchant-initiated rollback) plus stragglers with `autoUpdate = false`.

## Publish Version

### `POST /apps/developer/:appId/versions/:version/publish`

Promotes a draft to `published`. Has three side effects:

1. The previously-published version (if any) is set to `deprecated`
   with `deprecatedAt = now()`.
2. `App.version` is synced to the new version.
3. All installations with `autoUpdate = true` AND `pinnedVersion IS NULL`
   have `installedVersion` updated to the new version.

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/developer/lms_app_xxx/versions/1.2.0/publish" \
  -H "Authorization: Bearer DEVELOPER_JWT"
```

Response: the full `AppVersion` row with `status: "published"` and
`publishedAt` populated.

Error codes:

| HTTP  | Message                        | When                                                        |
| ----- | ------------------------------ | ----------------------------------------------------------- |
| `400` | `Version is already published` | Calling publish on a version already in `published` status. |
| `404` | `App not found`                | Caller doesn't own this app, or `appId` doesn't exist.      |
| `404` | `Version X.Y.Z not found`      | No `AppVersion` row matches `(appId, version)`.             |

## Deprecate Version

### `POST /apps/developer/:appId/versions/:version/deprecate`

Manually deprecate a version — useful for emergency pulls when a bug is
discovered in a previously-published version. **Does not** auto-promote
another version; installations already running the deprecated version
keep running until they are rolled back or the next published version
hits them via `autoUpdate`.

```bash theme={null}
curl -X POST "https://api.launchmystore.io/apps/developer/lms_app_xxx/versions/1.1.0/deprecate" \
  -H "Authorization: Bearer DEVELOPER_JWT"
```

Sets `status = 'deprecated'`, `deprecatedAt = now()`, appends a
`deprecated` changelog entry.

## Changelog

Every state transition is recorded in the version changelog:

| `action` value | When fired                                                          |
| -------------- | ------------------------------------------------------------------- |
| `created`      | `POST .../versions`                                                 |
| `published`    | `POST .../versions/:version/publish`                                |
| `deprecated`   | `POST .../versions/:version/deprecate`, or auto-demotion on publish |
| `rolled_back`  | `POST /apps/store/installations/:id/rollback` (per-install)         |

The changelog endpoint is internal — there is no public list endpoint
yet. Use the per-version stats and the version list to drive a UI.

<Note>
  The publish flow's auto-update fan-out skips any installation with a
  non-null `pinnedVersion` — pinning is the merchant's signal that they
  do not want automatic updates for this app.
</Note>
