CLI Automation and CI/CD
Synth can be configured entirely through CLI arguments. CI jobs never need to answer an interactive prompt. An optional environment variable can provide additional HTTP headers when an access gateway protects the Synth server.
Recommended Headless Login
Give the job a knowledge:read API key through a mounted secret file, then run:
synth login https://synth.example.com \
--api-key-file /run/secrets/synth-api-key \
--project prj_product_docs \
--credential-store file \
--non-interactive \
--json
This one command:
- Reads the key without placing it in the command line.
- Contacts the server and verifies the key.
- Confirms that
prj_product_docsis authorized and unambiguous. - Saves the active server and stable Project ID.
- Stores the key in Synth's private mode-
0600credential file.
Nothing is saved if server verification or Project selection fails.
If the server requires additional gateway headers, export one generic JSON object for the whole job:
export SYNTH_HTTP_HEADERS='{"X-Gateway-Client":"client","X-Gateway-Secret":"secret"}'
The CLI sends these headers to readiness and MCP requests without persisting or
printing them. Alternatively, place repeatable -H "Name: Value" options
before an individual command. Prefer the environment form for secret values so
they do not appear in process arguments.
For Cloudflare Access, Bash and PowerShell setup, and gateway-specific troubleshooting, see CLI Access Gateways.
Subsequent steps need no repeated server, key, or Project arguments. They still contact the saved Synth server:
synth status --json
synth search "release rollback procedure" --top-k 5 --json
synth page PAGE_ID --json
This reads the page's current state. For consequential agent workflows, search
also returns a page revision and Project snapshot_id. They can be supplied
as optional consistency guards:
synth page PAGE_ID \
--revision PAGE_REVISION \
--snapshot SNAPSHOT_ID \
--json
These values do not select old versions. They make the command fail if the page or Project changed between search and read, so the automation can search again.
Non-Interactive Key Inputs
Choose exactly one key input.
Secret file — recommended
synth login https://synth.example.com \
--api-key-file /run/secrets/synth-api-key \
--project prj_docs \
--credential-store file \
--non-interactive
Use this with Docker secrets, Kubernetes Secret volumes, CI temporary files, or any secret manager that materializes a file.
Standard input
Pipe a secret-manager command directly into login:
vault kv get -field=token secret/synth/ci | \
synth login https://synth.example.com \
--api-key-stdin \
--project prj_docs \
--credential-store file \
--non-interactive \
--json
--api-key-stdin consumes stdin as the key and does not print it.
Command-line value
synth login https://synth.example.com \
--api-key synth_sk_REDACTED \
--project prj_docs \
--non-interactive
This is supported but not recommended for CI: command arguments may appear in shell history, workflow logs, or process inspection. Prefer a file or stdin.
The three inputs are mutually exclusive. Passing more than one fails before any network request.
Why --non-interactive Matters
Without a key input, normal synth login opens a hidden prompt for a human.
Automation should always add --non-interactive. If the secret is missing,
Synth exits non-zero with an actionable error instead of waiting for input:
Non-interactive login requires --api-key-file or --api-key-stdin.
An empty key file or empty stdin also fails before the server call.
Credential Storage In Headless Runners
--credential-store controls where the verified key is persisted:
| Value | Behavior | Recommended use |
|---|---|---|
auto | Try the OS keychain, then fall back to a private file | Developer laptops |
keyring | Require the OS keychain; fail if unavailable | Managed desktops with a configured keychain |
file | Use Synth's mode-0600 credential file directly | CI, containers, headless Linux |
Headless jobs should normally choose --credential-store file. This avoids
desktop keychain discovery and makes failure behavior consistent.
Set SYNTH_CONFIG_DIR to an isolated workspace when concurrent jobs need
different connections. Without that override, the CLI uses the platform-native
configuration directory:
| Platform | Default directory |
|---|---|
| Linux | $XDG_CONFIG_HOME/synth, or ~/.config/synth |
| macOS | ~/Library/Application Support/Synth |
| Windows | %APPDATA%\Synth |
The non-secret profile is remote-cli.json. File-backed credentials are kept
separately in remote-cli-credentials.json with private permissions. Both are
local to the runner user; one configuration directory has one active
connection.
Complete CI Job Pattern
set -euo pipefail
export SYNTH_CONFIG_DIR="$RUNNER_TEMP/synth-cli"
synth login https://synth.example.com \
--api-key-file /run/secrets/synth-api-key \
--project prj_product_docs \
--credential-store file \
--non-interactive \
--json
synth connection --json
synth status --json
synth search "deployment safety checks" --top-k 8 --json > synth-search.json
synth logout --json
logout removes Synth's saved connection and saved copy of the key. It does
not revoke the server-side API key and does not delete the original mounted
secret file.
Use a shell cleanup trap when later steps may fail:
trap 'synth logout --json >/dev/null 2>&1 || true' EXIT
GitHub Actions Example
Store the key as the repository or organization secret SYNTH_API_KEY. Install
the pinned CLI version before using it:
- name: Install Synth CLI
run: pipx install 'synthkb-cli==X.Y.Z' # Pin the version approved by your team.
- name: Write temporary Synth key
shell: bash
run: |
umask 077
printf '%s' '${{ secrets.SYNTH_API_KEY }}' > '${{ runner.temp }}/synth-api-key'
- name: Query Synth
shell: bash
run: |
set -euo pipefail
export SYNTH_CONFIG_DIR='${{ runner.temp }}/synth-cli'
trap 'synth logout --json >/dev/null 2>&1 || true' EXIT
synth login https://synth.example.com \
--api-key-file '${{ runner.temp }}/synth-api-key' \
--project prj_product_docs \
--credential-store file \
--non-interactive \
--json
synth status --json
synth search "release requirements" --top-k 8 --json > synth-search.json
The Synth commands use no endpoint, key, or Project environment variables. The workflow secret is materialized as a temporary mode-private file and never passed as a process argument.
Parse JSON Without Parsing Prose
Every data command returns the same versioned success envelope. For example:
snapshot_id=$(synth search "release requirements" --top-k 5 --json |
jq -er '.data.snapshot_id')
synth search "release requirements" --top-k 5 --json |
jq -er '.data.results[] | {page_id: .page.page_id, revision: .page.revision}'
On failure, stdout stays empty, the error envelope goes to stderr, and the process uses the documented non-zero exit code. Capture the streams separately when a job needs both evidence and diagnostics.
Retry Policy
Retry only network failures (exit 6) and explicitly unready deployments. Do not blindly retry authentication, Project-selection, or revision-conflict failures:
- exit 3: replace or reauthorize the key;
- exit 4: correct Project access or the stable Project ID;
- exit 5: search again, inspect the new snapshot/revision, then reread;
- exit 6: use bounded exponential backoff and retain the request ID.
The CLI itself does not hide retries because callers must decide whether stale or repeated reads are acceptable.
Docker And Kubernetes Secrets
Docker Compose secret mount:
services:
agent:
secrets:
- synth_api_key
secrets:
synth_api_key:
file: ./secrets/synth-api-key
The container command can then use:
synth login https://synth.example.com \
--api-key-file /run/secrets/synth_api_key \
--project prj_docs \
--credential-store file \
--non-interactive
For Kubernetes, mount the Secret as a read-only volume and pass the mounted
file path to --api-key-file. Avoid putting the key directly in the Pod command
or arguments.
Failure And Safety Contract
- Invalid server or key: non-zero exit; no new connection is saved.
- Unknown or ambiguous Project: non-zero exit; no new connection is saved.
- Missing input with
--non-interactive: non-zero exit; no prompt. - Multiple key inputs: non-zero exit before the network request.
- Unavailable explicit
keyring: non-zero exit with guidance to usefile. - Raw keys never appear in JSON, human output, or the connection profile.
- Remote HTTP is rejected; only localhost development may use plain HTTP.
See the CLI Reference for every command and option, and API Keys for scopes and Project restrictions.