Connect the CLI to a server
Two environment variables point the CLI at a server. Nothing else about how you run Bravos changes: scans still run locally, the ledger is still written locally, and the gate still evaluates locally.
What you gain is one command, bravos push, that files a completed run so the
rest of your team can read it.
Before you start
Section titled “Before you start”- A server URL and a bearer token, from whoever
stood the deployment up.
The token needs the
memberrole to push. - A run on this machine with a ledger.
bravos resumelists what is available.
Point the CLI at it
Section titled “Point the CLI at it”-
Set both variables.
Terminal window export BRAVOS_SERVER_URL=http://127.0.0.1:8123export BRAVOS_TOKEN=bugb_NCyQ7A6ELltEtf_qLrREDMhSHiYpf4_O4alfsMSQFCcNo trailing slash is needed on the URL. The token still carries a
bugb_prefix: it is minted by the server, and the server kept its name. -
Give the repository a name to push under.
A run is filed against a repository key. Bravos takes it from
remote.origin.url, and a checkout with no remote has to be told:bravos: no git remote 'origin' in /Users/you/demo and no bravos.projectId set for this repository, so it has no name to push under. Set one: git config --local bravos.projectId <host/org/repo> (codebase='/Users/you/demo')Terminal window git config --local bravos.projectId github.com/acme/demo-shop -
Push a run.
Terminal window bravos pushpushed 20260910-004305-demo → github.com/acme/demo-shophttp://127.0.0.1:8123/v1/eventssent 5appended 5duplicates 0summary yes masterdashboard no no threat-dashboard.html in the codebaseadvisories no no confirmed findings in this runtemplates no no confirmed findings in this runEach line is an artifact the run had already written to disk. The three
norows carry their reason rather than being silent: this run was model tier, so it proved nothing and had nothing to promote.--run <id>names a run; without it, the latest for this repository. -
Push it again.
sent 5appended 0duplicates 5 already on the serverIngest is content-addressed, so a repeated push is a no-op. That is the designed answer to a retry, which means a pipeline can push as often as it likes and a failed step can be re-run without a second thought.
Pinning the endpoint per repository
Section titled “Pinning the endpoint per repository”Exporting BRAVOS_SERVER_URL follows every repository on the machine. A team
usually wants one checkout reporting to one server:
git config bravos.server.url https://bravos.example.com$BRAVOS_SERVER_URL wins over the git config when both are set, so CI can
override without editing anything in the checkout.
What the CLI does when something is wrong
Section titled “What the CLI does when something is wrong”None of these touch your local run. The ledger, the advisories, and the dashboard on this machine are already written and are unaffected.
Neither variable is set. The error names both, before any request is built,
and exits 2, because the request could not be carried out as asked:
bravos: no bravos server configured. Set BRAVOS_SERVER_URL=https://… or: git config bravos.server.url https://…; no bravos token. Set BRAVOS_TOKEN=<token>The server is unreachable. Exit 1:
bravos: could not reach http://127.0.0.1:9/v1/events: ConnectError (url='http://127.0.0.1:9/v1/events')The server’s licence has lapsed. This is the one worth reading in full,
because the fix is not yours. Exit 1:
bravos: this bugb-server deployment declined to record the run: its licence ismissing or lapsed (HTTP 402): license expired: acme's team plan expired at2026-06-03T00:00:00+00:00 and the grace window ended at2026-06-17T00:00:00+00:00. This is the server's licence, not your token or yourcheckout, so there is nothing to fix locally. Everything that runs on thismachine is unaffected — scans, the ledger, advisories, and `bravos ci` against acommitted registry set all keep working offline. What pauses is theserver-backed channel above. Whoever operates this deployment can restore it byrenewing or installing its licence. (status=402)Line breaks added for reading; the CLI prints it as one paragraph.
The token’s role is too weak. viewer can read but not push, and the server
answers 403:
{"detail":"role 'viewer' cannot push events; member or above is required"}HTTP 403Ask for a member token.
The gate does not need any of this
Section titled “The gate does not need any of this”bravos ci reads a ledger and a policy from your repository and exits with a
code. It contacts no server, and it needs neither variable:
env -u BRAVOS_SERVER_URL -u BRAVOS_TOKEN bravos ci PASS 20260910-004305-demo policy built-in default fail_on_severity high · max_confirmed none · fail_on confirmed
4 threats tracked · 0 counted · 0 breachesThat separation is the point. A build gate that depended on a server would be a build gate that a server outage turns red. See Gate a build on a run for the policy and the exit codes.
Reading it back
Section titled “Reading it back”Any valid token reads, and viewer is enough:
curl -s $BRAVOS_SERVER_URL/v1/repos -H "Authorization: Bearer $BRAVOS_TOKEN"The token also says who it thinks you are:
curl -s $BRAVOS_SERVER_URL/v1/me -H "Authorization: Bearer $BRAVOS_TOKEN"{ "tenant": "acme", "identity": "bob", "role": "member"}Every query is filtered by the token’s tenant before it touches a name, so
naming another tenant’s repository returns 404:
{"detail":"no such repository: 'github.com/other/secret'"}HTTP 404That is the same answer as a typo, deliberately, so the read routes cannot be used to enumerate what other tenants exist.
Related
Section titled “Related”- Run Bravos in CI/CD: the gate and the push as pipeline steps.
- The CLI, the server and CI: what the server is for.
- License a deployment: for whoever
operates the server when a
402appears.

