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

# Apps

> Deploy your own frontend code — React, vanilla JS, or any static site — connected to your Mixpeek retrievers. Served on your custom domain with auth built in.

<Note>
  Canvas Apps require an **Enterprise** plan. [Contact us](https://mixpeek.com/contact) to upgrade.
</Note>

## Overview

Canvas is Mixpeek's **application hosting platform**. Build a web app using any frontend framework — React, Vue, Svelte, vanilla JS, or plain HTML — and deploy it as a zip bundle. Mixpeek handles hosting, CDN, auth, versioning, and the entire backend.

Your app is live at `{slug}.mxp.co` the moment you deploy. No servers to manage, no infrastructure to configure, no API keys in your frontend code.

### How it works

You write frontend code. Mixpeek provides everything else:

* **Hosting** — your built assets (HTML, JS, CSS, images) are stored on S3 and served via CDN with immutable caching
* **Backend proxy** — all `/api/*` requests are forwarded to `api.mixpeek.com` with your org's API key and namespace injected server-side. Your credentials never reach the browser.
* **Runtime injection** — the Canvas runtime injects `window.__MIXPEEK__` into your HTML at serve time with auth config, environment variables, and monitoring hooks
* **Auth** — optionally enable Clerk-powered sign-in (Google, GitHub, email) with per-app user management. The auth SDK is auto-injected — no libraries to install.
* **Environments** — deploy to staging (`staging-{slug}.mxp.co`) or production (`{slug}.mxp.co`) independently
* **Versioning** — every deploy creates an immutable version with content hash, commit message, and source files. Rollback instantly.
* **Monitoring** — error boundaries, Sentry, and PostHog are auto-injected for crash detection and usage analytics

You bring the UI, Mixpeek brings search, retrieval, ingestion, and multimodal AI.

<CardGroup cols={2}>
  <Card title="Deploy from code" icon="upload" href="/canvas/apps/deploy">
    Upload any React, vanilla JS, or static site. Mixpeek serves it from S3 with CDN caching.
  </Card>

  <Card title="Custom domains" icon="globe" href="/canvas/apps/domains">
    Default domain at `{slug}.mxp.co`. Add your own subdomain via CNAME with auto TLS.
  </Card>

  <Card title="Built-in auth" icon="lock" href="/canvas/apps/authentication">
    Clerk-powered sign-in (Google, GitHub, email) — auto-injected into your app with zero config.
  </Card>

  <Card title="User management" icon="users" href="/canvas/apps/users">
    Invite members, assign roles, and control per-app access — powered by Clerk organizations.
  </Card>

  <Card title="Git-like version control" icon="code-branch" href="/canvas/apps/versions">
    Every publish and deploy creates an immutable version with a content hash, message, and diffable snapshot.
  </Card>
</CardGroup>

***

## Architecture

When a user visits `{slug}.mxp.co`, here's the request lifecycle:

1. **DNS** — resolves to the Canvas runtime (Cloudflare-proxied, DDoS protected)
2. **Routing** — the Express server maps the hostname to your `app_id` (cached in Redis). Supports `{slug}.mxp.co`, `staging-{slug}.mxp.co`, and custom domains.
3. **HTML** — `index.html` is fetched from S3 and dynamically injected with `window.__MIXPEEK__` (runtime config, auth, monitoring)
4. **Assets** — JS, CSS, and images are served from S3 via CDN with immutable cache headers (content-hashed filenames → permanent caching)
5. **SPA routing** — non-asset paths fall through to `index.html` so client-side routing (React Router, etc.) works out of the box
6. **API proxy** — `/api/*` requests are forwarded to `api.mixpeek.com` with `Authorization` and `X-Namespace` headers injected server-side

<Note>
  Your API key is **never** sent to the browser. The `/api` proxy runs server-side and injects credentials on every request. Your frontend code only calls relative paths like `/api/v1/retrievers/execute`.
</Note>

### Runtime config injection

The canvas runtime injects a `window.__MIXPEEK__` object into your app's HTML at serve time:

```javascript theme={null}
window.__MIXPEEK__ = {
  apiUrl: "https://api.mixpeek.com",  // Always present
  // Your custom env vars from build_config.env_vars
  MY_CUSTOM_VAR: "value",
  // Auth config (if enabled)
  auth: {
    mode: "clerk",
    publishableKey: "pk_live_...",
    orgId: "org_xyz",
    providers: ["google", "github", "email"],
  },
}
```

Access these values in your app code:

```jsx theme={null}
const apiUrl = window.__MIXPEEK__?.apiUrl
const customVar = window.__MIXPEEK__?.MY_CUSTOM_VAR
```

<Warning>
  Do not use `process.env` in your app — it will crash in the browser. Use `window.__MIXPEEK__` for runtime config or `import.meta.env` for Vite build-time variables.
</Warning>

***

## Quickstart

<Steps>
  <Step title="Create an App in Studio">
    Go to **Apps** → **Create App**. Give it a name and a globally-unique slug — this becomes your default URL at `https://{slug}.mxp.co`.
  </Step>

  <Step title="Build your frontend">
    Write a React app (or any static site) that calls Mixpeek APIs via the canvas proxy. See the [example below](#canvas-sdk).

    ```bash theme={null}
    # Bootstrap a React app
    npm create vite@latest my-search-app -- --template react
    cd my-search-app
    npm install
    npm run build
    # Zip the dist/ folder
    zip -r my-search-app.zip dist/
    ```
  </Step>

  <Step title="Deploy the bundle">
    In the App details page, drag & drop your `.zip` onto the Deploy panel and click **Deploy**. Your build is queued immediately.

    Or via API:

    ```bash theme={null}
    # 1. Get a presigned upload URL
    curl -X POST https://api.mixpeek.com/v1/apps/$APP_ID/deploy/upload-url \
      -H "Authorization: Bearer $API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"filename": "my-app.zip", "content_type": "application/zip"}'

    # 2. PUT the zip to the returned upload_url
    curl -X PUT "$UPLOAD_URL" \
      -H "Content-Type: application/zip" \
      --data-binary @my-app.zip

    # 3. Trigger the build
    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", "environment": "production", "message": "Initial deploy"}'
    ```
  </Step>

  <Step title="Visit your App">
    Your app is live at `https://{slug}.mxp.co` within seconds of a successful build. Add a custom domain to use your own URL.
  </Step>
</Steps>

***

## Canvas SDK

The canvas runtime injects credentials **server-side** — your API key never reaches the browser. Call any Mixpeek API through the `/api` proxy:

```jsx theme={null}
// src/App.jsx — a minimal React search app
import { useState } from 'react'

async function search(query) {
  // No auth headers needed — the canvas proxy injects them server-side
  const res = await fetch('/api/v1/retrievers/ret_abc123/execute', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      inputs: { query },
      settings: { limit: 20 },
    }),
  })
  return res.json()
}

export default function App() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [loading, setLoading] = useState(false)

  const handleSearch = async (e) => {
    e.preventDefault()
    setLoading(true)
    const data = await search(query)
    setResults(data.results ?? [])
    setLoading(false)
  }

  return (
    <div style={{ maxWidth: 800, margin: '0 auto', padding: 24 }}>
      <h1>Search</h1>
      <form onSubmit={handleSearch}>
        <input
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Search..."
          style={{ width: '100%', padding: 8, fontSize: 16 }}
        />
        <button type="submit" disabled={loading}>
          {loading ? 'Searching...' : 'Search'}
        </button>
      </form>
      <ul>
        {results.map((r) => (
          <li key={r.document_id}>
            <strong>{r.fields?.title ?? r.document_id}</strong>
            <p>{r.fields?.description}</p>
          </li>
        ))}
      </ul>
    </div>
  )
}
```

### What `/api` supports

The proxy forwards **all** Mixpeek API methods with your org's credentials injected:

| Category        | Example paths                                                 |
| --------------- | ------------------------------------------------------------- |
| **Retrievers**  | `/api/v1/retrievers/{id}/execute`, `/api/v1/retrievers/list`  |
| **Collections** | `/api/v1/collections/list`, `/api/v1/collections/{id}`        |
| **Documents**   | `/api/v1/documents/{id}`, `/api/v1/documents/list`            |
| **Namespaces**  | `/api/v1/namespaces/list`                                     |
| **Batches**     | `/api/v1/batches/list`, `/api/v1/batches/{id}`                |
| **Tasks**       | `/api/v1/tasks/{id}`, `/api/v1/tasks/list`                    |
| **Marketplace** | `/api/v1/marketplace/catalog/{name}/execute`                  |
| **Taxonomies**  | `/api/v1/taxonomies/list`, `/api/v1/taxonomies/{id}/classify` |

<Note>
  Use `/api/v1/...` (relative path) instead of `https://api.mixpeek.com/v1/...` — the canvas proxy injects `Authorization` and `X-Namespace` headers automatically, avoiding CORS and keeping API keys out of your bundle.
</Note>

### Using the Mixpeek JS SDK

You can also use the `mixpeek` npm package pointed at `/api`:

```jsx theme={null}
import { Mixpeek } from 'mixpeek'

// apiKey is a placeholder — the proxy injects the real key server-side
const client = new Mixpeek({ apiKey: 'canvas', baseUrl: '/api' })

// Execute a retriever
const results = await client.request({
  method: 'POST',
  url: '/v1/retrievers/ret_abc123/execute',
  data: { inputs: { query: 'red shoes' }, settings: { limit: 10 } },
})

// Execute a marketplace retriever
const marketplace = await client.request({
  method: 'POST',
  url: '/v1/marketplace/catalog/brand-safety/execute',
  data: { inputs: { query: 'test content' }, settings: { limit: 5 } },
})
```

### Billing

All API calls through `/api` are billed to the organization that owns the canvas app. End-users don't need their own Mixpeek API keys or accounts — usage is attributed to your org automatically.

***

## Environments

Each app supports two independent environments:

| Environment    | URL                     | Use case                       |
| -------------- | ----------------------- | ------------------------------ |
| **Staging**    | `staging-{slug}.mxp.co` | Test changes before going live |
| **Production** | `{slug}.mxp.co`         | Live, user-facing deployment   |

Each environment has its own S3 asset prefix, so staging and production can serve different versions simultaneously. Deploy to either environment from Studio or via the API.

### Deploying to staging

Set `"environment": "staging"` in the deploy request. Your staging build is live at `https://staging-{slug}.mxp.co`.

### Promoting to production

Deploy the same bundle to production, or use the restore endpoint to point production at a staging version's assets:

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

***

## Creating an App

### Via Studio

Go to **Apps** → **Create App**. The wizard collects:

1. **Name + Slug** — the name is display-only; the slug becomes your URL (`{slug}.mxp.co`) and must be globally unique
2. **Access Control** — choose how end-users authenticate (public, password, API key, JWT, or SSO)

### Via API

Only `slug` and `meta` are required. After creation, deploy your code via the deploy pipeline.

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

  client = Mixpeek(api_key="your-api-key")

  app = client.apps.create(
      slug="product-search",
      meta={"title": "Product Search"},
      auth_config={"mode": "clerk"},  # optional — enable Clerk auth
  )
  print(f"App created: {app.app_id}")
  print(f"URL: https://{app.slug}.mxp.co")
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.mixpeek.com/v1/apps \
    -H "Authorization: Bearer $API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "slug": "product-search",
      "meta": { "title": "Product Search" },
      "auth_config": { "mode": "clerk" }
    }'
  ```
</CodeGroup>

***

## Deploy lifecycle

Every deploy creates a new versioned build. The lifecycle is:

| Stage      | Description                                                     |
| ---------- | --------------------------------------------------------------- |
| `queued`   | Bundle uploaded, build queued                                   |
| `building` | Mixpeek is packaging and deploying your bundle                  |
| `complete` | Build complete — app is live                                    |
| `failed`   | Build failed — previous version stays live, check error message |

### Operations

| Operation                                               | What it does                               | Creates a version?                    |
| ------------------------------------------------------- | ------------------------------------------ | ------------------------------------- |
| **Deploy** (`POST /v1/apps/{id}/deploy`)                | Upload and serve a new code bundle         | Yes — with asset manifest and message |
| **Restore** (`POST /v1/apps/{id}/versions/{v}/restore`) | Roll back to any previous version's assets | No — instant redirect                 |
| **Promote** (`POST /v1/apps/{id}/promote`)              | Promote staging deploy to production       | No — swaps environments               |
| **Update** (`PATCH /v1/apps/{id}`)                      | Update app config (auth, meta, etc.)       | No                                    |

Each deploy requires a commit message describing what changed. See [Version History](/canvas/apps/versions) for the full version control system.

***

## Authentication modes

| Mode       | Description                                                              |
| ---------- | ------------------------------------------------------------------------ |
| `public`   | Anyone can access — no login required                                    |
| `password` | Shared password gate                                                     |
| `api_key`  | Requires `X-App-Key` header                                              |
| `clerk`    | Clerk-powered sign-in with [per-app user management](/canvas/apps/users) |
| `jwt`      | Customer-managed tokens                                                  |
| `sso_oidc` | Okta, Auth0, Azure AD                                                    |
| `sso_saml` | Enterprise SAML 2.0 IdP                                                  |

<Note>
  Auth enforcement activates when the canvas runtime is deployed. Config is stored now and takes effect automatically.
</Note>

***

## Custom domains

Every app gets a default URL at `https://{slug}.mxp.co`. You can also add your own subdomain:

1. Enter your subdomain (e.g., `search.yourdomain.com`) in the **Domains** panel
2. Add a DNS `CNAME` record pointing to the target shown in the response
3. Click **Verify** — Mixpeek provisions a TLS certificate automatically via Cloudflare

See [Custom Domains](/canvas/apps/domains) for the full setup guide.

***

## Example apps

<CardGroup cols={2}>
  <Card title="E-commerce product search" icon="bag-shopping">
    React app with multimodal search — text queries, image upload, and faceted filters backed by a Mixpeek retriever.
  </Card>

  <Card title="Internal knowledge base" icon="book">
    Password-protected semantic search over company documents, PDFs, and meeting transcripts.
  </Card>

  <Card title="Media library" icon="photo-film">
    Video + image search portal with scene-level results, thumbnails, and timestamp previews.
  </Card>

  <Card title="Content moderation dashboard" icon="shield-check">
    Real-time moderation queue pulling from alert webhooks and showing flagged content with similarity scores.
  </Card>
</CardGroup>

***

## Related

* [Version History](/canvas/apps/versions)
* [Deploy from Code](/canvas/apps/deploy)
* [Authentication](/canvas/apps/authentication)
* [User Management](/canvas/apps/users)
* [Custom Domains](/canvas/apps/domains)
* [Create App (API)](/api-reference/apps/create-app)
* [Deploy App (API)](/api-reference/apps/deploy-app)
