+++
title = "Scripting and CI with the CLI"
description = "JSON output, exit codes, running without prompts, and bonsai api."
weight = 120
updated = 2026-11-11

[extra]
nav_title = "Scripting and CI"
weight = 70
+++

The CLI is built to run unattended: it never waits on a prompt nobody will answer, it prints data and messages on separate streams, and its exit codes say what went wrong.

## Authenticate with BONSAI_TOKEN

In CI, skip `bonsai login` and put an [API token](@/docs/account/api-tokens/index.md) in the `BONSAI_TOKEN` environment variable, from your CI's secret store:

```bash
export BONSAI_TOKEN=bonsai_<key>_<secret>
```

Give the token only the [scopes](@/docs/api/authentication/index.md#scopes) the job needs.

## No prompts

Commands never prompt when stdin isn't a terminal, when the `CI` environment variable is set, or with `--no-input`. Instead, a missing value fails with exit code 2 and names the flag to add:

```
$ bonsai cluster destroy search --no-input
✗ destroying a cluster needs --confirm <its name> when prompts are off
```

## Output

- **Data goes to stdout. Messages and progress go to stderr.** So `$(bonsai cluster url search)` captures only the URL.
- **`--json`** prints the API's JSON instead of tables and text, for `jq` and scripts. Lists come out as one array, with every page.
- **`-q`, `--quiet`** prints only data and errors, with no progress or notes.
- **Tables** fit your terminal: long text is cut with `…`, and the least important columns are left out when space runs out. Add `-w` or `--wide` to see every column. Piped output is never cut.
- **`NO_COLOR`** turns colour off.

Text that comes from the API, such as cluster names and search terms, is cleaned of terminal control characters before it's shown. Under `--json`, control and bidirectional-text characters inside strings are escaped as `\uXXXX`.

The only commands that print a cluster's password are [`bonsai cluster url`](@/docs/cli/connection-urls/index.md) without `--write`, and `bonsai api` when the API's response includes one. Tokens are never logged, even with `-vv`.

## Exit codes {#exit-codes}

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Network errors, server errors, anything not listed below |
| 2 | Usage: a bad or missing flag, or an ambiguous cluster name |
| 3 | Not allowed: no token, an invalid token, or a missing scope |
| 4 | Not found |
| 5 | The Action failed |
| 6 | A payment method is needed, or the card was refused |
| 7 | Busy: another change is already running |
| 8 | Refused: the request was valid but Bonsai declined it |
| 9 | `--timeout` ran out before the Action finished |
| 130 | Interrupted with Ctrl-C |

Exit code 9 means the wait ran out, not that the change stopped. Pick it up with `bonsai wait <action-id>`.

Exit code 8 covers the API's [`validation_failed`](@/docs/api/errors/index.md#validation_failed), [`name_mismatch`](@/docs/api/errors/index.md#name_mismatch), [`plan_change_refused`](@/docs/api/errors/index.md#plan_change_refused) and [`cluster_terminated`](@/docs/api/errors/index.md#cluster_terminated) errors.

## A cluster per CI run

```bash
name="ci-$RUN_ID"
id=$(bonsai cluster create "$name" --plan sandbox --region aws-us-east-1 --json | jq -r .result.cluster)
trap 'bonsai cluster destroy "$id" --confirm "$name"' EXIT

export BONSAI_URL=$(bonsai cluster url "$id")
run-tests
```

`cluster create --json` prints the finished Action, and the new cluster's ID is its `.result.cluster`. If the create fails after Bonsai started it, Bonsai removes the failed cluster.

To start several clusters at once, use `--no-wait` and wait for each afterwards:

```bash
a=$(bonsai cluster create "$name-a" --plan sandbox --region aws-us-east-1 --no-wait --json | jq -r .id)
b=$(bonsai cluster create "$name-b" --plan sandbox --region aws-us-east-1 --no-wait --json | jq -r .id)
bonsai wait "$a" && bonsai wait "$b"
```

A script that retries a create on its own should pass the same `--idempotency-key` each time, so a retry can't make a second cluster. See [Retrying safely](@/docs/api/retrying-safely/index.md).

## bonsai api {#bonsai-api}

`bonsai api` sends any request to the [API](@/docs/api/reference/_index.md) with your token, for things that have no command yet:

```bash
bonsai api GET /clusters --paginate
bonsai api GET '/clusters?limit=5'
bonsai api POST /clusters/search-nozais/credentials -d '{"name": "reader", "access_type": "ro"}'
```

| Flag | |
|---|---|
| `-d`, `--data <JSON>` | The JSON body. `@FILE` reads a file, and `-` reads stdin |
| `-H`, `--header <NAME: VALUE>` | An extra header. Repeatable |
| `--paginate` | Follow `next_cursor` and print every page's data as one array |

The path is relative to `/v1`, and takes cluster IDs, not names. The response is printed as it is, so it can include passwords.

## Debugging

`-v` logs each request to stderr, and `-vv` logs more. `bonsai doctor` checks the API, the token and your clock in one go.
