Bruno API Client | Blog & News

How to Share Bruno Collections and API Docs Publicly: Fetch in Bruno Buttons and Generated Docs

Written by Anthony Dombrowski | Oct 21, 2026

Your collection is already the most accurate description of your API. It holds every endpoint, the headers each one expects, example bodies, the auth it needs, and the tests that prove it still works. The next step is getting it in front of people outside your team: partners integrating against your API, customers reading your reference docs, or developers who found your open-source project on GitHub. This guide covers how to share a Bruno collection or workspace publicly, and how to publish API documentation from it, without a Bruno account or a hosted workspace.

Bruno gives you two ways to do that. You can share the collection itself through a public Git repository, with a Fetch in Bruno button that clones it into a reader's app in one click. Or you can generate a standalone HTML documentation site from the collection, playground included, and host it anywhere static files can live. Workspaces follow the same Git route as collections.

This post walks through both, shows what a reader sees on the other end, and covers how to keep secrets and unfinished requests out of anything you publish. If you are sharing with your own team rather than the public, start with How to Share Bruno Collections: Git, Export, and Import, which covers Git sync, exports, and imports.

Quick answer

To share a Bruno collection publicly, push it to a public Git repository and add a Fetch in Bruno button to your README or docs site; anyone who clicks it gets a Git-synced copy in their own Bruno. To publish API docs, open the collection's Overview tab and click Generate Docs (or run bru docs generate in CI) to get a single HTML file with a live playground that you can host anywhere. The recommended setup is both. Secret variables never land in the repository or the HTML, and everything here works in the free edition.

What you can share publicly, and what the reader gets

The two routes are not either/or, and the recommended setup is both: publish the generated docs for anyone with a browser, and put a Fetch in Bruno button tied to the Git repository next to them for readers who want the collection in their own app. The generated docs already embed the repository link as Open in Bruno, so the pair comes out of one Git-backed collection with no extra upkeep. If you only ship one, pick by what the reader should be able to do afterward: run and edit requests in Bruno means Git, while read, search, and try without installing anything means the docs.

One Git-backed collection feeds both routesYour collectionin a public Git repoFetch in Bruno buttonon a README, docs site, or blogGenerate the docsOverview tab or bru docs generateReader's Brunoa Git clone, pulls updatesSingle HTML fileplayground, hosted anywhereOpen in Bruno

One Git-backed collection feeds both the Fetch in Bruno button and the generated docs.

You share How What the reader gets Reader needs
A collection Public Git repo plus a Fetch in Bruno button A Git-synced copy in their own Bruno: every request, script, test, and doc. They pull your updates whenever you push Bruno installed
A workspace Public Git repo plus a Fetch in Bruno button from the Export action on the workspace overview A pick list of every collection in the workspace repository, each opening as a Git-synced copy Bruno installed
Docs A single generated HTML file, hosted anywhere A browsable, searchable API reference with a Try playground, code samples, and an Open in Bruno button that clones the source when the Git link is included, or offers a download when it is not A browser
An OpenAPI spec A Fetch in Bruno button pointed at the spec URL The spec converted into a Bruno collection and opened in their app Bruno installed

Both routes read from the same collection files on disk, so there is one source of truth. The markdown you write at the workspace, collection, folder, and request levels travels with the Git clone and fills the descriptions in the generated docs site.

What is free and what needs a license

Free vs. paid

Everything in this post works in the free, open-source edition of Bruno, with one exception: doing some of the Git plumbing from inside the app. Generating docs from the collection's Overview tab or with bru docs generate, the Fetch in Bruno button, and the reader's clone, fetch, and pull are all included in the free edition, as are initializing Git and viewing diffs in the Git UI. Connecting a remote, committing, pushing, branching, stashing, and resolving merge conflicts inside the Git UI need Pro or Ultimate or a free trial, and so do private repositories through the Git providers. Check out more details on our pricing page.

In practice, a free-edition user initializes the repository in Bruno, on the command line, or with an IDE, then connects the remote and pushes from the terminal. The button, the clone on the reader's side, and the generated docs behave the same on every plan. If you want the in-app Git actions, a 14-day trial of Ultimate unlocks them with no credit card.

How to share a Bruno collection or workspace through Git

This is the first half of the recommended setup; the generated docs are the other. The collection lives in a public Git repository, and a Fetch in Bruno button on your README, docs site, or blog post turns the clone into a single click. The Fetch in Bruno button is a link to fetch.usebruno.com that carries your repository URL and clones it into the reader's Bruno. There is nothing to upload to us. The public Git repository is the share.

Before you start

The collection or workspace has to be a Git repository with a public remote. You can initialize Git from the Bruno UI, from the command line, or from an IDE, then push to GitHub, GitLab, Bitbucket, or any other host. Initializing is free in every edition of Bruno. Connecting the remote and pushing from inside the app's Git UI needs a Pro or Ultimate license. On the free version, you can always push from the terminal, and the Fetch in Bruno button works the same either way.

Get the Fetch in Bruno button for a collection

  1. 1Open the collection in Bruno.
  2. 2Click Share on the collection's Overview tab, or choose Share from the ... menu next to the collection name in the sidebar.
  3. 3Open the Embed tab and copy the snippet in HTML or Markdown form.

Get the Fetch in Bruno button for a workspace

  1. 1Click the Home icon in the title bar to open the workspace overview.
  2. 2In the tab header, click the ... next to the workspace name and choose Export.
  3. 3Open the Embed tab and copy the snippet.
Note

The Export action is only offered for workspaces you created or opened from a folder; the default workspace does not have it.

A workspace button lists every Bruno collection in that repository, so this is the route when you want readers to pick from a set of related collections in one import.

Or write the snippet yourself

The button is a link to fetch.usebruno.com with your repository URL in the query string. You can hand-write it, or generate it programmatically, anywhere that renders HTML or Markdown.

HTML:

■index.html
<div
  class="bruno-fetch-button"
  data-bruno-collection-url="https://github.com/your-org/your-collection.git"
></div>
<script src="https://fetch.usebruno.com/button.js"></script>

Markdown, which is what you want in a GitHub README:

■README.md
[<img src="https://fetch.usebruno.com/button.svg" alt="Fetch in Bruno" width="128" height="32">](https://fetch.usebruno.com?url=https://github.com/your-org/your-collection.git "target=_blank rel=noopener noreferrer")

Swap in your own image and alt text if you want the button to carry your project's branding. Keep the link target as it is.

For example, a README that uses a button image hosted in the project's own repository, with alt text that names the API:

■README.md · custom button
<a href="https://fetch.usebruno.com?url=https://github.com/your-org/your-collection.git" target="_blank" rel="noopener noreferrer">
  <img
    src="https://raw.githubusercontent.com/your-org/your-collection/main/assets/try-in-bruno.svg"
    alt="Try the Acme API in Bruno"
    width="160"
    height="40"
  >
</a>

This form works both in a GitHub README, which renders inline HTML, and on any web page. Only three attributes on the image are yours to change. Everything else stays as the Embed tab generated it, including the link, which already carries your Git repository URL:

Part Generated by the Embed tab Customized here
src https://fetch.usebruno.com/button.svg Your own image, hosted in the repo or on your site
alt Fetch in Bruno Try the Acme API in Bruno
width / height 128 / 32 160 / 40, to match the new image
Link target https://fetch.usebruno.com?url= followed by your Git repository URL Same shape, with your own repository URL in the url parameter

The fetch.usebruno.com link with your repository URL is what makes the click open Bruno, so that part stays exactly as generated.

What happens when a reader clicks

  1. 1The button opens a hand-off page from fetch.usebruno.com in a new browser tab. Nothing is downloaded at this point. The page shows an Open In Bruno button.
  2. 2Click Open In Bruno. That hands the repository URL to the Bruno desktop app. Depending on the browser, the reader may see a one-time prompt asking to allow the site to open Bruno.
  3. 3Bruno launches and asks where to store the collection on their machine.
  4. 4Bruno clones the repository and lists the Bruno collections it finds inside.
  5. 5The reader picks one and it opens with every folder, request, script, and test intact.
One click takes a README button to a Git cloneReader clicks the buttonon your README or docs siteHand-off page opensfetch.usebruno.com, new tabOpen In Brunothe URL is handed to BrunoBruno asks for a folderon the reader's machineThe repo is clonedcollections inside are listedReader picks a collectiona Git-synced copy opens

Six steps from a README button to a Git-synced copy in the reader's Bruno.

The result is a real Git clone, not a snapshot. When you push a change, the reader pulls it from Bruno's Git UI or from their terminal and gets the new version.

Point the button at an OpenAPI spec instead

The same button also accepts an OpenAPI spec URL. Bruno imports OpenAPI 2.0 (Swagger 2.0) and OpenAPI 3.x specs, written in either JSON or YAML, so the URL can end in .json, .yaml, or .yml. Add data-bruno-source-type="openapi" to the HTML form, or &type=openapi to the Markdown link, and Bruno fetches the spec, converts it into a collection, and opens it. That is useful when the public artifact you already maintain is the spec rather than a collection.

HTML, pointed at a spec instead of a repository:

■index.html
<div
  class="bruno-fetch-button"
  data-bruno-collection-url="https://api.example.com/openapi.json"
  data-bruno-source-type="openapi"
></div>
<script src="https://fetch.usebruno.com/button.js"></script>

Markdown, with &type=openapi added to the link:

■README.md
[<img src="https://fetch.usebruno.com/button.svg" alt="Fetch in Bruno" width="128" height="32">](https://fetch.usebruno.com?url=https://api.example.com/openapi.json&type=openapi "target=_blank rel=noopener noreferrer")

When a reader clicks either one, Bruno fetches the spec from that URL, converts it into a collection, and asks where to save it. The spec URL has to be publicly reachable, since the reader's machine is what downloads it. The OpenAPI import docs cover the supported versions and how endpoints are grouped into folders.

Let readers find the repository from inside Bruno

Readers can also skip the button entirely. The GitHub tab in the Import Collection dialog searches all of GitHub for public repositories, no login required, and clones from the results. Private and organization repositories need the GitHub provider connected, which needs Pro, Ultimate, or a free trial. We covered that flow in Git provider integration in Bruno.

How to generate API documentation from a Bruno collection

Generated documentation is for readers who shouldn't have to install anything. Bruno builds a self-contained HTML file from the collection: requests, params, headers, auth, scripts, tests, and the markdown docs you have written at each level. It also ships with an interactive playground, so readers can edit a request, send it, and see the response without leaving the page. You host the file wherever you like, and generating it is included in the free edition.

The docs are a build step from the files you already haveCollection files on diskrequests, docs, environments, tagsGenerateOverview tab, or bru docs generate in CIchoose environments, tag filters, Git linkOne self-contained HTML filecollection-name-documentation.htmlOpen locallyStatic hostSend the fileDocs portal

Collection files go in, one self-contained HTML file comes out, and it can live anywhere static files do.

Generate from the app

  1. 1Open the collection's Overview tab (the first tab in Collection Settings) and click Generate Docs on the Documentation card. The same action is in the collection's ... menu in the sidebar.
  2. 2Under Environments to include, pick the environments readers should be able to switch between. Anything left unselected is not embedded in the file, not just hidden.
  3. 3Expand Advanced to filter requests by tag or to drop the Git repository link.
  4. 4Click Generate. Bruno writes collection-name-documentation.html to your file system.

Generate from the CLI

The CLI and the app share one implementation, so the same collection and options produce the same docs file. Run the CLI from the collection root, the folder that holds opencollection.yml:

terminal
$ bru docs generate --all-envs --exclude-tags "Work in progress" --no-git-link

That makes the docs a build artifact. A CI job can regenerate on every merge and publish the output:

CI step
$ bru docs generate \
  --all-envs \
  --exclude-envs Local \
  --exclude-tags "WIP,Internal" \
  --no-git-link \
  --output dist/docs/api.html

The flags in those two commands:

  • --all-envs embeds every environment in the collection. Use --envs Production,Staging instead to name specific ones.
  • --exclude-envs Local leaves the named environments out. It wins over --all-envs and --envs when the same name appears in both.
  • --exclude-tags "WIP,Internal" drops every request carrying any of those tags. --tags does the opposite, keeping only matching requests, and --exclude-tags wins when they overlap.
  • --no-git-link leaves the Git repository link out of the docs, for when the repo is private but the docs are public. Open in Bruno still appears, offering a .yml download and import steps instead of a clone, so readers can still open the collection in Bruno.
  • --output dist/docs/api.html (or -o) sets the output path. Without it, the file lands in the current directory as collection-name-documentation.html.

Every flag is listed in the bru docs generate options reference.

Host it anywhere

The output is one file with no server-side dependencies, so sharing it is as simple as moving a file:

  • Open it locally in any browser to check the result.
  • Deploy it to GitHub Pages, Netlify, Vercel, or any static host for a public URL.
  • Send the file directly to a partner or customer.
  • Drop it into an existing developer portal or docs site.

When the API changes, regenerate and replace the deployed copy. You can even automate this using the Bruno CLI.

How readers use the generated docs

The generated site is executable, not just readable. At first glance it reads like any other API reference. The difference is the Try button on every request.

What a reader sees on a generated docs pageEnvironment: ProductionShow varsSearch requestsOpen in BrunoAuthenticationLoginRefresh tokenUsersGet userList usersLoginPOST /auth/loginYour request docs, rendered from markdownParams · Headers · Body · AuthPre-request script, tests, assertionsCode samples: cURL, Python, JavaScriptDeep link: #/authentication/loginTryEdit params, headers, bodySendResponse200 OKtoken: eyJhbGci...scripts ran in the sandboxtests: 3 passed

The generated page: environment switcher and Open in Bruno up top, navigation on the left, the request in the middle, and Try on the right.

Try requests live. The playground lets readers edit params, headers, and body, send the request, and see the response in the page. It also shows the pre-request script, post-response script, tests, and assertions attached to the request, so the behavior is visible rather than implied.

Copy code samples. Every request ships with ready-to-copy snippets in cURL, Python (requests), and JavaScript.

Switch environments. The topbar environment switcher lets readers pick any environment you embedded. Turning on Show vars resolves placeholders across URLs, headers, bodies, and code snippets. Secret environment variables stay masked even with Show vars on.

Search. Full-text search covers request names, URLs, params, and descriptions.

Share deep links. Every folder, request, and saved example has its own URL. The path is built from the names in the collection, so a request called Login inside an Authentication folder lives at #/authentication/login, and one of its examples at #/authentication/login/<example-name>. Copy the address bar and send it: the link opens the page on that exact request, survives a reload, and works with the browser's back and forward buttons. Playground state is in the URL too, so a link can land a reader in Try for a specific request.

Open in Bruno. Every generated site has an Open in Bruno button. For Git-backed collections the repository URL is embedded by default, so the button works like Fetch in Bruno and clones the collection straight from the docs. Without a repository link, the button opens a dialog instead: a Download Collection button that saves the collection as a .yml file, the two steps to import it in Bruno, and a download link for readers who do not have Bruno yet.

Scripts run the way they do on your machine

When a reader hits Try, the playground runs the same collection, folder, and request scripts the desktop app would, in that order, inside a browser sandbox that matches Safe Mode. The bru, req, and res objects are the ones you already use. Environment, collection, folder, and request variables, bru.setVar, bru.getSecretVar, bru.sendRequest, bru.setNextRequest, and bru.interpolate all work.

A few APIs need the desktop app and are not available in the browser: bru.runner.*, the OAuth2 credential helpers, bru.cookies.*, and bru.visualize. If a script calls one of them, the playground shows a warning in the response pane naming the API instead of failing quietly. Developer Mode features such as filesystem access and extra npm packages are also out of reach in the browser.

The practical rule: if a script works in Safe Mode in Bruno, it works for your readers.

How to keep public collections and docs up to date

The two routes stay current in different ways, and it is worth knowing which one you are relying on.

The Git route updates itself; the docs route is a build you rerunGit routeDocs routeYou push a changeReaders pull the new versionThe README snippet never changesYou merge to mainCI runs bru docs generateThe hosted file is replaced

Git-shared collections update when readers pull; generated docs update when you regenerate and replace the file.

The Git route updates itself. A Fetch in Bruno button points at a repository, not at a copy. Push a change and every reader who cloned it can pull the new version from Bruno's Git UI. The button snippet in your README never needs to change.

The docs route is a build. The HTML file is a snapshot of the collection at the moment you generated it. To update public docs, regenerate and replace the deployed file. If that sounds like a chore, it is exactly the job for bru docs generate in CI: regenerate on every merge to main and publish the output to the same URL, so the docs can never drift behind the collection.

Publish more than one variant from the same collection. Tag filters are set per run, so you can generate once with an include tag like Production ready for the public file, then generate again with no filters for an internal copy. Both come from the same files, so nothing is maintained twice.

Set a collection version. Collections without one show Not Set in the generate modal and in the generated site. A version string in the collection overview gives readers a way to tell which iteration of the docs they are looking at.

Before you share: a checklist

Public means anyone. Run through this before the repository goes public or the HTML file goes live.

1. Keep credentials in secrets, not plain variables

Bruno has two different things called secrets. A secret environment variable is a regular variable whose value is masked and kept off disk. An external secret is not stored in Bruno at all; it is fetched from a secrets provider when a request runs. Both stay out of anything you publish, for different reasons.

  Secret environment variable (masked) External secret (fetched from a provider)
How you set it Mark a normal environment variable as Secret. Bruno masks it in the UI and stores the value encrypted on your machine instead of in the environment file Add an entry on the environment's External Secrets tab that points at HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault. Bruno fetches the value from the provider at run time; you never type it
In the Git repo Name only, no value Provider config and the secret's name or path, no value
In the generated docs Name only, shown as (Secret) Name only, no value; a browser cannot reach the manager

In the playground, a reader who wants to try a request types their own value. That works for secret environment variables: the value lives in that browser tab and is never saved.

External secrets are different. The reference comes through as a variable with no value and no field to fill in, so a request that depends on one cannot be tried as written. Readers have two ways around that: copy the cURL, Python, or JavaScript sample and drop in their own credential, or use Open in Bruno and run the request in the desktop app, where their own environment and secrets provider apply. If you want the Try button to work for everyone, embed an environment for the docs in which that credential is a secret environment variable instead. Readers then get a field to fill in, and your internal environments keep the external secret.

Heads up

What is not protected: anything typed straight into an Auth tab (a basic auth password, an OAuth2 client secret, AWS keys) is exported as written. Reference a secret environment variable or an external secret there instead, and move any token sitting in a plain variable or header to one of those before you publish.

2. Check every environment you embed

Each environment you select goes into the file with its full variable set: staging and internal base URLs, account IDs, test data. Include only what a public reader should see, and use --exclude-envs in CI for the rest.

3. Tag work in progress and internal endpoints, then exclude them

Excluded requests are absent from the file, not hidden. They are not in navigation, search, or the embedded collection data. If nothing is tagged yet, this is the moment to add tags.

4. Decide whether to ship the repository link

Git-backed collections embed the repository URL by default, so Open in Bruno clones the repository. Turn it off in Advanced, or pass --no-git-link, when the repository is private and the docs are public.

5. Read the written docs as a stranger

Descriptions come from the markdown at the collection, folder, and request levels. Requests without docs still appear, with nothing to explain them. Fill in the ones a first-time reader hits first.

6. Review scripts for desktop-only APIs

bru.runner, the OAuth2 credential helpers, bru.cookies, and bru.visualize show a warning in the playground. Readers can still send the request, but know which ones those are before a partner does.

Frequently asked questions

Do readers need a Bruno account to use a shared collection?

No account, ever. For the Fetch in Bruno route they need the free Bruno desktop app installed, and the button clones the repository straight into it. For generated docs they need only a browser: the file is self-contained and the playground runs client-side.

Are secret variables included in generated docs?

No. Secret environment variables are exported as a name with no value and shown as (Secret). External secrets keep only their provider config. Readers who want to try a request type their own value in the playground, which stays in that browser tab and is never saved.

Is the Fetch in Bruno button free?

Yes. Generating the button, the reader's clone, fetch, and pull, initializing Git, and generating docs are all in the free, open-source edition. Only Git actions inside the app, such as connecting a remote and pushing, need Pro or Ultimate; free users do those from the terminal.

Can I host generated Bruno docs on GitHub Pages?

Yes. The output is one static HTML file with no server-side dependencies, so it works on GitHub Pages, Netlify, Vercel, any static host, or inside an existing developer portal. Regenerate and replace the file when the API changes, ideally from CI with bru docs generate.

What is the difference between Fetch in Bruno and Open in Bruno?

Fetch in Bruno is the button you place on a README or website; it clones a public Git repository into the reader's app. Open in Bruno is the button inside generated docs: with a Git link it clones the same way, and without one it offers a .yml download plus import steps.

How do I update public docs when the API changes?

Regenerate the HTML file and replace the deployed copy. The CLI makes this a build step: run bru docs generate on every merge to main and publish the output to the same URL. Collections shared through Git need nothing from you; readers pull the new version themselves.

Where to start

If you maintain an open-source API or a public collection, add a Fetch in Bruno button to the README today. It is one line of Markdown and it gives every visitor a working, Git-synced collection in one click.

If you have partners or customers who need a reference without the tooling, generate the docs once from the collection's Overview tab, open the file in a browser, and see what they would see. Then wire bru docs generate into the pipeline that already ships your API so the docs never fall behind it.

Better still, do both. Host the generated docs at a public URL and keep the Fetch in Bruno button beside them, so a reader can start in the browser and move into Bruno the moment they want to edit a request or write a script. Both come from the same repository, so publishing one more surface costs nothing to maintain.

Either way, the collection you already keep in Git is the only thing you maintain.

Ready to share your API? Bruno is free and open source, and everything in this post works from the first install.

Download Bruno

Sharing with your own team instead? How to Share Bruno Collections covers Git sync, exports, and imports.

Docs