Flows almost never hard-code everything. Base URLs differ between staging and production, tokens come back from login calls, secrets live outside Git. Bandura gives each of those a home.
Setting up .env, the 60-second version
What it is. A plain text file named .env holding one KEY=value per line. It’s the
standard way to keep secrets and machine-specific settings out of your flow files.
Why you want it. Your .aether flows are committed to Git and shared with your team. You
do not want an API key or password sitting in a committed file. So instead of writing the
secret into the flow, you write a reference to it (${{ env.API_KEY }}) and put the real
value in .env, which Git ignores. The flow travels; the secret stays on your machine.
How to create it.
-
In the file explorer, make a new file named exactly
.envat the root of your workspace (the top folder you opened). The leading dot matters. -
Add your values, one per line, with no quotes needed and no spaces around the
=:BASE_URL=https://api.example.com API_KEY=sk_live_9f2c... EMAIL=[email protected] PASSWORD=hunter2 -
Reference them from any node with
${{ env.NAME }}:url: ${{ env.BASE_URL }}/todos/1 headers: Authorization: Bearer ${{ env.API_KEY }}
That’s the whole loop: .env holds the value → ${{ env.NAME }} pulls it in → the request
sends the real thing at run time. Nothing in the committed .aether file ever contains the
secret.
Where Bandura looks. The workspace-root .env applies to every flow. A flow can also have
its own .env sitting next to it in the same folder, which is handy when one suite needs
different values. Both are read automatically; you don’t register them anywhere.
It’s picked up live. Create or edit .env and Bandura re-reads it immediately: the
⚠ unset badges clear and ${{ env.X }} resolves without reopening the flow. That means a
value which looks stale is a value that isn’t set where you think it is, not a cache to work
around.
Keep it out of Git. Add .env to .gitignore and commit a .env.example (same keys,
empty or dummy values) so teammates know what to fill in. The Environments view’s
Set up for Git button scaffolds both for you.
Putting a variable inside a JSON body
A common snag: a request body is JSON, and JSON is strict about quotes. A placeholder that
stands in for a string value must sit inside the quotes, or the body stops being valid
JSON and you get Invalid JSON: Expected ',' or '}':
// ✗ wrong: the placeholder is bare, so the JSON is malformed
{ "token": ${{ env.REFRESH_TOKEN }} }
// ✓ right: the placeholder is inside the string
{ "token": "${{ env.REFRESH_TOKEN }}" }
Bandura substitutes the value first, then sends the body, so as long as the template is valid JSON with the placeholder quoted, the final request is valid too.
The five sources of a value
References resolve from two namespaces. env.* is read-only, lowest to highest:
- Manifest environment. The selected environment’s variables from
bandura.json(e.g. staging’sBASE_URL). .envfile. Key=value pairs in your workspace root. This is where secrets go; keep it in.gitignore.
variables.* starts from a seed, then updates as the run executes:
- Flow variables. The
variables:block at the top of the.aetherfile: defaults that travel with the flow in Git. The base of the seed. - Runtime overrides. Values you type into a node’s Variables tab in the inspector. They seed over the flow variables, so an override beats a flow default. Stored locally on your machine only, never written into the flow file, so they can’t leak into a commit.
- Captured values. Written into
variablesat run time by a request node’scaptureor anai-action’soutput. Because they land as the flow executes, captures are last-write-wins: one that shares a name with a flow default or an override overwrites it from that point on.
Referencing values
Inside YAML string values, use ${{ }} interpolation:
url: ${{ variables.baseUrl }}/users/${{ variables.userId }}
headers:
Authorization: Bearer ${{ env.TOKEN }}
Inside JavaScript fields (check, expression, over, capture entries, script code),
reference variables, env, and response directly, with no ${{ }}.
env.X reaches .env and manifest-environment values; variables.X reaches flow
variables, captures, and overrides.
Both kinds of field also have the dynamic value helpers in scope, for
values that should be generated rather than looked up: ${{ $uuid() }}, ${{ $randomEmail() }},
${{ $isoTimestamp() }}, ${{ $hmac(payload, env.SECRET) }} and the rest.
One thing that surprises people: a value in the flow’s own variables: block is stored exactly
as written. Interpolation happens where a value is used, not where it is declared, so
key: ${{ $uuid() }} at the top of a file leaves variables.key holding that text rather than a
UUID. Generate it in a script node instead.
Capturing values between steps
The login-then-use-the-token pattern:
- id: login
type: request
config:
method: POST
url: ${{ variables.baseUrl }}/auth/login
body: '{ "email": "${{ env.EMAIL }}", "password": "${{ env.PASSWORD }}" }'
capture:
accessToken: response.body.token
next: get_profile
- id: get_profile
type: request
config:
method: GET
url: ${{ variables.baseUrl }}/me
headers:
Authorization: Bearer ${{ variables.accessToken }}
next: null
The graph badges the login node with ⤓ accessToken so the data flow is visible. (Plain
response also works between adjacent requests, but it’s overwritten by every new request;
capture is the durable way.)
Named environments: bandura.json
To switch a whole workspace between staging and production, define environments in a
bandura.json manifest at the workspace root:
{
"name": "My API Tests",
"environments": {
"staging": { "BASE_URL": "https://staging.api.example.com" },
"production": { "BASE_URL": "https://api.example.com" }
},
"requiredEnv": [{ "name": "API_KEY", "secret": true }]
}
- Create it from the Environments sidebar view (globe icon), where one click on Initialize bandura.json does it, or from Settings → Workspace.
- Switch environments from the selector in the status bar (bottom-left), the
Environments view, or the command palette (
Environment: staging). Choose None (.env only) to use just your.env. requiredEnvdeclares which.envkeys the project expects, so a teammate who clones the repo can see at a glance what to fill in. Your selection is stored locally and remembered between sessions.
Commit bandura.json (it holds names and URLs); never commit .env (it holds secrets).
Collections: an ordered suite in the manifest
A collection is a named, ordered list of flows that run together and report as one result. It is the Git-native equivalent of a Postman folder you press Run on, and it lives in the same manifest:
{
"name": "My API Tests",
"collections": {
"smoke": {
"description": "Runs on every pull request",
"flows": [
"flows/health.aether",
"flows/login.aether",
"flows/orders/*.aether"
],
"environment": "staging",
"continueOnError": true
}
}
}
| Field | Required | What it does |
|---|---|---|
flows | yes | Ordered .aether paths or globs. This order is the run order; globs expand sorted |
description | no | Shown in the report header and in the desktop’s collections view |
environment | no | Pins one environments key for the whole suite, whatever is otherwise selected |
continueOnError | no | true (default) runs everything and reports all failures. false stops at the first failure |
Run it with bandura run --collection smoke, or from the command palette
in the app. Two behaviours are worth knowing before you rely on the order: a --collection run
ignores the positional globs and the manifest’s own flows field, because the collection’s list
is the selection; and the flows still execute independently, with no shared variables between
them, exactly as a plain bandura run does. A collection controls order, the environment and the
stop-on-failure policy, and nothing else. Duplicates across entries are de-duplicated, keeping the
first position.
An unknown collection name is a usage error (exit 2), and a collection whose globs match no files
is an error rather than an empty pass.
Unset values and the run guard
A node referencing a variable with no value gets a ⚠ badge on the graph and in its Variables tab. Pressing Run with unset references doesn’t fail mysteriously mid-run. Bandura selects the first unresolved node, opens its Variables tab, and turns the button into Run anyway. Fill the value (or type a local override) and run, or press again to proceed regardless.
In the Variables tab, values that come from .env are masked by default; use the per-row
reveal toggle when you need to see one.
Everything on this page works with no account and no network: the offline API client page lists exactly which file on your disk holds which piece of your data, including where these values live.
What to read next
- Dynamic values for values that should be generated per run rather than looked up, like a fresh UUID or a signed timestamp.
- Authentication & cookies once the credential is more than a static token.
- Run flows in CI for handing the same
env.*names to a pipeline’s secret store.