> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sabaothcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripting and CI

> Use the sabaoth CLI in scripts and CI pipelines with JSON output, exit codes and environment credentials

Every command is designed to be safe to script. This page covers the rules all commands follow, so you can rely on them in automation.

## The essentials

1. **Authenticate with `SABAOTH_API_KEY`**, not `sabaoth login`. Store the key in your CI provider's secret store. See [Authentication](https://docs.sabaothcloud.com/cli/authentication.md#use-an-environment-variable).
2. **Pass `--json`** and parse stdout.
3. **Pass `--yes`** to `postgres create`, `postgres delete` and `backup create`. Without a terminal they refuse to run unconfirmed.
4. **Branch on the exit code**, and on the error `code` when you need detail.

## Output streams

| Stream | What goes there |
| - | - |
| **stdout** | Only the command's result: a table or message in human mode, one JSON document in `--json` mode. |
| **stderr** | Prompts, warnings, notices and errors. |

This means `sabaoth postgres list --json > services.json` always produces a clean file, and confirmation prompts never end up in captured output.

## JSON output

With `--json`, a successful command prints exactly one JSON document on stdout.

* Keys are `snake_case`.
* Within a major version, fields are only ever **added**, never renamed or removed. **Ignore fields you don't recognize** so your scripts keep working as new ones appear.
* Every list command puts its results in `items`.
* JSON output isn't altered for terminal safety. Values round-trip exactly as the API sent them.

`sabaoth postgres logs --json` is the exception: it prints newline-delimited JSON, one record per line. See [JSON output for logs](https://docs.sabaothcloud.com/cli/commands/postgres-logs.md#json-output).

### Warnings

In human mode, warnings print on stderr as `Warning: ...`. In `--json` mode they're added to the success document instead, so stderr stays clean:

```json theme={null}
{
  "logged_in": true,
  "organization": { "id": "org_abc", "display_name": "Acme" },
  "warnings": ["--api-key is recorded in your shell history. Prefer the hidden prompt, or set SABAOTH_API_KEY for automation."]
}
```

## Errors

A failed command prints exactly one error.

**In `--json` mode** the error is a single line of JSON on **stderr**, and stdout stays empty:

```json theme={null}
{"code":"CLUSTER_NOT_FOUND","category":"client","message":"No service clu_xyz in the effective Organization.","retryable":false}
```

| Field | Description |
| - | - |
| `code` | A stable identifier in `SCREAMING_SNAKE_CASE`. Safe to branch on. See [Error codes](https://docs.sabaothcloud.com/cli/errors.md). |
| `category` | A broad group: `invocation`, `auth`, `credential`, `context`, `client`, `server`, `network` or `internal`. |
| `message` | A human-readable explanation. Don't parse it; the wording may change. |
| `retryable` | `true` if running the same command again may succeed, for example after a network blip. |
| `request_id` | Included when the API supplied one. Quote it when contacting [support](https://docs.sabaothcloud.com/support.md). |

**In human mode** the error goes to stderr as:

```text theme={null}
Error [CLUSTER_NOT_FOUND]: No service clu_xyz in the effective Organization.
```

followed by a `Request ID:` line when there is one, and `This failure may be temporary; retrying is safe.` when `retryable` is `true`.

## Exit codes

| Code | Meaning |
| - | - |
| `0` | Success. |
| `1` | The command ran but failed: an API error, a network failure, a rejected key, a missing service. |
| `2` | The command didn't run: a bad flag value, a missing confirmation, no organization selected, or you declined a prompt. |
| `130` | Cancelled with Ctrl-C. |

Exit codes are part of the CLI's stable interface and won't change within a major version.

## Retrying safely

Only retry when `retryable` is `true`. That covers network failures (`NETWORK_UNAVAILABLE`), rate limiting (`RATE_LIMITED`) and temporary API outages (`API_UNAVAILABLE`).

The CLI never retries a create, delete or backup request on its own. If one of them gets no response, it fails with `UNKNOWN_MUTATION_OUTCOME` and `retryable: false`, because the change may already have happened. **Check before trying again**, or you risk a second billable service or duplicate backup. See the "If the result is unknown" section on each command's page.

## Colors

Colors are turned off when stdout isn't a terminal. To turn them off everywhere, set `NO_COLOR` to any value:

```bash theme={null}
export NO_COLOR=1
```

## Examples

### Get the IDs of all ready services

```bash theme={null}
sabaoth postgres list --all --json | jq -r '.items[] | select(.status == "ready") | .id'
```

### Wait for a new service to be ready

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail

id=$(sabaoth postgres create \
  --name "preview-${BRANCH_NAME}" \
  --cpu-millicores 250 --memory-mb 512 --storage-gb 5 \
  --yes --json | jq -r '.id')

echo "Created $id, waiting for it to be ready..."

for _ in $(seq 1 60); do
  status=$(sabaoth postgres get "$id" --json | jq -r '.status')
  case "$status" in
    ready)  echo "Ready."; break ;;
    failed) echo "Provisioning failed." >&2; exit 1 ;;
  esac
  sleep 10
done

sabaoth postgres get "$id" --json | jq '.connection'
```

### Handle a specific error

```bash theme={null}
if ! out=$(sabaoth postgres get "$id" --json 2>err.json); then
  code=$(jq -r '.code' err.json)
  if [ "$code" = "CLUSTER_NOT_FOUND" ]; then
    echo "Service $id is gone."
  else
    cat err.json >&2
    exit 1
  fi
fi
```

### Back up before a migration

```bash theme={null}
sabaoth backup create "$SERVICE_ID" --name "pre-migrate-$(date +%Y%m%d%H%M)" --yes --json
```

### GitHub Actions

```yaml theme={null}
name: Nightly backup
on:
  schedule:
    - cron: "0 3 * * *"

jobs:
  backup:
    runs-on: ubuntu-latest
    env:
      SABAOTH_API_KEY: ${{ secrets.SABAOTH_API_KEY }}
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm install --global @sabaoth-cloud/cli
      - run: sabaoth backup create ${{ vars.SERVICE_ID }} --yes --json
```

### GitLab CI

```yaml theme={null}
backup:
  image: node:22
  # Set SABAOTH_API_KEY and SERVICE_ID as masked CI/CD variables in project settings
  script:
    - npm install --global @sabaoth-cloud/cli
    - sabaoth backup create "$SERVICE_ID" --yes --json
```

> [!TIP]
>
> If the CI key covers more than one organization, add `--org <org-id>` to every command, or the CLI fails with `AMBIGUOUS_ORGANIZATION`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.