Browse docs

Use the CLI in scripts, CI and AI agents

JSON output, exit codes, non-interactive behavior and scoped tokens for automation and AI agents.

Updated

On this page

The CLI is designed to be safe in scripts: nothing ever prompts when there's no terminal or --json is set, every error has a stable machine-readable shape, and exit codes are documented. This article covers the contract; CLI reference has the tables in full.

Tokens for automation

For CI and agents, don't log in with your personal profile; create a dedicated token in Settings → API with only the scopes the job needs and an expiry date, then pass it through the environment:

export BITQATE_TOKEN=bqat_xxxxxxxx_xxxxxxxxxxxxxxxx
bitqate servers restart api --yes

BITQATE_TOKEN overrides the stored profile and may be used against any API URL (an explicit choice you make, unlike a stored profile token, which is only ever sent to the URL it was logged in with; see below). Point at another API with --api-url or BITQATE_API_URL.

Output contract

  • --json prints the command's data as JSON on stdout; the same data the human view shows, with full IDs.
  • Errors always go to stderr: a JSON {"error":{"code","message"}} in JSON mode, ✗ message plus a hint otherwise.
  • Prompts, spinners and pickers need stdin and stderr to be terminals; colour and table formatting need stdout to be one. None of it appears in JSON mode or when CI is set. Without a TTY, a missing required flag or argument is a plain usage error; nothing falls back to prompting.
  • -q prints only resource IDs for list commands.

Exit codes

CodeMeaning
0Success
1Server or network error
2Usage error, or the API rejecting the request (400/422)
3Not authenticated / invalid token
4Forbidden; the token lacks the required scope
5Resource not found
6Conflict
7Rate limited
8Payment required
9Wait timed out
130Cancelled (Ctrl-C or Esc in a prompt or wait)

Non-interactive safety

Destructive commands (delete, reinstall, backups restore, backups delete) need --yes without a TTY; with one they ask you to type the resource name. --dry-run prints the HTTP request a command would send and sends nothing; handy for checking a generated request body:

bitqate vms create --node montreal --os "debian 12" --memory-mb 2048 --cpus 2 --disk-gb 20 --dry-run --yes

bitqate schema dumps every command, flag, alias, scope and flag placeholder as JSON; intended for agents and generated tooling rather than hand-reading.

Example: GitHub Actions

Store the token as a repository secret (BITQATE_TOKEN), then:

name: Deploy
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 i -g @bitqate/cli
      - name: Deploy and wait
        env:
          BITQATE_TOKEN: ${{ secrets.BITQATE_TOKEN }}
        run: bitqate servers deploy api --follow --json

--follow streams the build log to stderr in JSON mode (stdout stays valid JSON) and exits 1 if the deployment fails, so the job fails correctly.

Giving the CLI to an AI agent

  • Create a token with only the scopes the agent needs; for example vms:read, servers:read, servers:power, never *:delete unless it's meant to delete. Set an expiry.
  • Hand the agent the token as BITQATE_TOKEN and tell it to run bitqate schema for the exact command surface rather than guessing flags.
  • Prefer --json so output parses cleanly; agents should pass --yes for destructive actions.
  • A token only works against the /v1 API and only within its scopes. It can't change your password or email, open consoles or the file manager, or reach admin functions.

Token-origin binding

When you auth login, the CLI records the API URL with the profile. If anything later asks the CLI to send that token somewhere else (--api-url or BITQATE_API_URL pointing at a different origin), the command fails before making the request:

✗ The stored token for profile 'default' is bound to https://api.bitqate.com.

This stops a copied config or a prompt-injected agent from leaking your token to an arbitrary host. Use BITQATE_TOKEN when you genuinely want a different API.

Still stuck?

Our team can look at your specific setup.