Bruno API Collections in Git: A Workflow Shift, Not Free Postman
Committing Bruno .bru files to the repo keeps the API contract in the same PR and history as the code. The only real tax is a deliberate secrets boundary.
Your API collection lives in a vendor cloud or on one engineer’s laptop, while the code that serves those endpoints lives in git. The two drift apart the moment an endpoint adds a required header or renames a field: the code change ships in a pull request, but the saved request stays behind, and the next person who opens the collection debugs a request that no longer matches reality. The fix is to commit your request collection as plain-text files next to the code, so the API contract travels through the same pull request, the same review, and the same history as the handler it exercises. The tool here is Bruno, but the argument is about where the contract lives, not which client renders it; there is an honest boundary around secrets, and a few cases where cloud collaboration still wins.
The point worth stating up front: this is not “free Postman.” Swapping one client for another that happens to be free changes your tooling bill, not your workflow. Committing the collection changes the workflow, because it removes the second system where drift hides.
The API Change Gets Reviewed in the PR
Bruno stores each request as a plain-text file in a collection folder you check into the repo. The format is “Bru,” a simple markup language built on plain-text files; it is a small DSL, not JSON, which is what makes the diffs read cleanly in a pull request. A minimal request looks like this:
meta {
name: Get user
type: http
seq: 1
}
get {
url: {{baseUrl}}/users/42
body: none
auth: bearer
}
headers {
Accept: application/json
}
auth:bearer {
token: {{authToken}}
}
assert {
res.status: eq 200
res.body.id: eq 42
}
When the /users/42 endpoint starts requiring a new Accept value, or the response gains a field an assertion should check, you edit this file in the same commit as the handler. The reviewer sees both diffs side by side: the handler change on one side, the request change on the other. The contract change is reviewed in-band, as text, by the same person who reviews the code.
Contrast that with a cloud-hosted collection. The handler edit lives in git; the request edit lives in the vendor app. That is two systems and two timelines, and they drift by default rather than by accident. Bruno’s own framing matches this directly: it positions itself as “the Git-native API client” with “collections stored as code,” and it is explicit that it is “Local Only” with no cloud sync, so your data stays in the repo. Collaboration happens through pull requests, because the human-readable file format makes the change easy to read.
One currency note, because the tool is moving. Bruno still fully supports .bru, but it now recommends OpenCollection YAML, an open spec it authored, for new collections. The thesis does not depend on the file extension. Both formats are plain text, both live in the repo, and both review through a pull request. Lead with .bru if that is what your existing collections use; reach for OpenCollection YAML on a greenfield collection. The workflow shift is identical either way.
The Secrets Boundary
Here is the honest gotcha buried inside “commit everything”: you do not commit everything. The request shape goes in git; the secret values do not. Bruno’s docs are blunt about this, stating that whether you check the collection into git or export it, “we want to ensure that the secrets are stripped out of the collection before it is shared.” Getting this boundary right is the one real tax of the flat-file workflow, and it is worth a precise answer rather than a hand-wave.
The cleanest default is the secret variable. In an environment, you tick the secret checkbox on a variable. Bruno then manages that value internally and never writes it into the environment file; the file records only the variable name. The shape that lands in git is the name and nothing else:
vars:secret [
baseUrl
]
The value lives on your local machine, encrypted with OS-level encryption when available and falling back to AES256. Bruno also strips secret variables when you export a collection. The net effect, in Bruno’s words, is that “you can safely check in your collection to source control without worrying about exposing your secrets.” That is the direct answer to “but won’t I commit my tokens?”
The second path is the one most backend engineers already know: a .env file at the root of the collection folder, read inside Bruno through process.env. The documented folder structure ships a .gitignore alongside the .env, so the pattern is the familiar one. The .env stays git-ignored, while the request files and bruno.json are committed. If your application already handles secrets this way, the collection follows the same rule, and a new teammate populates one local file. For org-scale setups, Bruno’s Ultimate tier integrates with HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault, so values resolve from a vault rather than touching disk; that is the paid option, off the default path, and most teams never need it.
Two failure modes show up when teams skip this discipline. The first is a real token pasted into a plain vars block instead of vars:secret, then pushed to a public repo; the value lands in history and stays there. The fix is to tick the secret checkbox so the value never reaches the file, or move it to the git-ignored .env. The second is subtler: treating the collection as an export you regenerate occasionally. An exported-then-committed JSON dump is drift waiting to happen, because nobody edits it in the same PR as the handler. The whole point is that the request file is a first-class source file, edited by hand alongside the code, not a snapshot you refresh when you remember to. Document the boundary in the collection’s README so the next teammate knows which file to populate locally and which never to touch.
When This Loses
The flat-file workflow is the default for an engineering team, but it is a default with real edges, and pretending otherwise weakens the argument. Three cases push you back toward a cloud collaboration tool.
The first is people who run your requests but will not touch git. Git fluency is the price of admission here. A non-technical QA tester or a stakeholder who needs a click-to-run shared workspace is genuinely better served by a cloud tool. If a meaningful share of your request-runners lack git, the flat-file model adds friction instead of removing it. The second is large-org sharing across teams that do not share a repo. “Clone our repo” is a fine onboarding step inside one codebase; it falls apart as a discovery mechanism across an organization with no common repo, where a cloud workspace with org-wide search wins. The third is the set of capabilities a local-first flat-file client simply does not ship: mock servers, monitors, scheduled health checks, hosted dashboards, and performance or load testing. Bruno is explicitly local-only with no cloud sync, so for those you stay on a cloud platform or add separate tooling. Postman, for instance, provides mock servers, monitors, and performance testing as cloud features.
It is worth stating the Postman contrast conservatively, because the loose version is wrong. Postman is account-centric and cloud-sync-by-default: collections live in a Postman account and workspace, and sync to the cloud unless you deliberately avoid it. That is a different center of gravity from Bruno’s local-first, files-in-your-repo model, and that difference, not any claim that you cannot use Postman without an account, is the real workflow contrast. The two tools optimize for different things. One optimizes for an account-centric cloud workspace; the other optimizes for keeping the contract in the repo next to the code.
Closing
For an engineering team where the people running the requests already live in git, commit the collection as plain-text files and treat it as part of the codebase: same pull request, same review, same history as the handler it exercises. That removes the second system where drift hides and makes the API-shape change reviewable as text. The boundary is firm in three places, and they are the cases to honor rather than fight: non-technical request-runners, large-org sharing with no shared repo, and cloud-only capabilities like mock servers and monitors. The one piece of discipline the workflow demands is a deliberate secrets boundary, so the practical next step is to set up the secret variable checkbox or a git-ignored .env before the first push, and document which file each teammate fills in locally.
References
- Bru Markup Language — Bruno Docs - Confirms
.bruis a plain-text DSL for version-controlled collections; notes OpenCollection YAML is now the recommended format for new collections. - OpenCollection YAML — Bruno Docs - The YAML spec Bruno now recommends for new collections; relevant for the format-shift caveat.
- Secret Management overview — Bruno Docs - States secrets must be stripped before checking-in or exporting; lists the three secret approaches.
- Secret Variables — Bruno Docs - The
secretcheckbox; values are not written to the env file (vars:secret [...]), stored OS-encrypted or AES256. The default safe-to-commit pattern. - DotEnv File — Bruno Docs - A
.envat the collection root plus.gitignore, accessed viaprocess.env; the familiar backend pattern. - Secret Managers overview — Bruno Docs - HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault integration, gated to Bruno Ultimate.
- Bruno (GitHub repository) - The
@usebruno/cliandbru runfor CI, and the language breakdown showing a dedicated “Bru” language. - Bruno LICENSE - MIT License, Copyright (c) 2022 Anoop M D, Anusree P S and Contributors. Confirms the core is MIT, not source-available or commercial.
- Bruno latest release (GitHub) - The latest published release tag; spot-check the version before relying on it.
- Bruno Pricing - Open Source, Pro, and Ultimate tiers per user per month, billed annually. The git-in-repo workflow needs only the free core.
- Bruno homepage - “The Git-native API client,” “Local Only — no cloud sync,” “Collections stored as code.” The primary framing source for the thesis.
- About Postman accounts — Postman Docs - Postman’s account-centric, cloud-sync-by-default model; the contrast point.
- Install Postman — Postman Docs - The web versus desktop feature split; supports the cloud-only-features point (mock servers, monitors, performance testing).
Related posts
Backstage looks like a quick install, but the recurring cost is a standing platform team. A leader's guide to deciding DIY Backstage versus a hosted IDP.
How a frontend platform team makes the right way the easy way: golden-path scaffolding, versioned shared packages, and a task CLI that removes per-team drift.
Copying Claude Code configurations causes context window bloat, degraded tool selection, and mismatched workflows. Build setups intentionally with token budget math.
Why production teams replace broad MCP access with scoped API proxies. Atlassian, Google Workspace, and Notion via FastAPI proxy, CLI wrapper, and n8n.
Why the salary-vs-impact-vs-satisfaction debate is a false trichotomy, and what your answer reveals about your vocational development stage.