---
title: CI and AI agent workflows
---

The CLI behaves the same way on a laptop, in a CI runner, and inside an AI agent's tool. Three things make that work:

1. **Environment-variable authentication** so secrets never have to be written to disk on a shared runner.
2. **Tightly scoped API keys** so a leaked credential is bounded by what it can do.
3. **Predictable output** in non-interactive contexts (no banners, no prompts, no surprise prompts for `--help`).

This page covers how to wire each of those up.

## Authenticate without a file

Set two environment variables and skip `zevcloud login` entirely:

```bash
export ZEVCLOUD_API_KEY=sk_live_...
export ZEVCLOUD_TEAM_ID=<your-team-id>

zevcloud deploy <serviceId>
```

When `ZEVCLOUD_API_KEY` is set, the CLI:

- Uses the env-var key for every request.
- Never reads from the on-disk credential store.
- Never writes credentials to disk.
- Leaves any team configuration the user has set up locally untouched.

`ZEVCLOUD_TEAM_ID` is the team UUID and pairs with the API key. You can find it in the dashboard URL when viewing the team.

> **Warning:**
>
> Treat `ZEVCLOUD_API_KEY` like any other production secret. Pass it via the runner's secret store (GitHub Actions secrets, GitLab CI/CD variables, etc), never commit it to a repository, and never echo it to logs.

## GitHub Actions

A minimal deploy step in a workflow:

```yaml title=".github/workflows/deploy.yml"
name: Deploy to ZevCloud
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm install -g @zevcloud/cli

      - name: Deploy
        env:
          ZEVCLOUD_API_KEY: ${{ secrets.ZEVCLOUD_API_KEY }}
          ZEVCLOUD_TEAM_ID: ${{ secrets.ZEVCLOUD_TEAM_ID }}
        run: zevcloud deploy ${{ vars.SERVICE_ID }}
```

A few notes on this pattern:

- Use `secrets.ZEVCLOUD_API_KEY`, not `vars`, for the key. GitHub masks values from `secrets` in logs but does not mask `vars`.
- `vars.SERVICE_ID` is fine for the service id since it is not sensitive.
- `npm install -g @zevcloud/cli` finishes in seconds and installs cleanly without local file writes.

## AI agents

For tool-using agents (Claude Code, agents wrapped around the OpenAI tool API, custom orchestrators), expose `zevcloud` as a tool and pass the env vars at process start.

The two most useful commands for an agent to call:

- `zevcloud services` to discover what is deployed.
- `zevcloud deploy <serviceId>` to push a change.

Generate a key with the `deploy` scope (see below). The agent can deploy and read state but cannot create or destroy services, change billing, or rotate keys. Tighter scope means a leaked key cannot do as much.

The CLI also exposes `zevcloud version` for diagnostics. An agent that hits an error and includes `zevcloud version` output in its report gives the maintainers everything they need to reproduce.

## API key scopes

Pick the lowest scope that lets the workflow do its job. Scopes are fixed at key creation and cannot be promoted later.

| Scope | Use it for |
|-------|------------|
| `read` | Health-check workflows, dashboards that just need to list state. |
| `deploy` | CI pipelines, AI agents. Can deploy, restart/stop, set env vars, verify domains. **The right default for non-interactive use.** |
| `full` | Equivalent to a human admin. Required for creating projects, services, environments, or running terminal commands inside containers. Use sparingly. |

The full permission table is at [Authentication](/cli/auth#api-key-scopes).

## What the CLI suppresses in a non-interactive run

When `CI=true` is set (most CI providers set this automatically) or when stdout is not a TTY:

- The postinstall welcome banner is silent.
- The "is there a newer version?" check is silent.
- Color output is auto-disabled in environments that do not support ANSI.

To disable these explicitly even outside CI, set `ZEVCLOUD_DISABLE_UPDATE_CHECK=true` and `ZEVCLOUD_DISABLE_POSTINSTALL=true`.

## Rotating a key without downtime

API keys are issued and revoked from the dashboard at **Settings → API Keys**. To rotate:

1. Generate a new key with the same scope.
2. Update `ZEVCLOUD_API_KEY` in your CI secrets to the new value.
3. Trigger a deploy to confirm the new key works.
4. Revoke the old key in the dashboard.

The old key keeps working until you revoke it. There is no overlap requirement on our side.

## Per-team workflows in CI

If a single repository deploys to multiple teams (a monorepo serving more than one client, for example), use one set of env vars per job:

```yaml title=".github/workflows/deploy-multi-team.yml"
jobs:
  deploy-acme:
    runs-on: ubuntu-latest
    env:
      ZEVCLOUD_API_KEY: ${{ secrets.ACME_KEY }}
      ZEVCLOUD_TEAM_ID: ${{ vars.ACME_TEAM_ID }}
    steps:
      - run: npm install -g @zevcloud/cli
      - run: zevcloud deploy ${{ vars.ACME_SERVICE_ID }}

  deploy-beta:
    runs-on: ubuntu-latest
    env:
      ZEVCLOUD_API_KEY: ${{ secrets.BETA_KEY }}
      ZEVCLOUD_TEAM_ID: ${{ vars.BETA_TEAM_ID }}
    steps:
      - run: npm install -g @zevcloud/cli
      - run: zevcloud deploy ${{ vars.BETA_SERVICE_ID }}
```

Each job uses the team's own key. The CLI picks up whichever pair of env vars the job exports, with no per-machine state to worry about.

> **Tip:**
>
> For any non-interactive workflow, `zevcloud deploy <id>` is the single most useful command. The type-aware routing means it does the right thing whether the service is a web app, WordPress site, static site, or database. The workflow does not need to know what type it is.
