Skip to main content

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.

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:

  1. Reads the key without placing it in the command line.
  2. Contacts the server and verifies the key.
  3. Confirms that prj_product_docs is authorized and unambiguous.
  4. Saves the active server and stable Project ID.
  5. Stores the key in Synth's private mode-0600 credential 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.

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:

ValueBehaviorRecommended use
autoTry the OS keychain, then fall back to a private fileDeveloper laptops
keyringRequire the OS keychain; fail if unavailableManaged desktops with a configured keychain
fileUse Synth's mode-0600 credential file directlyCI, 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:

PlatformDefault 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 use file.
  • 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.