View all services
Talk to QA Advisor
Browse the Knowledge Hub211 resources
Tools Library · Review and guide

Bruno review: pricing, pros and cons

Bruno is an open source, offline-first API client that stores collections as plain text files in a folder you choose, usually inside your Git repository, with no account or vendor cloud. New collections use OpenCollection YAML; the older .bru format is still supported. Bruno 4.0 shipped on 23 July 2026 with breaking changes to secrets and environment scripting.

QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.

last reviewed
13 October 2026
version covered
Bruno 4
licence
Open source

Reviewed by Viral Patel

Co-Founder, QAbleLast reviewed 13 October 2026

At a glance

Bruno at a glance

Official site
Vendor
Bruno
Category
API testing
Licence
Open source (MIT, with paid Pro and Ultimate editions)
Platforms
Windows, macOS, Linux
First released
2023
Version reviewed
Bruno 4

Verdict

Our verdict on Bruno

Strengths

  • Collections are YAML or .bru files in your repository, so every change is a readable diff in review
  • Works offline with no account; nothing is synced to a vendor cloud
  • bru run executes the same files in CI, with JSON, JUnit and HTML reports
  • Declarative assertions and Chai-based test scripts cover simple and complex checks
  • Imports Postman, Insomnia, OpenAPI and WSDL, which makes trial migrations cheap
  • Core client and CLI are MIT licensed

Weaknesses

  • Version 4 moved secret configuration and made bru.setEnvVar persist values to disk
  • The CLI runs scripts in Safe Mode by default, blocking npm packages and filesystem access until changed
  • Private Git provider integration and some team features in the app need a paid plan
  • A collection saved by version 4 may not open correctly for teammates on older versions

Our take

Bruno solves the problem that makes cloud-first API clients awkward for engineering teams: API requests and tests become ordinary files that live with the code, change in pull requests and need no account to open. Its CLI runs the same files in CI with JUnit output, so the path from exploration to a pipeline gate is short. QAble has evaluated it rather than maintained a long-running suite on it, so treat this as a bounded take. Expect friction at the edges: version 4 changed where secrets are configured and made bru.setEnvVar write to disk, the CLI defaults to Safe Mode so scripts that need npm packages or the filesystem fail until you opt in, and some Git and governance features in the app are paid.

We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.

How it works

How Bruno works

A collection is a folder of files

Bruno has no database or cloud workspace. A collection is a directory: opencollection.yml at the root for YAML collections, or bruno.json and collection.bru for the older format, with one file per request, a folder.yml or folder.bru per folder and an environments folder. The app reads and writes those files directly, so Git, your editor and code review see exactly what Bruno sees. Collaboration is whatever your repository already does: branches, pull requests and code owners. That design is the whole point of the tool, and it explains its limits too, since anything that needs a server, such as shared live workspaces, sits outside the open source core.

OpenCollection YAML and the Bru language

Bruno started with its own markup, Bru lang, in .bru files. From 3.0 it can save requests as YAML following OpenCollection, an open specification Bruno created for executable API collections, and from 3.1 new and imported collections use YAML by default. Each request file holds info, http, runtime and settings sections; scripts, tests and declarative assertions sit under runtime. Version 4.1 added a migration from .bru to YAML in the app, which removes the original .bru files after converting, so take the backup the dialog offers. Both formats remain supported, and the CLI runs either.

Scripts, tests and the sandbox

Each request can have a pre-request script, a post-response script and tests, written in JavaScript against Bruno’s bru and res objects, with Chai assertions. Declarative assertions handle simple checks without code, such as res.status eq 201. Scripts run in one of two modes. Safe Mode, the default in the app and in the CLI since CLI 3.0, runs code in a sandbox with no filesystem, system commands or external npm packages. Developer Mode lifts those restrictions for collections you trust and is selected per collection in the app or with --sandbox=developer on the CLI.

Variables and secrets

Variables resolve from runtime, request, folder, collection, environment and global scopes, referenced with double braces. Environments are files in the collection, so secrets must not be stored in them in plain text. Bruno offers several routes: variables marked secret, which are not written to disk and must be passed to the CLI with --env-var; a .env file at the collection root read through process.env; and external secret managers such as AWS Secrets Manager and Azure Key Vault. In version 4, external secret configuration moved from secrets.json into environment files, and bru.setEnvVar now writes values to the environment file, so a script that stores a token that way can end up committing it.

Strengths

What it does well

Reviewable API tests

Because each request is a small text file, a change to a URL, header or assertion appears as a focused diff. Teams can apply the same review and branch rules to API tests as to code, and a feature branch can carry its API checks with it.

CLI built for pipelines

bru run executes a collection, folder or list of files with an environment, environment overrides, tag filters, CSV or JSON iteration data and optional parallel execution. It writes JSON, JUnit and HTML reports, and can skip headers or bodies in reports to keep secrets out of artefacts.

Several protocols in one client

Requests can be REST, GraphQL, gRPC, WebSocket or SOAP, with authentication helpers for Basic, Bearer, Digest, AWS Signature, NTLM, OAuth 1.0 and OAuth 2.0. For most product teams that covers every API they call from one collection.

Import from other clients

Converters import Postman collections and environments, Insomnia exports, OpenAPI specifications and WSDL files. A team can bring an existing Postman collection into a repository in one step and compare behaviour before deciding to switch.

Data-driven runs

The CLI iterates a collection over a CSV or JSON file, one run per row, with each column available as a variable, and can repeat runs with --iteration-count. That covers input matrices and permission checks without duplicating request files.

Pricing

Bruno pricing and licensing

Bruno’s client and CLI are open source under the MIT licence and free, with no account needed. Paid Pro and Ultimate editions are licensed per user and billed annually, adding private Git provider integration in the app, unlimited workspaces and OpenAPI syncs, support and, on Ultimate, enterprise administration. List prices on usebruno.com on 13 October 2026 were $6 per user a month for Pro and $11 for Ultimate, billed annually.

  • Licence: MIT for the open source edition, stated in the repository. No licence management is needed to use it.
  • Open source edition: unlimited collection runs, two workspaces, Git provider integration for public repositories in the app and five OpenAPI syncs a month, with community support.
  • Pro: per user, unlimited workspaces, public and private Git providers in the app, unlimited OpenAPI syncs and email and chat support.
  • Ultimate: per user, adding further administration, payment by purchase order and invoice, custom terms and faster support.
  • Hidden costs: none from a vendor cloud, since files live in your repository; the real cost is agreeing secret handling and keeping everyone on compatible versions.

Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.

Getting started

Bruno tutorial: getting started

Setup, step by step

  1. Install the Bruno app for Windows, macOS or Linux, and Node.js 18 or later for the CLI.
  2. Create a collection inside your repository, or import a Postman or OpenAPI file.
  3. Add an environment file with non-secret values and mark tokens as secret variables.
  4. Add a request with assertions or a tests script, and run it in the app.
  5. Install the CLI and run bru run --env with the environment name from the collection root.
  6. Commit the collection folder, excluding .env files, and review it like code.

Install

npm install -g @usebruno/cli
# run from the collection root, the folder with opencollection.yml or bruno.json
cd api-tests
bru run --env local

A first example

# api-tests/orders/create-order.yml
info:
  name: Create order
  type: http
  seq: 1

http:
  method: POST
  url: "{{baseUrl}}/orders"
  body:
    type: json
    data: |-
      { "sku": "ABC-1", "quantity": 2 }

runtime:
  # Declarative checks, no code needed
  assertions:
    - expression: res.status
      operator: eq
      value: "201"
  scripts:
    - type: tests
      code: |-
        test("returns a pending order", function () {
          expect(res.body.status).to.equal("PENDING");
          expect(res.body.lines).to.have.lengthOf(1);
        });
    - type: after-response
      code: |-
        // In memory for this run only, so it is never written to disk
        bru.setVar("orderId", res.body.id);

settings:
  encodeUrl: true

What trips people up first

Scripts that work in the app and fail in the CLI. Since CLI 3.0, bru run uses Safe Mode, so a script that requires an npm package, reads a file or uses Node built-ins fails until you pass --sandbox=developer, and only collections you trust should get that. The second hurdle is secrets: variables marked secret are not saved to disk, so the CLI does not see them and requests go out with empty tokens. Pass them with --env-var, or reference process.env values from a .env file at the collection root that is ignored by Git.

In practice

Using Bruno in practice

Repository layout and environments

Keep the collection in the repository of the service it tests, in its own folder, and run the CLI from that folder, since bru run expects the collection root as its working directory. Organise requests into folders by resource or journey and use folder-level settings for shared headers and auth. Commit environment files for local, staging and CI with non-secret values, and reference secrets through process.env so the same files work locally with a .env file and in CI with injected variables. Add .env to .gitignore before the first commit, and agree on one format for the collection, YAML for new work, since the migration guide says a collection must use one format.

# api-tests/environments/staging.yml
name: staging
variables:
  - name: baseUrl
    value: https://staging.example.com
  - name: apiToken
    # Read from api-tests/.env locally, or from the CI environment
    value: '{{process.env.API_TOKEN}}'

Running collections in GitHub Actions

Bruno publishes an official action, usebruno/bruno-cli-action, which installs the CLI, prepends bru to the command you give it, adds a JUnit reporter if you did not, and exposes passed, failed and total counts as step outputs. Point working-directory at the collection root, select the environment and pass secrets through the job environment so process.env references resolve. Tag smoke requests and run them on every pull request, with the full collection on merges or a schedule. If scripts need npm packages, add --sandbox=developer to the command and install the collection’s dependencies first.

# .github/workflows/bruno.yml
name: api-tests
on: [push, pull_request]
jobs:
  bruno:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: usebruno/bruno-cli-action@v1
        with:
          working-directory: api-tests
          # The action adds the bru prefix
          command: >-
            run --env staging --tags=smoke
            --reporter-junit results/junit.xml
            --reporter-html results/report.html
        env:
          API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: bruno-results
          path: api-tests/results/

Upgrading to Bruno 4

Read the v4 release notes before anyone upgrades, because a collection saved by version 4 can break for teammates on older versions. If you use external secret managers, open each collection in the v4 app so secrets.json is migrated into environment files before the CLI runs it, and replace {{$secrets.name.key}} references with the new {{name.key}} form within the three-month deprecation window. Search scripts for bru.setEnvVar, bru.deleteEnvVar and bru.setGlobalEnvVar: these now write to disk, so switch anything holding a token to bru.setVar. Finally, if test management tools read the JUnit classname as a URL, update them, since v4 uses the collection path instead.

Keeping collections maintainable

Text files make maintenance visible, which helps only if the team keeps them tidy. Put shared authentication on the collection or folder rather than repeating headers in every request. Keep scripts short, and move logic shared by many requests into folder or collection scripts, which Bruno runs around each request in a defined order. Prefer declarative assertions for status and simple fields, which read clearly in diffs, and tests scripts for logic. Use tags for smoke and regression subsets, and skip headers or bodies in published reports with the reporter options so tokens and personal data do not leak into CI artefacts.

Pitfalls

Common pitfalls

Secret variables missing in CLI runs

Why it happens: Variables marked secret in the app are deliberately not persisted to disk, so the CLI cannot read them and sends empty values.

The fix: Pass them with --env-var name=value, or reference process.env values loaded from a .env file locally and from CI secrets in the pipeline.

Scripts failing in CI that pass in the app

Why it happens: Since CLI 3.0 the default runtime is Safe Mode, which blocks external npm packages, filesystem access and system commands.

The fix: Add --sandbox=developer for collections you trust, and check bru.isSafeMode() in scripts that need Developer Mode so failures explain themselves.

Tokens committed after upgrading to v4

Why it happens: In version 4, bru.setEnvVar, bru.deleteEnvVar and bru.setGlobalEnvVar write to the environment file, so a token stored that way lands in Git.

The fix: Use bru.setVar and bru.deleteVar for sensitive run-time values, which stay in memory, and review environment file diffs before committing.

Running bru run from the wrong directory

Why it happens: The CLI expects to run from the collection root, the folder with opencollection.yml or bruno.json, and resolves file and folder arguments relative to it.

The fix: cd into the collection root in scripts, or set working-directory in the GitHub Action, and pass paths relative to that folder.

Mixed Bruno versions corrupting a shared collection

Why it happens: Version 4 stores new metadata such as variable types, descriptions and multiple WebSocket messages. Older versions may ignore it or fail to load affected requests.

The fix: Upgrade the whole team together, pin the CLI version in CI with bru-version in the action, and keep a tagged commit from before the upgrade.

Following tutorials that use {{$secrets...}} references

Why it happens: Version 4 deprecated the $secrets reference form in favour of {{name.key}}, and the release notes say the old form will be removed after three months.

The fix: Migrate secret manager configuration in the v4 app, then update references to the new form across the collection.

Compared

How Bruno compares

ToolWhere collections liveFree plan for a teamCI reportsLicenceProtocols
BrunoPlain files in your repositoryYes, through your own GitJSON, JUnit and HTML from bru runMIT, paid Pro and UltimateREST, GraphQL, gRPC, WebSocket, SOAP
PostmanPostman cloud, or v3 YAML in GitNo: Free and Solo are single-userJUnit for v2.1 collections onlyProprietary, freemiumREST, GraphQL, gRPC, WebSocket and more
InsomniaLocal, encrypted cloud or Git SyncGit Sync for up to 3 usersConsole reporters and JSON outputApache 2.0 app, paid plansREST, GraphQL, gRPC, WebSocket, SSE

Head-to-head comparisons

Bruno vs Postman

FAQ

Frequently asked questions

Is Bruno free?

Yes. The Bruno app and CLI are open source under the MIT licence and free, with no account or licence management needed. Paid Pro and Ultimate editions, licensed per user and billed annually, add private Git provider integration in the app, unlimited workspaces and OpenAPI syncs, support and enterprise administration. Running collections, locally or in CI, is unlimited on every edition.

What changed in Bruno 4?

Bruno 4.0, released on 23 July 2026, moved external secret configuration from secrets.json into environment files with a new reference form, made bru.setEnvVar and related calls persist values to disk, added descriptions and types for variables and multiple WebSocket messages, and changed the JUnit classname from the request URL to the collection path. It also added AI features and app support. Collections saved in v4 may not load fully in older versions.

Bruno or Postman?

Choose Bruno when API requests and tests should live in your repository, be reviewed like code and work without an account or vendor cloud, and when a free tool matters for a large team. Choose Postman when the team relies on its cloud workspaces, monitors, mocks and integrations, or shares collections with people outside engineering. Bruno imports Postman collections, so you can test the switch on a real collection first.

Should I use .bru files or YAML?

Use OpenCollection YAML for new collections, which has been the default since Bruno 3.1 and is readable by any YAML tooling. Existing .bru collections still work in the app and CLI. When you are ready, version 4.1 and later can migrate a collection to YAML from the app; export a backup first, because the original .bru files are removed, and update any bru.runRequest paths that end in .bru.

How do I run Bruno tests in CI?

Install the CLI with npm install -g @usebruno/cli, or use the usebruno/cli Docker image or the official usebruno/bruno-cli-action for GitHub Actions. Run bru run from the collection root with --env and any --env-var overrides, add --reporter-junit for CI test reports, and filter with --tags. Pass --sandbox=developer only if scripts need npm packages or the filesystem.

Is Bruno ready for team use?

For teams that already collaborate through Git, yes: the collection is files, so branching, review and history come from the repository. What the open source edition lacks is server-side collaboration such as shared workspaces and private Git provider integration inside the app, which are paid. Agree on a secrets approach and upgrade everyone together, since version differences can affect shared collections.

Alternatives

Bruno alternatives worth weighing

Resources

Where to go next

Ready

Picking the wrong tool costs more than picking late.

QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.

Talk to QA Advisor