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 — no quotes needed, no spaces around the
=:BASE_URL=https://api.example.com API_KEY=sk_live_9f2c... EMAIL=test@example.com 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 — 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. (Fixed in a
recent build; if a value looks stale, it isn’t a caching issue you need 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
From lowest to highest precedence:
- 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.- Flow variables — the
variables:block at the top of the.aetherfile. Defaults that belong to the flow itself and travel with it in Git. - Captured values — written into
variablesat run time by a request node’scaptureor anai-action’soutput. - Runtime overrides — values you type into a node’s Variables tab in the inspector. Highest precedence, stored locally on your machine only — never written into the flow file, so they can’t leak into a commit.
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 — no ${{ }}.
env.X reaches .env and manifest-environment values; variables.X reaches flow
variables, captures, and overrides.
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) — one click on Initialize bandura.json — 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).
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.