Terminal & CI
API testing in CI: the flow you just ran is the check that gates the pull request.
Bandura flows are plain-text files, so running them in CI is one command with an exit
code. Not an export, not a sync, not a hosted runner you rent. The desktop app, the
bandura CLI, and the MCP server share one execution engine. What passes
on your machine passes in the pipeline: same code, same file.
v1.0.0-rc.8 is out now on Windows and Linux. v1.0.0 final, and signed macOS builds, in August 2026.
The whole integration
One command, and the exit code does the rest.
On npm now
# One command. The exit code is the whole contract.
bandura run "flows/**/*.aether" -r junit,github -o report.xml @bandura/cli is published: a small installer that fetches a native
binary for your platform. The 1.0.0 tag is held for launch day, so
today you pin a release candidate. CLI reference →
No quota on your own computer
There is no account to create, no backend to reach, and no metered runner. The engine executes locally: in the app, on your laptop, or on whatever machine the job already runs on. A nightly suite costs the same as a single run: nothing.
Flows are selected by path, by glob, or by a named
collection from
bandura.json, giving you an ordered suite reported as one result.
Reporters & exit codes
Output your pipeline already knows how to read.
Comma-separate the reporters to emit several at once: human output in the log, machine output in the artifact.
| Reporter | What it's for |
|---|---|
pretty |
The default. Human-readable per-node results, with anything a node printed
via log() dimmed underneath it.
|
json | Structured results for your own tooling. Write it with -o. |
junit |
JUnit XML for the test dashboard every CI system already has.
log() output travels in <system-out>.
|
github | GitHub Actions annotations, so a failure lands as an inline comment on the pull request rather than a line in a log nobody opens. |
| Exit code | Meaning |
|---|---|
0 | All flows passed. |
1 | A test failure: an assertion or a request failed. |
2 | Usage or config error: bad flags, no flows matched, missing env. |
3 | Parse or schema error in an .aether file or bandura.json. |
4 | Internal error. |
The split is the point: "the tests failed" and "the tool broke" must not look alike to
a pipeline. bandura validate exits 3 on any schema error
without executing anything, which is fast enough for a pre-commit hook or a
network-free PR gate.
GitHub Actions
How to run API tests in GitHub Actions: annotations on the PR, JUnit in the artifacts.
The official action is a thin composite wrapper over the same CLI, with no logic of its own. It runs your flows with GitHub annotations, always writes the JUnit XML report and uploads it as an artifact, and fails the job on a non-zero exit.
# .github/workflows/api-tests.yml
- uses: actions/checkout@v4
- run: npx @bandura/cli run "flows/**/*.aether" --reporter github,junit -o report.xml
env:
BASE_URL: ${{ secrets.STAGING_URL }}
ACME_API_KEY: ${{ secrets.ACME_API_KEY }}
Checkout stays your job. Secrets are never action inputs. The
manifest's requiredEnv names map one-to-one onto secrets supplied
through the workflow's env: block. The action publishes to the
GitHub Marketplace at launch alongside the CLI.
# .gitlab-ci.yml. Any CI works: it's one command and an artifact.
api-tests:
script:
- npx @bandura/cli run "flows/**/*.aether" -r junit -o report.xml
artifacts:
reports:
junit: report.xml Nothing about the runner is GitHub-specific: GitLab, CircleCI, Jenkins, Buildkite, or a cron job on a box all work the same way: run the command, read the exit code, keep the JUnit file. The desktop app can also generate the workflow file for you via Export to CI/CD. Working pipeline recipes →
One suite, many environments
Point the same flows at staging without editing a file.
Named environments
-e staging loads that environment's non-secret values from
bandura.json; an unknown name exits 2 instead of
quietly running against the wrong host.
Variables & environments →
Secrets stay outside the repo
Values arrive from the process environment or a dotenv file. Flows reference them
as env.*, so credentials are never a field inside a committed
.aether file.
Overrides and ceilings
--var KEY=VALUE injects a value at the highest precedence,
--timeout sets a whole-run deadline per flow, and
--bail stops at the first failing flow when a smoke check should
fail fast.
Keep the suite honest
Let the pipeline notice the API moved.
The usual way an API test suite dies is quietly: the API changes, the tests keep asserting last quarter's shape, and nobody notices until they are all red at once.
# Fail the build when the committed spec no longer matches the flows
bandura export openapi --check -o specs/openapi.yaml --check regenerates the spec from your flows and exits 1
when the committed one no longer matches. A staleness gate, not a nightly email.
Deterministic and offline: no model is called.
The other direction, too
Drift detection compares a flow against an OpenAPI spec and names what moved: a renamed path, a changed method, a newly required parameter, each with a proposed fix you apply in the app, or that an agent applies for you through the MCP server. Same deterministic comparison in both places.
Where the line is
What Bandura will not do for your pipeline.
"Runs where your pipeline runs" is the whole design, and it cuts both ways. Four things you should expect to solve elsewhere.
No hosted monitors or schedules
There is no Bandura service running your flows every fifteen minutes against production. Remote and scheduled execution is a planned feature with no backend behind it yet. Until then, a cron trigger in your own CI is the answer, and it is the answer we would give even if the service existed.
The report is the artifact; there is no dashboard
The CLI writes JUnit XML and prints annotations; whatever your CI does with those is the trend view you get. Run history with full captured bodies is a desktop feature, stored in a local SQLite file, and it does not follow the suite into the pipeline.
The Action is not on the Marketplace yet
You can run the CLI in any pipeline today, because @bandura/cli is on
npm as a release candidate. What is held for launch day is the 1.0.0
tag and the bandura-io/run Action, so the uses: workflow
above is the shape to plan for rather than a line you can pin this afternoon. The
npx recipe beside it works now.
One integration, not a catalogue
There is no CircleCI orb, no Jenkins plugin, no Buildkite step, and none is planned. Everywhere except GitHub you invoke a binary and read an exit code. That is a deliberate trade: a per-vendor plugin is a per-vendor thing to keep working, and a command is stable in a way a catalogue entry is not. Pipeline recipes →
Questions, answered straight
FAQ
How do I run API tests in a CI pipeline with Bandura?
One command: bandura run "flows/**/*.aether". It exits 0 when everything passed and non-zero otherwise, which is all any CI system needs. Add -r junit -o report.xml for a report your dashboard can read, and -r github on top for inline PR annotations. There is no account, no runner registration, and no per-run quota, because the engine executes on the machine the job is already paying for.
Do local runs and CI runs behave the same?
They are the same engine reading the same file. The desktop app, the CLI, and the MCP server all embed @bandura/execution-engine, so a flow that passes in the editor passes in the pipeline unless the environment differs, which is the failure you actually want CI to find.
How are secrets handled?
As environment variables, never as fields in the flow files. Flows read env.* from the process environment plus a dotenv file (--env-file, default .env), so the .aether files stay clean enough to commit. With the GitHub Action, secrets are supplied through the workflow's env: block. They are deliberately not action inputs, because inputs get echoed into logs.
What do the exit codes mean?
0 means every flow passed. 1 is a real test failure: an assertion or a request. 2 is a usage or config error, such as a bad flag, a glob that matched nothing, or a missing environment. 3 is a parse or schema error in an .aether file or bandura.json. 4 is an internal error. The split exists so a pipeline can tell 'the tests failed' apart from 'the tool broke'.
Can I run the suite faster than one flow at a time?
Use --concurrency <n> to run up to N flows in parallel; each flow still runs its own nodes in order. Output is buffered back into input order, so the report stays byte-identical regardless of how many ran at once. A parallel run does not produce a shuffled log.
Does CI need Node.js installed?
The CLI is distributed as a self-contained native binary per platform: macOS (arm64/x64), Linux (x64 and arm64, glibc and musl, so Alpine works), and Windows x64. Once it is installed there is no runtime to load. The npm installer that places it does run under Node, and the GitHub Action sets Node up for that step.
Can CI check that my OpenAPI spec still matches the flows?
Yes. bandura export openapi --check regenerates the spec from your flows and exits 1 if the committed file is out of date, which turns spec staleness into an ordinary failing check. The comparison is deterministic and offline: it never calls a model.
Keep reading
What makes a one-command pipeline possible.
Tests as files in your repo
CI can only run what it can clone. Flows are plain-YAML files that live beside the code they test, so there is nothing to export before a build.
Coming from Postman and Newman
Collections, environments, and pm.* scripts import, and the CI step
becomes one binary instead of a Newman install to maintain.
AI without a third-party endpoint
ai-action nodes run headlessly against any OpenAI-compatible
endpoint, including a model served on the runner itself.
Fix the red build with an agent
A local MCP server lets Claude Code or Cursor run the failing flow, read the actual response, and edit the YAML.
No login step in the pipeline
Why the binary reaches nothing but the API under test, and where every other piece of your data sits on disk.
gRPC, WebSocket, and SSE in the same suite
All ten node types run headlessly, so the streaming endpoints gate the pull request with the same one command as the REST calls.
v1.0.0-rc.8 is out now. v1.0.0 final in August 2026
Green locally, green in the pipeline.
The CLI is on npm now, so the pipeline step is two lines you can write this afternoon
and one exit code your CI already knows how to read.
Windows and Linux desktop builds
are up alongside it. On a Mac, or waiting for the 1.0.0 tag and the
Action? Leave an address and you get both links the day they exist.
Writing the step first? The pipeline recipes are working YAML for GitHub Actions and GitLab, and the CLI reference has every flag.