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

# Version History

> Every change to a Canvas app is versioned with a content hash, commit message, and diffable snapshot — like git for deployed applications.

## Overview

Canvas apps have built-in version control. Every time you **publish** config changes or **deploy** a new code bundle, an immutable version record is created with:

* A **content hash** (SHA-256) — like a git commit SHA
* A **commit message** (required) — describes what changed
* A **full config snapshot** — serialized as diffable JSON files
* **Who** published and **when**

You can list all versions, diff any two versions, download previous bundles, and restore (rollback) to any version instantly.

***

## How versioning works

There are two ways a new version is created:

| Action                                     | What creates the version                            | What's captured                                                 |
| ------------------------------------------ | --------------------------------------------------- | --------------------------------------------------------------- |
| **Publish** (`POST /v1/apps/{id}/publish`) | Config snapshot (sections, meta, auth, theme, etc.) | Content hash of config, message, config as JSON files           |
| **Deploy** (`POST /v1/apps/{id}/deploy`)   | Code bundle upload (React/JS build)                 | Asset manifest with file hashes, message, optional source files |

Both paths create a `VersionRecord` in the app's `versions` array. The version number auto-increments.

### Content hashing

When you publish, Mixpeek generates a deterministic SHA-256 hash of your entire config snapshot. This hash changes only when the config actually changes — identical publishes produce the same hash.

```
v1  →  hash: d733b11021fa  →  "Initial release"
v2  →  hash: e186b986ff96  →  "feat: add custom HTML search interface"
v3  →  hash: d733b11021fa  →  "revert: back to original config"  (same hash as v1!)
```

### Config as diffable files

Your app config is serialized into individual JSON files for each top-level key. This makes version diffs meaningful:

```
meta.json          →  {"title": "Product Search", "logo_url": "..."}
theme.json         →  {"colors": {"primary": "#FC5185"}}
sections.json      →  [{"type": "search", ...}]
auth_config.json   →  {"mode": "clerk", "clerk_allowed_providers": [...]}
custom_html.json   →  "<div id=\"app\">...</div>"
```

***

## Listing versions

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.mixpeek.com/v1/apps/$APP_ID/versions \
    -H "Authorization: Bearer $API_KEY"
  ```

  ```python Python theme={null}
  from mixpeek import Mixpeek

  client = Mixpeek(api_key="your-api-key")
  versions = client.apps.versions.list(app_id="app_abc123")

  for v in versions.versions:
      print(f"v{v.version}: {v.s3_version_id} — {v.message}")
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "versions": [
    {
      "version": 2,
      "s3_version_id": "e186b986ff96",
      "message": "feat: add custom HTML search interface",
      "deployed_by": "int_40ed22c147907235",
      "deployed_at": "2026-03-27T13:10:00Z",
      "environment": "production"
    },
    {
      "version": 1,
      "s3_version_id": "d733b11021fa",
      "message": "Initial release",
      "deployed_by": "int_40ed22c147907235",
      "deployed_at": "2026-03-27T13:09:00Z",
      "environment": "production"
    }
  ],
  "total": 2
}
```

***

## Viewing version details

Get full metadata for a specific version, including the source files snapshot and which environments it's active in:

```bash theme={null}
curl https://api.mixpeek.com/v1/apps/$APP_ID/versions/1 \
  -H "Authorization: Bearer $API_KEY"
```

Response:

```json theme={null}
{
  "version": 1,
  "s3_version_id": "d733b11021fa",
  "message": "Initial release",
  "deployed_by": "int_40ed22c147907235",
  "deployed_at": "2026-03-27T13:09:00Z",
  "environment": "production",
  "source_files": {
    "meta.json": "{\"title\": \"Product Search\"}",
    "theme.json": "{\"colors\": {\"primary\": \"#FC5185\"}}",
    "sections.json": "[]"
  },
  "is_active": {
    "staging": false,
    "production": true
  }
}
```

***

## Diffing versions

Compare any two versions to see what changed — like `git diff v1..v2`:

```bash theme={null}
curl https://api.mixpeek.com/v1/apps/$APP_ID/versions/1/diff/2 \
  -H "Authorization: Bearer $API_KEY"
```

Response:

```json theme={null}
{
  "app_id": "app_abc123",
  "from_version": 1,
  "to_version": 2,
  "summary": {
    "added": 1,
    "removed": 0,
    "modified": 0,
    "unchanged": 7
  },
  "source_diff": {
    "custom_html.json": "--- v1/custom_html.json\n+++ v2/custom_html.json\n@@ -0,0 +1 @@\n+\"<div><h1>Custom Search</h1></div>\""
  }
}
```

The `summary` counts file-level changes (based on content hashes). The `source_diff` provides unified diffs of the actual content — the same format as `git diff`.

***

## Rollback

### Quick rollback (one level)

Restore the previous published config instantly:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.mixpeek.com/v1/apps/$APP_ID/rollback \
    -H "Authorization: Bearer $API_KEY"
  ```

  ```python Python theme={null}
  app = client.apps.rollback(app_id="app_abc123")
  print(f"Rolled back to v{app.version}")
  ```
</CodeGroup>

### Restore any version

For deploy-based versions (code bundles), restore any specific version to any environment:

```bash theme={null}
curl -X POST https://api.mixpeek.com/v1/apps/$APP_ID/versions/3/restore \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environment": "production"}'
```

This is instant — no rebuild required. The previous assets are always retained in S3.

***

## Download a version's bundle

Download the original zip bundle for any deployed version and work locally:

<Steps>
  <Step title="Download the bundle">
    ```bash theme={null}
    curl https://api.mixpeek.com/v1/apps/$APP_ID/versions/2/download \
      -H "Authorization: Bearer $API_KEY"
    ```

    Response:

    ```json theme={null}
    {
      "download_url": "https://s3.amazonaws.com/...",
      "version": 2,
      "asset_prefix": "apps/my-app/dep_abc123",
      "expires_in": 3600
    }
    ```
  </Step>

  <Step title="Download and extract">
    ```bash theme={null}
    curl -o bundle.zip "$DOWNLOAD_URL"
    unzip bundle.zip -d my-app/
    ```
  </Step>

  <Step title="Edit files locally">
    Make your changes to the extracted source.
  </Step>

  <Step title="Re-zip and deploy">
    ```bash theme={null}
    cd my-app && zip -r ../updated.zip .
    # Upload and deploy as usual (see Deploy from Code)
    ```
  </Step>
</Steps>

***

## Git metadata

When deploying via CI/CD or the CLI, you can attach git metadata to each version for full traceability:

```bash theme={null}
curl -X POST https://api.mixpeek.com/v1/apps/$APP_ID/deploy \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "cli_upload",
    "bundle_s3_key": "$BUNDLE_KEY",
    "message": "fix: search results pagination",
    "git_commit_sha": "a1b2c3d4e5f6",
    "git_commit_message": "fix: search results pagination",
    "git_author": "Jane Smith <jane@example.com>"
  }'
```

Git metadata appears in version detail responses and Studio's version history panel.

<Tip>
  If you connect a GitHub repository via `POST /v1/apps/{id}/connect-repo`, deploys are triggered automatically on push. Git metadata is captured from the webhook payload — no manual fields needed.
</Tip>

***

## Version record fields

| Field                | Type     | Description                                      |
| -------------------- | -------- | ------------------------------------------------ |
| `version`            | `int`    | Auto-incrementing version number                 |
| `s3_version_id`      | `string` | Content hash (publish) or S3 version ID (deploy) |
| `asset_prefix`       | `string` | S3 path prefix for deployed assets               |
| `asset_manifest`     | `object` | Map of `{relative_path: {s3_key, hash, size}}`   |
| `source_files`       | `object` | Map of `{path: content}` for source-level diffs  |
| `deployed_by`        | `string` | Internal ID of the user who published/deployed   |
| `deployed_at`        | `string` | ISO 8601 timestamp                               |
| `environment`        | `string` | `"staging"` or `"production"`                    |
| `message`            | `string` | Commit message (required)                        |
| `build_duration_ms`  | `int`    | Build time in milliseconds (deploy only)         |
| `git_commit_sha`     | `string` | Git SHA (if provided)                            |
| `git_commit_message` | `string` | Git commit message (if provided)                 |
| `git_author`         | `string` | Git author (if provided)                         |

***

## Related

* [Apps overview](/canvas/apps)
* [Deploy from Code](/canvas/apps/deploy)
* [List Versions (API)](/api-reference/apps/list-versions)
* [Publish App (API)](/api-reference/apps/publish-app)
* [Rollback App (API)](/api-reference/apps/rollback-app)
