# Agents, CI, and scripts

Use Deplane from AI agents, CI pipelines, and scripts with a token, JSON output, and stable exit codes.

The CLI works the same for people and programs. Every command supports `--json` and `--yes`, never prompts without a terminal, and exits with a documented code.

## Get a token

Create a CI token from your own machine:

```sh
deplane tokens create github-actions
```

Creating a token needs a fresh confirmation with your passkey from the last 5 minutes. In a terminal, the CLI opens your browser, you tap your passkey, and the command continues. The token is printed once. Store it in your CI's secret store.

Then set it in the environment where the CLI runs:

```sh
export DEPLANE_TOKEN=dpl_...
deplane deploy --json --yes
```

About CI tokens:

- A CI token belongs to your organization and acts as you.
- It lasts 90 days by default. Choose 1 to 365 days with `--expires-in-days`.
- It cannot create tokens, list or revoke other tokens, or run admin commands. It can revoke itself.
- Revoke it any time with `deplane tokens revoke ID`. It stops working at once.
- Tokens start with `dpl_`. If one leaks into a log, `deplane` output and `--debug` hide it as `dpl_[REDACTED]`.

## Agent tokens

A CI token, like your own login, can do everything you can. To give an AI agent less, give it an agent token with a role, and optionally limit it to some apps:

```sh
deplane agent login --role deployer --app web --name claude-code
```

The agent can run this itself. You approve the request in your browser, which shows the role, the apps and how long the token lasts. The token is printed once, for the agent to use as `DEPLANE_TOKEN`, and never saved on the computer.

| Role | Can |
|---|---|
| `viewer` | See apps, builds, releases, logs, metrics, domains and databases. Changes nothing. |
| `deployer` | Also build, redeploy and roll back. A deploy can be any code or image, which runs with the app's variables. |
| `operator` | Also create apps and databases, set and unset variables, scale, and add domains and workers. |

- No role can delete anything, run commands, import databases, manage tokens or team members, or confirm actions: those stay yours ([agent_not_allowed](errors.md#agent_not_allowed)).
- With `--app A,B`, the token reaches only those apps ([agent_app_not_allowed](errors.md#agent_app_not_allowed)).
- It lasts 7 days by default, at most 30 (`--ttl 12h`, `--ttl 30d`).
- You get an email with a link that revokes it. It is listed by `deplane tokens list` and revoked with `deplane tokens revoke ID`.
- Its actions are in your team's activity log under the agent's name.

Note that an agent on your own computer can also use your `deplane login`. An agent token keeps the agent to its role when it runs somewhere your login is not, such as a sandbox or a cloud session.

## GitHub Actions example

On GitLab CI or Bitbucket Pipelines, the job needs no token at all: see [GitLab and Bitbucket](gitlab-bitbucket.md).

The install and deploy steps in `.github/workflows/deploy.yml`:

```yaml
- run: |
    curl -fsSL https://deplane.io/install | DEPLANE_VERSION=0.1.0 DEPLANE_INSTALL_DIR="$HOME/.local/bin" sh
    echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- run: deplane deploy --json --yes
  env:
    DEPLANE_TOKEN: ${{ secrets.DEPLANE_TOKEN }}
```

`DEPLANE_VERSION` keeps CI on a version you chose. The install script checks the release's signature before it installs it ([Install](install.md#install-script)).

Commit `deplane.toml` so the CI run knows which app to deploy, or pass `--app NAME`.

## Rules for programs

- **Use `--json`.** Each command prints exactly one JSON document on stdout. Progress messages and build logs go to stderr. `deplane logs --json` prints one JSON object per line.
- **Use `--yes`.** Commands that destroy or roll back ask for confirmation. Without a terminal, the CLI never asks. Instead the command fails with [confirmation_required](errors.md#confirmation_required), and the fix names the flag to pass: `--yes`.
- **Check the exit code.** It tells you what kind of failure happened, without parsing text.
- **Read `fix`.** Every error has a fix: a sentence that says what to do next, usually with the exact command.

## Exit codes

| Code | Meaning | What an agent should do |
|---|---|---|
| 0 | Success. | Continue. |
| 1 | User error. | Read `fix`, change the command or the project, and try again. |
| 2 | Authentication or permission error. | Check `DEPLANE_TOKEN`, or ask a person to log in, confirm, or do the action a token or role may not do. |
| 3 | The build failed. | Read the build log on stderr and `fix`, change the code, and deploy again. |
| 4 | The deploy failed. | Run `deplane logs --json` and `deplane status --json`, fix the app, and deploy again. The previous release keeps serving. |
| 5 | Platform error. | Wait and retry. If it persists, report it. |
| 6 | Quota exceeded. | A plan limit was reached (codes starting with `limit_`). Free resources or ask a person to raise the limit. |

## Error shape

With `--json`, a failure prints:

```json
{
  "error": {
    "code": "no_app",
    "message": "This folder has no deplane.toml, so there is no app to deploy.",
    "fix": "Run `deplane launch` to create the app, or pass --app NAME.",
    "exit_code": 1
  }
}
```

`code` is stable. Look it up in [Troubleshooting and error codes](errors.md): each code has its own section at `https://deplane.io/docs/errors#CODE`.

## Actions that need a fresh passkey

Some actions need the person to confirm with their passkey in the last 5 minutes: creating a token, inviting or removing a team member, destroying an app, deleting, restoring or switching a database, and every admin command. In a terminal, the CLI opens the browser, waits for the tap, and retries on its own. Destroying an app and deleting, restoring or switching a database each use up their confirmation, so every one of them needs its own tap.

Without a terminal, the CLI cannot do that. It fails with exit code 2 and [step_up_required](errors.md#step_up_required):

```json
{
  "error": {
    "code": "step_up_required",
    "message": "This command needs a fresh confirmation with your passkey, and no terminal is attached.",
    "fix": "Run it in a terminal to confirm in your browser. For 5 minutes after that, most such commands also work from scripts; deleting, restoring and switching need a confirmation each time.",
    "exit_code": 2
  }
}
```

An agent should hand this step to a person. `--yes` does not replace it, and CI tokens are refused these actions with [ci_token_not_allowed](errors.md#ci_token_not_allowed). Deploys, scaling, secrets, logs, and rollbacks never need it.

## A typical agent session

```sh
deplane doctor --json
deplane launch --json --yes
deplane status --json
deplane logs --json --tail 200
deplane secrets set --json DATABASE_URL=postgres://...
deplane rollback --json --yes
```

Each command prints one JSON document and exits with a code from the table above.

## After a deploy

A deploy that succeeded can still be rolled back by Deplane in its first 10 minutes live, if the 5xx errors the app returns spike ([automatic rollback](cli.md#automatic-rollback)). To check, run `deplane releases --json`: a release Deplane made this way has `"automatic_rollback": true` and a description that starts with `Automatic rollback to vN`. Read the logs of the release that spiked before deploying again. To turn it off for an app, run `deplane config --auto-rollback off`.

## Restore a database

Restores never change the original database. Restoring and switching need a [fresh passkey](#actions-that-need-a-fresh-passkey), so a person runs them from a terminal (or the dashboard):

```sh
deplane db backups main --json
deplane db restore main --at -10m --json
deplane db switch main-restored-1442 --from main --json
```

`deplane db restore` waits until the new database is ready (up to 15 minutes); pass `--no-wait` to return at once. See [Databases](databases.md#backups-and-restore).

## AI tools

Claude, ChatGPT, Cursor and other MCP clients can use Deplane without the CLI: see [AI tools (MCP)](mcp.md).

## Docs for agents

- https://deplane.io/llms.txt lists every docs page with a one-line description and a link to its Markdown.
- Every docs page is also available as Markdown: add `.md` to its URL. For example, https://deplane.io/docs/limits.md.
- The API is described by OpenAPI at https://api.deplane.io/v1/openapi.yaml. API errors have the same `code`, `message`, and `fix`, plus a `docs` link and a `request_id`.
