# Preview environments

Open a short-lived Aviato environment for each pull request from CI, so reviewers see the back-office against the pull request's database.

Source: https://docs.getaviato.com/integrations/preview-environments

A preview environment is an environment of your project that CI opens for a pull request and Aviato removes when it expires. Reviewers switch to it in the dashboard and see the back-office, your plugin's actions and the schema of the pull request's database, before the change reaches staging or production.

A preview is an ordinary environment with its own connector, credential and audit trail, and the same members and roles as the project. It differs in four ways:

* **It expires.** Each preview has an expiry, 72 hours by default, between 1 hour and 30 days. Opening it again extends it. Aviato checks every five minutes and removes expired previews, as it does when you close one.
* **It has its own limit.** Previews do not count against the plan's environments per project. The plan sets how many previews a project can have open at once (see [Plans and limits](/administration/plans-and-limits)).
* **Its audit trail is short.** Events of a preview are kept for 7 days at most, or less when your plan's retention is shorter. Audit exports still work. [Audit streams](/administration/audit) (webhooks and S3) leave previews out.
* **It accepts no AI agents.** The connector of a preview answers MCP requests with `403 mcp_disabled`, and Aviato does not register it as an OAuth resource. Connect agents to a standard environment.

## Create a preview API key [#create-a-preview-api-key]

CI authenticates with a preview API key, which belongs to one project. In the dashboard, open the project's **Settings → Environments**, then **Preview API keys → Create key**. Copy the key (it starts with `avp_` and is shown once) and store it as a CI secret, for example `AVIATO_PREVIEW_KEY`.

Only project managers can create and revoke keys: workspace owners, and admins without a custom role on the project. A key acts for the manager who created it. It stops working when that person leaves the workspace or no longer manages the project. Creating and revoking a key is recorded in the project's audit trail; previews opened with the key are recorded with the key as the actor.

## Open and close previews [#open-and-close-previews]

The connector's `preview` command calls the previews API with `AVIATO_PREVIEW_KEY`. Set `AVIATO_CONTROL_PLANE_URL`, the same variable the connector reads, when you run your own control plane (`https://api.getaviato.com` by default).

```bash
aviato-connector preview up --name pr-12 --pull-request-url https://github.com/acme/shop/pull/12
# AVIATO_CONTROL_PLANE_URL=https://api.getaviato.com
# AVIATO_CONNECTOR_TOKEN=avt_pr-1_…
# AVIATO_ENVIRONMENT=pr-12
# AVIATO_DASHBOARD_URL=https://app.getaviato.com/dashboard/…?env=pr-12

aviato-connector preview down --name pr-12
```

`up` opens the preview, or extends it if it is already open. Either way it returns a new connector token, and the previous token stops working, so run `up` again on every push and restart the connector with the new token. Names use lowercase letters, digits and `-`, start with a letter and have at most 31 characters. A preview cannot reuse the name of a standard environment (`production`, `staging`…). `--ttl-hours` sets the lifetime (1–720, default 72) and `--format json` prints the API's answer instead of variables. Hosted projects pass the preview database with `--database-url`; self-hosted connectors read it from `AVIATO_DATABASE_URL` and never send it to Aviato.

Inside GitHub Actions, `up` also hides the token in the logs and writes step outputs: `control_plane_url`, `connector_token`, `environment`, `dashboard_url` and `expires_at`. `down` succeeds when the preview is already gone.

## GitHub Actions [#github-actions]

The workflow below opens a preview when a pull request opens or changes, runs its connector against the pull request's database, comments the dashboard link, and closes the preview when the pull request closes. The connector must be reachable from your reviewers' browsers at `AVIATO_PUBLIC_URL`, so run it where your pull request deployments run. The `docker run` step stands for that deployment.

```yaml
name: Aviato preview

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]

jobs:
  preview:
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Open the preview
        id: preview
        env:
          AVIATO_PREVIEW_KEY: ${{ secrets.AVIATO_PREVIEW_KEY }}
        run: |
          docker run --rm -e AVIATO_PREVIEW_KEY -e GITHUB_OUTPUT=/github/output -v "$GITHUB_OUTPUT:/github/output" \
            getaviato/connector:0.3.0 preview up \
            --name pr-${{ github.event.number }} --pull-request-url ${{ github.event.pull_request.html_url }}

      # Your preview platform: deploy the connector next to the pull request's database.
      - name: Run the connector
        run: |
          docker run -d --name aviato-pr-${{ github.event.number }} -p 8080:8080 \
            -e AVIATO_CONTROL_PLANE_URL=${{ steps.preview.outputs.control_plane_url }} \
            -e AVIATO_CONNECTOR_TOKEN=${{ steps.preview.outputs.connector_token }} \
            -e AVIATO_DATABASE_URL=${{ secrets.PREVIEW_DATABASE_URL }} \
            -e AVIATO_PUBLIC_URL=https://aviato-pr-${{ github.event.number }}.preview.example.com \
            getaviato/connector:0.3.0

      - name: Comment the dashboard link
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          header: aviato-preview
          message: |
            Aviato back-office for this pull request: ${{ steps.preview.outputs.dashboard_url }}
            (expires ${{ steps.preview.outputs.expires_at }})

  close:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:
      - name: Close the preview
        env:
          AVIATO_PREVIEW_KEY: ${{ secrets.AVIATO_PREVIEW_KEY }}
        run: docker run --rm -e AVIATO_PREVIEW_KEY getaviato/connector:0.3.0 preview down --name pr-${{ github.event.number }}
```

These commands need connector 0.3.0 or later; keep the version pinned. If a pipeline never closes a preview, it is removed when it expires.

## Previews API [#previews-api]

The command wraps three HTTP endpoints. Send the key as `Authorization: Bearer <key>` or `x-api-key`.

| Request                      | Effect                                                                                                                                                                                                                                                 |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PUT /v1/previews/{name}`    | Opens or extends the preview. Body (JSON, every field optional): `ttlHours` (1–720, default 72), `pullRequestUrl`, `databaseUrl` (hosted projects only). Answers `{ environment, connectorToken, controlPlaneUrl, dashboardUrl, expiresAt, created }`. |
| `DELETE /v1/previews/{name}` | Closes the preview. Answers `204`, also when it is already gone.                                                                                                                                                                                       |
| `GET /v1/previews`           | Lists the project's open previews: `{ previews: [{ name, expiresAt, pullRequestUrl, connectorUrl, dashboardUrl, createdAt }] }`.                                                                                                                       |

```bash
curl -X PUT https://api.getaviato.com/v1/previews/pr-12 \
  -H "Authorization: Bearer $AVIATO_PREVIEW_KEY" -H "content-type: application/json" \
  -d '{"ttlHours": 72, "pullRequestUrl": "https://github.com/acme/shop/pull/12"}'
```

Errors use the standard JSON shape (`statusCode`, `message`): `400` for an invalid name or body, `401` for a missing or invalid key (or one whose creator no longer manages the project), `403` when the plan's preview limit is reached, `409` when the name belongs to a standard environment, and `429` past 120 requests per minute per key.