Automation
Everything you can do in the UI goes through the same HTTP API, so anything can be scripted. There are three ways to reach it, all sharing one auth model.
Authentication: tokens
Outside a browser session you authenticate with a personal access token (PAT) for yourself, or a service account token for automation.
- Create a PAT in the UI under your account settings. It’s bound to one user and one organization and shown once.
- For unattended automation, prefer a service account token — it isn’t tied to a person who might leave.
Tokens go in the Authorization header:
curl -H "Authorization: Bearer gantrycd_pat_…" \
https://gantrycd.example.com/api/v1/orgs/$ORG/stacks
The CLI: gantrycli
gantrycli is the command-line client. It covers the common objects:
gantrycli stacks list --org $ORG
gantrycli stacks sync --org $ORG --stack my-stack
gantrycli deployments create --org $ORG --stack my-stack --mode plan
gantrycli runner-groups list --org $ORG
gantrycli whoami
It reads its endpoint and token from configuration/environment so you don’t
repeat them. There’s also an operator subtree for host-level maintenance
(like promoting the first super-admin) that talks to the database directly — see
Install.
Most commands accept --output json for a machine-readable result instead of
the default text table:
gantrycli stacks list --org $ORG --output json | jq '.stacks[].id'
Every command and flag is listed in the CLI reference,
which is generated from the CLI itself (make docs_cli) so it can’t drift.
Exit codes
0 always means success. On failure, most commands use one shared mapping
derived from the error itself:
| Code | Meaning |
|---|---|
1 | Generic — anything not covered below |
4 | Auth — the request was unauthorized or forbidden |
5 | Not found |
6 | Invalid — bad input or a conflict with existing state |
deployments wait, deployments watch, deployments create --wait/--follow,
and local-plan --wait/--follow use a different, more specific scheme for the
deployment’s own terminal status, since that’s usually what a CI script
actually wants to branch on — a poll-time error (an expired token, a typo’d ID)
still uses the shared mapping above:
| Code | Outcome |
|---|---|
0 | Deployment finished |
1 | Deployment failed |
2 | Deployment cancelled |
3 | Deployment skipped |
Pass --log-format json (or set $GANTRYCD_LOG_FORMAT=json) to get the final
error as a structured JSON line on stderr instead of plain text, for log
pipelines that parse it.
The Go SDK
pkg/sdk is the official Go client. The CLI and the Terraform provider are both
built on it, so it’s the most complete surface.
client, err := sdk.New(sdk.Options{
BaseURL: "https://gantrycd.example.com",
Token: os.Getenv("GANTRYCD_PAT"),
})
if err != nil { /* ... */ }
stacks, err := client.Stacks.List(ctx, orgID)
The SDK is deliberately policy-free: it doesn’t read env vars or config files —
the caller supplies the base URL and token. Errors from the backend come back as
a typed *APIError; branch on them with sdk.IsNotFound, sdk.IsConflict,
sdk.IsForbidden, and friends rather than matching strings.
The Terraform provider
Because GantryCD’s own objects (stacks, runner groups, cloud integrations, …) are just API resources, there’s a Terraform provider for managing GantryCD itself as code — so your stacks, contexts, and runner groups can live in OpenTofu next to the infrastructure they deploy.