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
--jsonprints 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,✗ messageplus 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
CIis set. Without a TTY, a missing required flag or argument is a plain usage error; nothing falls back to prompting. -qprints only resource IDs for list commands.
Exit codes
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*:deleteunless it's meant to delete. Set an expiry. - Hand the agent the token as
BITQATE_TOKENand tell it to runbitqate schemafor the exact command surface rather than guessing flags. - Prefer
--jsonso output parses cleanly; agents should pass--yesfor destructive actions. - A token only works against the
/v1API 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.