Connect the CLI Through an Access Gateway
Use this setup when the public Synth URL is protected by Cloudflare Access or
another gateway that requires extra HTTP headers. The CLI stays gateway-agnostic:
you provide the headers required by your deployment through
SYNTH_HTTP_HEADERS or the repeatable -H/--header option.
This feature requires synthkb-cli 0.5.2 or newer:
pipx upgrade synthkb-cli
synth --version
The version output should be synth, version 0.5.2 or newer.
How authentication is split
The gateway credential and the Synth API key have different jobs:
| Layer | Credential | How the CLI sends it | Saved by the CLI? |
|---|---|---|---|
| Access gateway | Gateway-specific headers such as CF-Access-Client-Id and CF-Access-Client-Secret | SYNTH_HTTP_HEADERS or -H/--header | No |
| Synth | A knowledge:read Synth API key | Authorization: Bearer ... | Yes, in the OS keychain or private credential file |
Do not add Authorization to the custom header map. The CLI owns that header
and rejects attempts to override it.
Recommended setup
Set one JSON object for the current shell, then use the CLI normally. These values are placeholders; load real values from your secret manager instead of putting them in a checked-in script:
export SYNTH_HTTP_HEADERS='{"CF-Access-Client-Id":"replace-with-client-id","CF-Access-Client-Secret":"replace-with-client-secret"}'
synth login https://synth.example.com/
synth projects
synth use PROJECT_NAME_OR_ID
synth status
synth search "test query"
At the hidden API key prompt, paste only the raw Synth API key. Do not add a
Bearer prefix. The CLI normalizes the public server URL to its MCP endpoint,
sends the gateway headers to readiness and MCP requests, and sends the Synth
key separately.
SYNTH_HTTP_HEADERS applies to every synth process started from that shell.
The CLI deliberately does not copy gateway secrets into its saved connection,
so load the variable again in every new shell or CI job.
When finished, remove the gateway values from the shell environment:
unset SYNTH_HTTP_HEADERS
Build the JSON from Bash variables
If a secret manager exports the two Cloudflare values, jq can construct valid
JSON without relying on manual quoting:
export CF_ACCESS_CLIENT_ID='replace-with-client-id'
export CF_ACCESS_CLIENT_SECRET='replace-with-client-secret'
export SYNTH_HTTP_HEADERS="$(
jq -cn \
--arg client_id "$CF_ACCESS_CLIENT_ID" \
--arg client_secret "$CF_ACCESS_CLIENT_SECRET" \
'{"CF-Access-Client-Id":$client_id,"CF-Access-Client-Secret":$client_secret}'
)"
unset CF_ACCESS_CLIENT_ID CF_ACCESS_CLIENT_SECRET
PowerShell
PowerShell can create the JSON without manual escaping:
$env:CF_ACCESS_CLIENT_ID = 'replace-with-client-id'
$env:CF_ACCESS_CLIENT_SECRET = 'replace-with-client-secret'
$env:SYNTH_HTTP_HEADERS = (@{
'CF-Access-Client-Id' = $env:CF_ACCESS_CLIENT_ID
'CF-Access-Client-Secret' = $env:CF_ACCESS_CLIENT_SECRET
} | ConvertTo-Json -Compress)
Remove-Item Env:CF_ACCESS_CLIENT_ID, Env:CF_ACCESS_CLIENT_SECRET
synth login https://synth.example.com/
synth projects
Remove the header map when finished:
Remove-Item Env:SYNTH_HTTP_HEADERS
One-off headers
For a single command, put repeatable -H/--header options before the command:
synth \
-H "CF-Access-Client-Id:$CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret:$CF_ACCESS_CLIENT_SECRET" \
doctor
This form affects only that invocation. It does not persist the headers for the
next command, so repeat the options or use SYNTH_HTTP_HEADERS. Prefer the
environment form for secrets because command arguments can be visible in shell
history and process inspection.
CI example
Store the Synth key and gateway values as separate CI secrets. This GitHub Actions example creates the JSON for the job without putting secret values in the command arguments:
- name: Query Synth through the access gateway
shell: bash
env:
SYNTH_API_KEY: ${{ secrets.SYNTH_API_KEY }}
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
run: |
set -euo pipefail
umask 077
printf '%s' "$SYNTH_API_KEY" > "${RUNNER_TEMP}/synth-api-key"
export SYNTH_HTTP_HEADERS="$(
jq -cn \
--arg client_id "$CF_ACCESS_CLIENT_ID" \
--arg client_secret "$CF_ACCESS_CLIENT_SECRET" \
'{"CF-Access-Client-Id":$client_id,"CF-Access-Client-Secret":$client_secret}'
)"
export SYNTH_CONFIG_DIR="${RUNNER_TEMP}/synth-cli"
synth login https://synth.example.com/ \
--api-key-file "${RUNNER_TEMP}/synth-api-key" \
--project PROJECT_NAME_OR_ID \
--credential-store file \
--non-interactive \
--json
synth status --json
See CLI Automation and CI/CD for cleanup, credential storage, JSON parsing, and retry guidance.
Security behavior
- Custom headers are never written to the saved Synth connection.
- Header values and the Synth API key are redacted from diagnostics and remote error messages.
Authorization,Host, request IDs, and content-negotiation headers owned by the CLI cannot be overridden.- Use a secret manager and short-lived gateway credentials where possible.
- Do not send real values in screenshots, shell transcripts, support tickets, or checked-in environment files.
Troubleshooting
Run synth doctor --json with the header environment still loaded.
| Symptom | Likely cause and action |
|---|---|
Cloudflare login HTML, gateway 403, or an access-denied page | The gateway headers are missing, expired, or rejected. Reload SYNTH_HTTP_HEADERS and verify the service token policy. |
| Login works, but the next command fails at the gateway | -H/--header was used only for login. Repeat it for every invocation or switch to SYNTH_HTTP_HEADERS. |
| Gateway passes the request, but Synth reports authentication failure | Replace or reauthorize the Synth API key. Gateway credentials do not replace the Synth key. |
SYNTH_HTTP_HEADERS must be a JSON object | Validate the environment value as JSON and ensure every header name and value is a string. |
Additional HTTP headers cannot override ... | Remove reserved headers such as Authorization or Host; let the CLI create them. |
No such option: -H | Upgrade to synthkb-cli 0.5.2 or newer, and confirm the selected executable with command -v synth or Get-Command synth. |
For all commands and transport options, see the Synth CLI reference.