DEV Community

Cover image for Bruno CLI vs Apidog CLI: Run API Tests in CI
Hassann
Hassann

Posted on • Originally published at apidog.com

Bruno CLI vs Apidog CLI: Run API Tests in CI

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.

Try Apidog today

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 .bru files 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, binary bru) runs .bru files from your git repo. It is open source, offline, and does not require an account or token.
  • Apidog CLI (apidog-cli, binary apidog) 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:

  1. Run without a GUI.
  2. Exit non-zero when something fails.
  3. 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
Enter fullscreen mode Exit fullscreen mode

The binary is bru.

Run a collection from the directory containing your .bru files:

bru run --env staging
Enter fullscreen mode Exit fullscreen mode

Run recursively through subfolders:

bru run -r --env staging
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Where:

  • --access-token authenticates the CLI.
  • -t selects the test scenario.
  • -e selects the environment.
  • -n sets the iteration count.
  • -r selects 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:

  1. The report file.
  2. 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
Enter fullscreen mode Exit fullscreen mode

Use:

  • --reporter-junit for CI dashboard integration.
  • --reporter-html for a human-readable artifact.
  • --reporter-json for custom processing.

To stop on the first failure:

bru run -r --env staging --bail
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Apidog’s --on-error controls failure behavior during a scenario:

apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 \
  --on-error end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or:

apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 || true
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Key implementation details:

  • working-directory should 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
Enter fullscreen mode Exit fullscreen mode

Implementation checklist:

  1. Create or open the test scenario in Apidog.
  2. Go to the scenario’s CI/CD tab.
  3. Generate the CLI command.
  4. Store the token as a GitHub Actions secret.
  5. Use the scenario ID and environment ID in the workflow.
  6. 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
Enter fullscreen mode Exit fullscreen mode

Apidog uses:

apidog run --on-error end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For Apidog:

apidog run -r junit --out-dir ./apidog-reports
Enter fullscreen mode Exit fullscreen mode

Add HTML reports when you want a browsable artifact:

bru run -r \
  --reporter-junit ./results/junit.xml \
  --reporter-html ./results/report.html
Enter fullscreen mode Exit fullscreen mode
apidog run -r html,junit --out-dir ./apidog-reports
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Use npx for Apidog:

npx apidog-cli run --access-token $APIDOG_ACCESS_TOKEN -t 605067
Enter fullscreen mode Exit fullscreen mode

To verify available options for your installed version:

bru run --help
Enter fullscreen mode Exit fullscreen mode

bash
apidog run --help
Enter fullscreen mode Exit fullscreen mode

Top comments (0)