An honest comparison

Bruno is very good. Here's the honest case for a Bruno alternative.

Bruno is free, MIT-licensed, offline, and stores collections as plain files in your repository. Those are the right defaults, we agree with all of them, and if you want a fast request client on plain text you can stop reading and go download it. Bandura takes the same starting position and answers a different question: what if the file described a flow, with branching, loops, parallel fan-out and subflows, and a graph canvas and a code editor were two ways of editing that one file? That is what the licence is for, and this page is going to be specific about when it isn't worth it.

v1.0.0-rc.8 is out now on Windows and Linux. v1.0.0 final, and signed macOS builds, in August 2026.

Download for Windows or Linux
or

One email when the signed macOS build and 1.0.0 ship, no reselling, opt out any time.

Windows · Linux · macOS when notarized  ·  Why pay when Bruno is free? ↓  ·  What we agree on ↓  ·  When to stay ↓

Settled arguments

Four things Bruno already got right.

These are not concessions we're making reluctantly. They are the reason Bruno is the comparison worth writing, and none of them is a differentiator for anybody.

Collections belong in the repo

Both tools store tests as plain text next to the code they test, both diff in a pull request, and both make git clone the sharing mechanism. Bruno made this argument mainstream. We are downstream of it.

No account, no cloud sync

Neither tool needs you to sign in to open your own files, and neither ships your requests to a vendor. If that is your requirement, both tools pass and the question is which one fits the work.

MIT is a real advantage

Bruno's source is on GitHub under MIT: auditable, forkable, and immune to us having a bad year. Bandura is commercially licensed and does not match that. The counterweight is that the .aether format is documented YAML, so leaving costs you a cp -r rather than an export tool.

Free beats paid when free does the job

Bruno's Open Source tier is not a trial or a crippled edition. It is the product. Paying for a tool you don't need more from is a bad trade, and we would rather say that here than have you discover it after a purchase.

The obvious question

"Why pay for Bandura when Bruno is free?"

Because they are answering different questions. Here is the test we'd apply if we were you.

Your requests are the work

You send calls, check a status and a field or two, keep them in Git, run them in CI, and the occasional chain is short enough that a script is fine.

Then use Bruno. It is free, it does this, and it does it now. Bandura would be a purchase and a migration for something you'd notice mostly as a different-shaped UI. We would rather you kept your money.

Your scenarios are the work

Log in, capture a token, branch on what came back, loop a dataset of 200 rows, fan out four calls in parallel, poll until a job finishes, reuse the auth sequence from five other suites, and find out which step broke at 3am.

That's what the licence is for. In Bandura each of those is a node type in the file rather than JavaScript you wrote and now maintain, and there is a debugger to stop inside it.

The licence is one payment, per seat, after a 30-day evaluation that disables nothing; the price is announced when checkout opens at launch. It covers every release built in the next 12 months; after that the build you have keeps working. Bruno's paid tiers, for reference, are $6 and $11 per user per month billed annually (usebruno.com/pricing, checked August 2026), so a paid Bruno seat recurs every year while a Bandura seat is bought once, and against free Bruno the comparison is one payment versus nothing. Both shapes are on this page because both are true.

The difference in one screen

Two editors, one file, no export in between.

The canvas is not a viewer. Wire two nodes on it and the YAML changes; type in the YAML and the canvas redraws. Node positions live in the file as ui.position, so the arrangement you made travels to your teammate's machine instead of being re-derived differently there.

The Bandura desktop app after a passing run: a checkout flow on the graph canvas with green borders on the passed nodes, a labelled branch edge leaving the condition node, the inspector showing the create-order request, and the console reporting that the flow passed The Bandura desktop app after a passing run: a checkout flow on the graph canvas with green borders on the passed nodes, a labelled branch edge leaving the condition node, the inspector showing the create-order request, and the console reporting that the flow passed
Fetch product → stock check → order → receipt. The labelled edge out of the condition node is its else branch: that routing is a field in the YAML, and the canvas is drawing it rather than storing it.
The Code tab: the create_order node of the same checkout flow as .aether YAML in the Monaco editor, showing its method, url, headers, body, capture and next fields, with the node inspector's Result tab docked to the right on the request that ran The Code tab: the create_order node of the same checkout flow as .aether YAML in the Monaco editor, showing its method, url, headers, body, capture and next fields, with the node inspector's Result tab docked to the right on the request that ran
The same flow as the graph above, in text. Edit either one and the other follows. There is no third copy in a database to fall behind.

"Poll until the job is ready"

The same wait, written twice.

This is the whole difference in one requirement. Both work. One of them is a file you read; the other is a file you maintain, test, and eventually debug at 3am when the counter is off by one.

As a script plain JavaScript, the shape in any script-based client

// Nothing wrong with this code.
// It is now yours to maintain.
const url = `${baseUrl}/jobs/${jobId}`;
let attempts = 0;
let body;

while (attempts < 20) {
  const res = await fetch(url);
  body = await res.json();
  if (res.status === 200 &&
      body.state === "ready") break;
  await new Promise((r) =>
    setTimeout(r, 2000));
  attempts++;
}

if (attempts === 20) {
  throw new Error("job never became ready");
}

As a retry block the same behaviour, as fields on the node

# Flow control is data, not a script.
- id: wait_ready
  type: request
  config:
    method: GET
    url: ${{ variables.baseUrl }}/jobs/${{ variables.jobId }}
  retry:
    maxAttempts: 20
    delayMs: 2000
    until: response.status === 200 && response.body.state === "ready"
  next: check_result

- id: check_result
  type: condition
  config:
    expression: "response.body.result.total > 0"
  then: assert_totals
  else: report_empty

The right-hand version is not shorter by accident. maxAttempts, delayMs and until are fields the engine reads, so the loop, the sleep, the counter and the give-up path are not code anybody on your team owns. The debugger can also stop on the node between attempts, which it cannot do inside a while loop. That is the trade this page is selling, stated as concretely as we can state it. Node reference →

What else is in the box

Five more things, each of them checkable.

A debugger over your API tests

Breakpoints, step, run-one-node, run-from-node, and loop-iteration badges, with a variables panel that names the source of every value: flow default, environment, or captured, with a link back to the node that produced it. When an assertion fails, the console carries the expected expression against the actual value, and the notification names the failing node. Breakpoints live on your machine, never in the flow file. Run and debug →

Drift detection that needs no model

Point a flow at your OpenAPI spec and Bandura compares them deterministically: renamed paths, changed methods, new required headers, each listed with a fix you can apply one at a time. It is ordinary code, runs offline, needs no AI key, and is not metered. Self-healing →

A mock server from the flows you already wrote

Bandura turns your flows into an OpenAPI document and serves a stub of it on loopback: one palette command, or bandura mock. The limit, stated up front: the mock is only as detailed as your assertions. A flow that checks a status code produces a mock that returns a status code.

An MCP server, first-party and local

bandura mcp boots a stdio MCP server out of the same binary, giving Claude Code or Cursor fourteen tools: list, read, run, search, lint and edit flows, detect drift, import, export OpenAPI, read the .aether schema itself, and replay this session's runs. It is a local process, so nothing transits a vendor's cloud. The MCP server →

AI you point wherever you like, including nowhere

Anthropic and Gemini natively, or any OpenAI-compatible /chat/completions endpoint. Point it at a local Ollama and the chat, flow generation, and the ai-action node run on your hardware with no key. Or leave it unconfigured: importing, drift detection, the mock server, and every other node type work with the AI switched off. Local models →

Protocols: near-parity, one addition

Bruno covers REST, GraphQL, gRPC, and WebSocket, and so does Bandura. The one addition is Server-Sent Events: an sse block on a request node collects the event stream into response.body as an array you can assert on. That is a genuine difference and a small one; it is listed here rather than dressed up as protocol breadth.

Head to head

Bandura vs Bruno, as of August 2026.

Bandura compared with Bruno across fifteen capabilities. Bruno takes three rows outright, ties three more, and the cost row depends on your team. Reviewed August 2026.
Bandura Bruno
Tests as plain files in your repo .aether YAML .bru / OpenCollection YAML
Account or cloud sync required None; no server exists on our side None
Source available ✗ commercial licence; the file format is documented ✓ MIT on GitHub: auditable and forkable
Available to install today 1.0.0-rc.8 on Windows and Linux; the Windows installer is unsigned, and macOS waits on notarization ✓ shipping, with an established user base
Free tier Free for personal, learning and open source; one-time licence for commercial use, priced at launch Free and open source, permanently
Visual flow canvas ✓ graph and code edit the same file ✗ request-and-folder tree
Flow control Ten node types: condition, loop, parallel, subflow, script, ai-action, retry/poll-until Scripting and a collection runner
Step-through debugging ✓ breakpoints, step, run-from-node, loop-iteration badges
Protocols REST, GraphQL, gRPC, WebSocket, SSE REST, GraphQL, gRPC, WebSocket
OpenAPI Import, export, and deterministic drift detection with per-finding fixes; offline, unmetered Import, plus an OpenAPI sync metered on the paid tiers
Mock server ✓ generated from your flows, on loopback; as detailed as your assertions
MCP server ✓ first-party, fourteen tools, bundled in the CLI binary Community wrappers around the CLI, not first-party
Headless runner Compiled binary; pretty/json/JUnit/GitHub reporters, concurrency Its own CLI, widely used in CI
In-app AI BYO key (Anthropic, Gemini, OpenAI-compatible) or a local model; no markup, no credits AI assistant on the Pro tier
What it costs Free download; one-time per-seat licence for commercial use, priced at launch, with 12 months of updates Free · Pro $6 · Ultimate $11, per user/month billed annually

Bruno takes three rows outright, ties three more, and the cost row is a genuine "depends". The two that should weigh heaviest if you are deciding today are near the top: an MIT licence, and a mature client with an established user base against a Bandura whose first public build is a release candidate. Bruno figures and features checked August 2026 against usebruno.com/pricing, the Bruno documentation, and github.com/usebruno/bruno. If we have a detail wrong, tell us at [email protected] and it gets fixed on this page, not argued about.

If you do try it

Your .bru files import, and Bruno keeps working.

Nothing about trying Bandura removes your Bruno collection. Import is a copy, into a folder you choose, with a review step in front of it.

  1. Drop a .bru file or an OpenCollection YAML on the window

    Format auto-detected. One flow per request file, headers and structured pathParams/queryParams carried over rather than baked into the URL string. A request with a body:graphql block becomes a real graphql request node, query and variables intact.

  2. Read the review, tick what you want

    Every flow it found with its node count and the requests it makes, an editable destination folder, and a flat option if you'd rather skip the subfolder. Nothing is written to disk until you press Import, and existing files are never overwritten. Import reference →

  3. Chain them into something Bruno couldn't express

    Imported single-request flows are building blocks. Set next, add assertions, capture a token, drop in a condition. That is the point at which the two tools stop overlapping.

Fit check

Stay on Bruno if any of this is you.

We would rather lose the sale than have you buy the wrong tool and find out in week three.

Stay if…

  • You're on a Mac: Bandura's macOS builds wait on notarization, and its Windows installer is unsigned
  • An MIT-licensed, forkable codebase is a hard requirement
  • Your work is requests, not multi-step scenarios
  • Your team is already fast in Bruno and the migration cost outweighs the gain
  • Budget is genuinely zero and free is doing the job
  • You want the OpenCollection standard specifically

Look at Bandura if…

  • Your suites have grown scripts you now maintain as a second codebase
  • You want to see the flow, not reconstruct it from folder order
  • "Which step failed?" costs you real time
  • Your API changes and your tests find out in CI, one break at a time
  • You want an agent to run and repair the suite without a cloud relay
  • gRPC, WebSocket, and SSE need to live in the same suite as the REST calls

Also worth naming, because they'd come up anyway: Postman if a hosted team workspace is what you actually want (the switching case is here), Insomnia if you want an Apache-2.0 client with a plugin ecosystem (covered here), Yaak for a fast file-based client without a test runner.

The questions a switch raises

FAQ

Why would I pay for Bandura when Bruno is free?

For most of what Bruno does, you shouldn't. Bruno is free, MIT-licensed, offline, git-native, and mature, and if what you need is a fast request client whose collections are plain files in your repo, it already is that and Bandura does not beat it. The licence buys a different job: flows rather than requests. A graph canvas and a code editor editing the same file, ten node types so branching, loops, parallel fan-out and subflows are declarative config instead of script you own, a step-through debugger with breakpoints, deterministic drift detection against your OpenAPI spec, a mock server generated from your flows, and an MCP server so an agent can run your suite. If none of those is a problem you have this quarter, the honest recommendation is Bruno.

What can Bandura do that Bruno can't?

Six things, all shipped and all checkable. A graph canvas that edits the same file the code editor edits; Bruno has no canvas at all. Ten node types, so a condition, a loop, a parallel fan-out, a subflow or an AI call is a block of YAML rather than JavaScript you maintain. Step-through debugging: breakpoints, step, run-one-node, run-from-node, loop-iteration badges. Deterministic drift detection against an OpenAPI spec with per-finding fixes, offline and with no model involved. A local mock server generated from your flows. And a first-party MCP server, bundled inside the CLI binary, that gives an agent fourteen tools over stdio. Server-Sent Events is a smaller seventh: Bandura collects an SSE stream into the response body as a request-node option, and SSE is not in Bruno's protocol list as of August 2026.

What does Bruno do better than Bandura?

It has years of shipping behind it and installs on any of the three desktop platforms without a warning dialog. Bandura is still on release candidates (1.0.0-rc.8 today), its Windows installer is unsigned, and its macOS builds are still waiting on notarization. It is MIT-licensed, so you can read it, fork it, and keep it running whatever happens to us. Its free tier is genuinely free rather than an evaluation, it has a large user base and community, and OpenCollection gives its format a standards story. If your team is already productive in Bruno, the cost of moving is real and the benefit has to be worth more than the disruption.

Can I import my Bruno collections?

Yes, both .bru files and OpenCollection YAML, one flow per request file. A review step shows what was detected before anything is written: every flow with its node count and the requests it makes, checkboxes to take only some, and an editable destination folder. Existing files are never overwritten. A Bruno request carrying a body:graphql block imports as a real graphql request node with the query and variables carried over, not flattened into an opaque JSON string.

Both store tests as files. What's actually different about the file?

The graph. A .aether file carries routing on each node (next, then, else, body, branches) so a flow is a directed graph in the text, and the canvas draws it rather than storing a second copy. That is why a canvas editor is possible at all without a separate layout file, and why node positions travel with the flow instead of being regenerated differently on each machine. A Bruno collection is a folder of requests plus scripts; the ordering and the logic between them live in the scripts. Both are plain text in your repo, and both diff in a pull request. That argument is settled, and neither of us wins it.

Is Bandura open source like Bruno?

No. Bandura is a commercially licensed product with a compiled CLI, and Bruno's MIT licence is a real advantage that this page is not going to argue away. What is open is the part that determines whether you can leave: the .aether format is documented YAML you can read, parse, and rewrite with your own tools, and the exit is cp -r. There is no proprietary store to be extracted from and no plan tier that decides whether you may open your own files.

How do the licences compare on price?

Bruno's Open Source tier is free; its Pro tier is $6 per user per month and Ultimate $11, both billed annually, as listed on usebruno.com/pricing in August 2026. Bandura is free indefinitely for personal projects, learning, and open source; commercial use is a one-time per-seat licence after a 30-day evaluation that disables nothing, priced when checkout opens at launch. It covers every release built in the following 12 months, after which the build you have keeps working. Nothing auto-renews, and there is no card on file. The shapes differ more than the sums: a five-person team on Bruno Pro pays $360 a year, every year, while Bandura is one payment per seat and year two costs nothing.

Can I keep Bruno and Bandura on the same repository?

Yes, and for a trial that is the sensible arrangement. Importing reads your .bru files and writes .aether files beside them; it never edits or moves the originals, so the Bruno collection keeps working exactly as it did and you can delete the flows folder and be back where you started. The two formats have no shared state and nothing to sync between them, so the practical question is only whether you want to maintain both. A common pattern is to convert the multi-step suites, the ones that chain and branch, and leave one-off requests in Bruno.

Keep reading

The parts this page skimmed.

Tests as files in your repo

The argument you and Bruno already agree on, written out: diffs, blame, clone-to-run, and what stays off the repo.

Offline, and where the data sits

The same local-first defaults you already chose Bruno for, written out file by file, including what a commercial licence does not change.

An agent that runs the suite

The capability with no Bruno equivalent: fourteen tools over local stdio, bundled in the CLI binary, with no vendor in the middle.

The same file in CI

What replaces the Bruno CLI step: one binary, exit codes, JUnit XML, GitHub annotations, and a p95 latency gate.

Postman vs Bruno, sourced

The two tools compared on their own terms, strengths conceded, with Bandura as the third option rather than the headline.

Weighing Insomnia as well?

What the 2023 account episode did and didn't change, and how Git Sync differs from files.

v1.0.0-rc.8 is out now. v1.0.0 final in August 2026

Keep Bruno. Try this on the suite that outgrew it.

You don't have to pick a side to find out. Importing is a copy, so your .bru files are untouched, and the 30-day evaluation disables nothing: point Bandura at the one suite that has grown scripts and leave everything else where it is. Windows and Linux builds are up now; on a Mac, leave an address and you get the signed build the day it is notarized.

Download for Windows or Linux

One email when the signed macOS build and 1.0.0 ship, no reselling, opt out any time.

Or settle it on paper first: the node reference is every one of the ten types with its fields, which is the concrete version of the argument above.