Your API tests passing locally is not enough. To make them useful, run them on every pull request, merge, and scheduled build without manual clicks. That is what a command-line runner does: it executes existing API tests headlessly in CI, returns a pass/fail exit code, and writes reports your pipeline can publish.
Two runners often come up when teams automate API testing: Bruno CLI and Apidog CLI.
They solve the same CI problem from different starting points:
-
Bruno is git-native, offline-first, and open source. Its CLI runs
.brufiles stored in your repository. - Apidog is an all-in-one API platform. Its CLI runs visual test scenarios created in the Apidog app.
Both work with GitHub Actions, GitLab CI, Jenkins, and any environment with Node.js. Both fail the build when a test fails. The main differences are how you author tests, how CI authenticates, and where the test definition lives.
This guide compares both runners at the command level so you can decide which one fits your pipeline.
TL;DR
-
Bruno CLI (
@usebruno/cli, binarybru) runs.brufiles from your git repo. It is open source, offline, and does not require an account or token. -
Apidog CLI (
apidog-cli, binaryapidog) runs test scenarios created in Apidog, fetched by ID using an access token. - Both support JUnit, JSON, and HTML reports.
- Both return a non-zero exit code when tests fail.
- Pick Bruno if you want plain-text tests stored directly in your repo.
- Pick Apidog if you want visual scenario authoring, request chaining, environment management, and data-driven runs without maintaining test code manually.
The real CI problem: tests that exist but never run
A test you only run manually will eventually rot. It may pass once, then sit unused while the API changes underneath it.
The fix is not just writing more tests. The fix is running tests automatically on every change.
A useful API test runner for CI needs to do three things:
- Run without a GUI.
- Exit non-zero when something fails.
- Write a machine-readable report.
Bruno and Apidog both meet those requirements. The difference is upstream: how the tests are created and where they are stored.
If you are setting up API test automation from scratch, also read automating API tests in CI/CD. This article focuses specifically on Bruno CLI vs Apidog CLI.
What Bruno CLI does well
Bruno is designed around git-native API testing.
Requests, environments, and assertions are stored as plain-text .bru files in your repository. That gives you a workflow similar to application code:
- Tests live next to the code they validate.
- Pull requests can update endpoints and tests in the same diff.
- Reviews happen in git.
- Files can be searched, refactored, and merged with standard developer tools.
- CI does not need to fetch test definitions from a remote service.
Bruno is also open source and offline-first. The CLI runs locally or in CI with no account, login, or access token.
That matters for teams with strict data-handling requirements, private networks, or air-gapped environments. Bruno Ultimate adds team features such as SSO, SCIM, secret-manager integrations, and audit capabilities, but the core CLI is free and self-contained.
Install Bruno CLI:
npm install -g @usebruno/cli
The binary is bru.
Run a collection from the directory containing your .bru files:
bru run --env staging
Run recursively through subfolders:
bru run -r --env staging
That is the main CI loop: the files in your repo are the tests, and the CLI executes them.
For more context on Bruno’s model, see what makes Bruno different as a git-native API client and where Bruno limitations can appear for larger teams.
What Apidog CLI does well
Apidog takes a different approach.
You create API test scenarios visually in the Apidog app:
- Chain multiple requests.
- Add assertions.
- Extract values from one response.
- Reuse those values in later requests.
- Run scenarios against environments.
- Execute data-driven runs with CSV or JSON files.
The CLI is the headless runner for those scenarios. It does not introduce a separate test file format. It runs the scenario you already built in Apidog.
Install Apidog CLI:
npm install -g apidog-cli
The binary is apidog.
A typical CI command looks like this:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 -n 1 -r html,junit
Where:
-
--access-tokenauthenticates the CLI. -
-tselects the test scenario. -
-eselects the environment. -
-nsets the iteration count. -
-rselects report formats.
You usually do not type those IDs manually. In Apidog, open the test scenario, go to the CI/CD tab, generate an access token, and copy the generated command. Then store the token as a CI secret and reference it as $APIDOG_ACCESS_TOKEN.
For the complete flag list, see the Apidog CLI complete guide.
The clearest difference is this:
- Bruno reads test files from disk.
- Apidog fetches a scenario from your Apidog project using a token.
Both models are valid. They fit different team workflows.
Side-by-side comparison
| Dimension | Bruno CLI (bru) |
Apidog CLI (apidog) |
|---|---|---|
| Package | @usebruno/cli |
apidog-cli |
| Run command | bru run |
apidog run |
| Test source |
.bru files in your git repo |
Test scenarios in your Apidog project, fetched by ID |
| Authoring | Hand-edit text files or use the Bruno app | Visual scenario builder in the Apidog app |
| Auth in CI | None; runs offline | Access token via --access-token
|
| Select what runs | Folder path, -r recursive, --tags
|
-t scenario, -f folder, --test-suite
|
| Environment | --env <name> |
-e <environmentId> |
| Data-driven runs |
--csv-file-path, --json-file-path
|
-d <path> for CSV or JSON |
| Iterations | --iteration-count <n> |
-n <n> |
| Reporters | JSON, JUnit, HTML |
cli, html, json, junit
|
| Fail-fast | --bail |
--on-error end; default fails on first error |
| Open source | Yes | No; free npm CLI that runs scenarios from your plan |
| License/account | None for the CLI | Apidog account required for the project |
The important takeaway: both tools cover the CI essentials. The decision is less about raw runner capability and more about where your test definitions should live.
Reports and exit codes
CI mostly cares about two things:
- The report file.
- The process exit code.
Bruno reports
Bruno writes reports with per-format flags:
bru run -r --env staging \
--reporter-junit ./results/junit.xml \
--reporter-html ./results/report.html \
--reporter-json ./results/report.json
Use:
-
--reporter-junitfor CI dashboard integration. -
--reporter-htmlfor a human-readable artifact. -
--reporter-jsonfor custom processing.
To stop on the first failure:
bru run -r --env staging --bail
Without --bail, Bruno continues running and reports all failures.
Apidog reports
Apidog uses a comma-separated reporter list and an output directory:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 \
-r html,junit \
--out-dir ./apidog-reports
Apidog’s --on-error controls failure behavior during a scenario:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 \
--on-error end
Options include:
-
end: stop at the first failure. -
continue: run remaining steps and collect all failures. -
ignore: skip past a known-flaky step.
In either tool, failed assertions produce a non-zero exit code. CI reads that code and fails the job.
Avoid this pattern:
bru run -r --env staging || true
Or:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 || true
That swallows the non-zero exit code and disables your quality gate.
Bruno CLI in GitHub Actions
Because Bruno tests are already in the repository, the workflow is straightforward:
name: API tests
on:
pull_request:
branches: [main]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Bruno CLI
run: npm install -g @usebruno/cli
- name: Run API tests
working-directory: ./api-tests
run: bru run -r --env staging --reporter-junit ./results/junit.xml
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: bruno-report
path: ./api-tests/results
Key implementation details:
-
working-directoryshould point to the folder containing the Bruno collection. -
if: always()uploads reports even when tests fail. - No secret is required because Bruno runs local files.
Apidog CLI in GitHub Actions
The Apidog workflow has the same structure, but it needs an access token and scenario ID:
name: API tests
on:
pull_request:
branches: [main]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Apidog CLI
run: npm install -g apidog-cli
- name: Run API test scenario
run: |
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t 605067 \
-e 1629989 \
-r html,junit \
--out-dir ./apidog-reports
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: apidog-report
path: ./apidog-reports
Implementation checklist:
- Create or open the test scenario in Apidog.
- Go to the scenario’s CI/CD tab.
- Generate the CLI command.
- Store the token as a GitHub Actions secret.
- Use the scenario ID and environment ID in the workflow.
- Upload the report directory with
if: always().
For GitLab CI and Jenkins examples, see the Apidog CLI complete guide.
How to choose
The decision usually comes down to how your team wants to author and store tests.
Choose Bruno when the repo is the source of truth
Bruno fits best when you want:
- API tests stored as plain-text files.
- Tests reviewed in the same pull request as code changes.
- No cloud dependency during CI execution.
- No account or token for the CLI.
- Offline or air-gapped execution.
- Open-source tooling.
Use Bruno if your team is comfortable treating API tests as code and maintaining .bru files directly.
Choose Apidog when visual authoring and integrated workflows matter
Apidog fits best when you want:
- Visual test scenario creation.
- Request chaining without hand-written scripts.
- Variable extraction between steps.
- Environment management.
- Data-driven test runs.
- The same scenario used for local debugging and CI execution.
Use Apidog if you want to reduce manual test-code maintenance and keep design, debugging, mocking, and testing in one workspace.
For teams coming from Postman, Apidog also provides a familiar workflow. See this guide to Postman alternatives for API testing.
Can you use both?
Yes.
Some teams use:
- Bruno for git-native, low-level request checks.
- Apidog for larger chained scenarios, environment-heavy tests, and regression workflows.
They can run as separate CI steps. Each CLI returns its own exit code, so either one can fail the pipeline.
If you are comparing the platforms beyond CLI usage, read the full Apidog vs Bruno comparison.
To set up an automated Apidog run, create a scenario, open its CI/CD tab, copy the generated command, and store the access token as a CI secret.
Frequently asked questions
Is the Bruno CLI free?
Yes. Bruno CLI is open source and ships as the @usebruno/cli npm package. It runs locally or in CI without an account or token.
Bruno Ultimate is a separate paid tier with team and governance features such as SSO, SCIM, secret-manager integrations, and audit capabilities.
Is the Apidog CLI free?
The CLI is a free npm package named apidog-cli.
It runs test scenarios from your Apidog project, so what you can run depends on your Apidog plan. The command-line runner itself is not a separate paid product.
Do I have to write tests as code?
With Bruno, tests are .bru files. You can edit them directly or create them in the Bruno app.
With Apidog, you build scenarios visually in the app and run them by ID from the CLI. You do not maintain separate test code manually.
Do both runners fail the build when a test fails?
Yes.
Both return a non-zero exit code when an assertion fails. CI uses that exit code to fail the step and block the merge or deployment.
Bruno can stop on the first failure with:
bru run --bail
Apidog uses:
apidog run --on-error end
end is the default Apidog behavior.
Which report format should I use in CI?
Use JUnit XML for CI dashboards.
For Bruno:
bru run -r --reporter-junit ./results/junit.xml
For Apidog:
apidog run -r junit --out-dir ./apidog-reports
Add HTML reports when you want a browsable artifact:
bru run -r \
--reporter-junit ./results/junit.xml \
--reporter-html ./results/report.html
apidog run -r html,junit --out-dir ./apidog-reports
Both tools also support JSON for custom processing.
Does Bruno CLI need an internet connection?
No.
Bruno runs .bru files from your repository locally. It does not need a login, account, token, or remote fetch.
Apidog CLI authenticates with an access token and fetches the scenario from your Apidog project, so it needs network access to Apidog at runtime.
Can I run either CLI without a global install?
Yes.
Use npx for Bruno:
npx @usebruno/cli run -r --env staging
Use npx for Apidog:
npx apidog-cli run --access-token $APIDOG_ACCESS_TOKEN -t 605067
To verify available options for your installed version:
bru run --help
bash
apidog run --help
Top comments (0)