# Create a Collection
Source: https://docs.usebruno.com/get-started/bruno-basics/create-a-collection
A collection in Bruno is a structured group of API requests, tests, and related resources that helps you organize and manage your API testing workflow.
If you have an existing collection inside of another tool that you would like to import, please see [Importing Collections ↗](../import-export-data/import-collections).
## How to Create a Collection
1. Open Bruno and navigate to the **home screen**
2. Click on the `+` button and select **Create Collection** from the dropdown.
3. Enter a name for your collection.
4. **(Optional)**:
* To store the collection at a custom location: click the **settings icon** (advanced options) and choose the desired folder on your system.
* Click on **Options** dropdown and select **YAML** to change the file format to YAML or BRU -> YAML recommended.
If you skip this, the collection is stored at the default location: `~/Documents/bruno` (e.g. `/Users/username/Documents/bruno`). You can view or change the default location in **Preferences** → **General** tab. See [Settings](/get-started/configure/settings#general).
5. Choose or confirm the location on your filesystem for the collection.
6. Click **Create**
Starting with Bruno v3.1.0, YAML (OpenCollection format) is the default file format when creating new collections. Learn more about [OpenCollection YAML](/opencollection-yaml/overview).
## Automatic Git Configuration
When you create a new collection or workspace, Bruno automatically generates a `.gitignore` file with recommended settings to ensure proper Git hygiene out of the box.
**What's Included in `.gitignore`:**
* Temporary files and caches
* Environment variable files with sensitive data
* Generated metadata
* System-specific files
The automatically generated `.gitignore` file helps prevent accidentally committing sensitive information (like API keys in environment variables) or unnecessary files to version control.
## Custom Collection Names
You can use any characters in your collection names, including special characters like `/`, `[`, `]`, `*`, etc. Bruno automatically handles filesystem compatibility by replacing unsupported characters with `-` in the filenames while preserving your original collection name.
[Add a request ↗](https://docs.usebruno.com/get-started/bruno-basics/create-a-request) to your collection as a next step!
If you're looking to collaborate and share with your team, please see [Collaboration in Bruno ↗](https://docs.usebruno.com/git-integration/overview).
# Create a Folder
Source: https://docs.usebruno.com/get-started/bruno-basics/create-a-folder
Bruno helps you organize your API requests efficiently by grouping them into folders. You can create a structured hierarchy that matches your API's organization, making it easier to manage and navigate your collections.
## Creating a New Folder
1. Select your Collection from the sidebar.
2. Click on the `···` button.
3. Select `New Folder` from the dropdown menu.
4. Enter a name for your folder.
5. Click `Create` to finish.
Your new folder is now ready to organize your requests!
## Custom Folder Names
You can use any characters in your folder names, including special characters like `/`, `[`, `]`, `*`, etc. Bruno automatically handles filesystem compatibility by replacing unsupported characters with `-` in the filenames while preserving your original folder name.
### How to View or Customize Filesystem Names
1. Select **Show Filesystem Name.**
2. You can now see how your folder name is stored on the filesystem.
3. (Optional) Click the edit icon to customize the filesystem name while keeping your display name intact.
This feature allows you to:
* Create more descriptive folder organization.
* Use special characters freely.
* Maintain filesystem compatibility automatically.
* Keep your collection structure clean and organized.
### Example
You could name a folder "API \[v2.0] / Authentication" and Bruno will automatically handle the filesystem compatibility while preserving your desired display name.
# Create a Request
Source: https://docs.usebruno.com/get-started/bruno-basics/create-a-request
Bruno supports multiple ways to create API requests. You can work with HTTP, GraphQL, gRPC, WebSocket, or convert cURL commands into reusable requests.
## Create a first request
1. Open **Bruno** (workspace **Home**).
2. Click the **`+`** button to create a request.
3. Enter a URL (e.g. `https://api.github.com/users/userbruno`) and press **Enter**.
You can also create requests from a collection menu or without a collection. Read below sections to learn more.
**Three ways to create a request:**
1. **[Within a collection](#1-create-a-request-within-a-collection)** – Create a new request from the collection menu (saved to that collection).
2. **[Without a collection](#2-create-a-request-without-a-collection)** – Create an unsaved request from Workspace Home; save to a collection later (or discard).
3. **[Inline "+" button](#3-create-a-request-with-the-inline-button)** – Create a new request from the "+" icon next to the request tabs (within a collection context).
***
## 1. Create a request within a collection
Create a new request that belongs to a collection from the start:
1. Open your **Collection** and click the **`···`** button.
2. Select **New Request** in the dropdown menu.
3. In the dialog, set:
* **Request Type** (HTTP, GraphQL, or from cURL)
* **Request Name**
* **HTTP Method** (GET, POST, PUT, etc.)
* **URL**
4. Click **Create**.
The request is created in your collection. You can add headers, query parameters, body, and other options as needed.
***
## 2. Create a request without a collection
Send a request without creating or opening a collection first—useful for ad-hoc checks (e.g. verifying an endpoint) or quick cURL-style calls.
1. Open **Workspace Home** and click the **"+"** icon in the tabs area.
2. A new **unsaved request** opens in the editor (not tied to any collection).
3. **Configure and run** the request: URL, method, headers, auth, body, scripts, and tests are all supported.
4. **Save (optional):** Click **Save** to open a modal where you can:
* Save to an **existing collection** from the list of open collections, or
* **Create a new collection** and save the request there.
If no collections are open, the flow defaults to creating a new collection. After saving, you're taken to the collection tab with the request open.
5. **Find unsaved requests** again via the **Home** icon or the workspace/collection dropdown.
6. **Closing the workspace** with unsaved requests open will prompt you that those requests will be lost unless you save them.
***
## 3. Create a request with the inline "+" button
**Quick request creation (v3.1.0+):** Create requests from the **"+"** icon next to the request tabs—no collection menu needed. The new request inherits the current collection's settings.
### Create an HTTP request
* **Click the "+" icon** next to the request tabs to create a new HTTP request in a new tab.
### Create GraphQL, gRPC, or WebSocket requests
1. **Right-click the "+" icon** next to the request tabs.
2. Choose the request type: **HTTP**, **GraphQL**, **gRPC**, or **WebSocket**.
3. The new request opens in a new tab.
### How it works
* A new **"Untitled"** request opens immediately; no file is created until you save.
* The request **inherits collection-level settings** (auth, headers, etc.) and any **presets** from collection settings.
* When you **Save**, Bruno prompts you to choose a location (collection root or folder) and confirm the name.
This workflow is useful for rapid prototyping: create and run requests, then save only when you're satisfied.
***
## Custom Request Filenames
You can use any characters in your request names, including special characters like `/`, `[`, `]`, `*`, etc. Bruno automatically handles filesystem compatibility by replacing unsupported characters with `-` in the filenames while preserving your original request name.
### To view or customize the filesystem name
1. Select **Show Filesystem Name.**
2. You can now see how your request name is stored on the filesystem.
3. (Optional) Click the edit icon to customize the filename while keeping your display name intact.
This feature gives you complete freedom in naming your requests while ensuring filesystem compatibility.
# Create a Test
Source: https://docs.usebruno.com/get-started/bruno-basics/create-a-test
Bruno uses the [Chai library ↗](https://www.chaijs.com/), so you can follow the same syntax for writing
assertions in your tests.
To ensure your API is behaving as expected, create a test against it.
Bruno supports writing tests by through either:
* Assertions to declaratively write tests
* JavaScript
## Assertion
To get familiar with testing:
1. open a Collection
2. Select a request
3. Click on the `Assertions` tab
You can then dictate an expression, operator, and value to test against.
## Testing with JavaScript
Bruno also supports writing automation test scripts in JavaScript for more advanced API testing scenarios. By automating your tests, you can increase efficiency and coverage, and integrate testing into CI/CD pipelines.
#### Example
```js copy theme={null}
test("should be able to login", function () {
const data = res.getBody();
expect(res.getStatus()).to.equal(200);
});
test("should return json", function () {
const data = res.getBody();
expect(res.getBody()).to.eql({
hello: "Bruno",
});
});
```
In this example:
1. The first test checks that the response status is 200 (indicating a successful login).
2. The second test verifies that the response body is equal to the expected JSON object.
For more advanced testing scenarios and tips, view the
[Scripting ↗](https://docs.usebruno.com/scripting/getting-started) docs.
# Create a Workspace
Source: https://docs.usebruno.com/get-started/bruno-basics/create-a-workspace
A workspace in Bruno is a container that helps you organize multiple collections, API specifications, and environments together. Workspaces allow you to group related projects, making it easier to switch between different teams or projects while keeping your API development workflow organized.
Workspaces are available in [Bruno 3.0.0](https://www.usebruno.com/downloads) and higher versions.
## Why Use Workspaces?
Workspaces provide several benefits:
* **Project Organization** - Group related collections together by project, team, or client
* **Git Integration** - Store workspaces in version-controlled directories for easy collaboration
* **Environment Isolation** - Each workspace can have its own global environments
* **Quick Switching** - Easily switch between different projects without losing context
* **Pin Favorites** - Pin frequently used workspaces for quick access
## Types of Workspaces
Bruno supports two types of workspaces:
### Default Workspace
The Default Workspace is automatically created when you first launch Bruno. It is stored in Bruno's application data folder and serves as your starting point. This workspace cannot be closed or deleted.
### Custom Workspaces
Custom workspaces are stored in a location of your choice on your filesystem. This makes them ideal for:
* Syncing with version control systems like Git
* Sharing with team members
* Organizing collections by project or team
## Workspace Structure
When you create a workspace, Bruno creates the following structure on your filesystem:
```
my-workspace/
├── workspace.yml # Workspace configuration file
├── collections/ # Directory for collections
└── environments/ # Directory for global environments
```
The `workspace.yml` file contains the workspace configuration, including:
* Workspace name
* List of collections in the workspace
* API specifications
## How to Create a Workspace
1. Open Bruno and click on the **workspace dropdown** in the top-left corner.
2. Select **Create workspace** from the menu.
3. Enter a **name** for your workspace (1-255 characters).
4. **(Optional)** To store the workspace at a custom location: click the **settings icon** (advanced options) and choose the desired folder on your system.
If you skip this, the workspace is stored at the default location: `~/Documents/bruno` (e.g. `/Users/username/Documents/bruno`). You can view or change the default location in **Preferences** → **General** tab. See [Settings](/get-started/configure/settings#general).
5. Click **Browse** to choose or confirm the location on your filesystem (or use the default).
6. Click **Create Workspace**.
Bruno will create the workspace folder structure and automatically switch to the new workspace.
Workspace names must be unique. You cannot create a workspace with the same name as an existing workspace.
## Managing Workspaces
### Switching Between Workspaces
1. Click on the **workspace dropdown** in the top-left corner
2. Click on any workspace name to switch to it
The active workspace is indicated with a checkmark.
### Pinning Workspaces
You can pin frequently used workspaces for quick access:
1. Hover over a workspace in the dropdown menu
2. Click the **pin icon** to pin or unpin the workspace
Pinned workspaces appear at the top of the workspace list.
### Managing Workspaces
To access workspace management options:
1. Click on the **workspace dropdown** in the top-left corner
2. Select **Manage workspaces** from the menu
From the Manage Workspace screen, you can:
* **Open** - Switch to a different workspace
* **Show in folder** - Open the workspace location in your file explorer
* **Rename** - Change the workspace name (available from the workspace home screen)
* **Close** - Remove the workspace from Bruno (files remain on disk)
### Renaming a Workspace
1. Open the workspace you want to rename
2. Click on the **workspace home icon** (or navigate to the workspace overview)
3. Click the **menu icon** (three dots) next to the workspace name
4. Select **Rename** and enter the new name
## Opening Workspaces
Bruno's Free and Open Source versions support having two (2) Workspaces open at a time. If you'd like to have additional Workspaces open, consider upgrading to one of our [paid plans](https://www.usebruno.com/pricing).
If you have an existing workspace folder that you've either created or copied to your system (for example, cloned from a Git repository or received from a colleague in a .zip):
1. Click on the **workspace dropdown** in the top-left corner
2. Select **Open workspace** from the menu
3. Navigate to and select the workspace folder containing `workspace.yml`
4. Click **Open**
Bruno will validate the workspace configuration and open it.
## Importing a Workspace
Workspaces can be imported from zip files or from git repositories.
To import a workspace:
1. Click on the **workspace dropdown** in the top-left corner
2. Select **Import workspace** from the menu
3. Follow the import wizard to complete the process from a `File` or `Git Repository`
For detailed instructions on importing and exporting workspaces, including Git-based workflows, see the [Import/Export Workspace Guide](/get-started/import-export-data/import-export-workspace).
## Closing a Workspace
To close a workspace (remove it from Bruno without deleting files):
1. Open the workspace you want to close
2. Navigate to the workspace overview
3. Click the **menu icon** and select **Close**
Closing a workspace only removes it from Bruno's workspace list. The files remain on your filesystem and can be re-opened at any time.
## Next Steps
Once you've created a workspace, you can [create a collection](./create-a-collection) inside it to start organizing your API requests.
# Download & Install
Source: https://docs.usebruno.com/get-started/bruno-basics/download
Bruno is an open-source, fast and lightweight API client for exploring and testing APIs. It's designed to be a modern alternative to traditional API clients with a focus on speed, simplicity, and collaboration.
## Installation Options
Bruno can be installed via direct download or through package managers on supported platforms.
Starting with Bruno v3, native ARM64 binaries are available for [Windows ARM64 devices ↗](https://www.usebruno.com/downloads).
### Direct Download
Download the latest macOS version from the [official downloads page ↗](https://www.usebruno.com/downloads).
### Homebrew
```bash theme={null}
brew install bruno
```
### Direct Download
Download the latest Windows installer from the [official downloads page ↗](https://www.usebruno.com/downloads).
**Enterprise MSI Installation**
When installing Bruno using an MSI installer, IT administrators can control auto-update behavior by passing the `AUTOUPDATE_ENABLED` flag during installation:
* Set to `false` to disable auto-updates enterprise-wide
* If auto-updates are disabled by the enterprise, users cannot enable them manually
* If no enterprise setting exists, Bruno respects individual user preferences
Example: `msiexec /i Bruno.msi AUTOUPDATE_ENABLED=false`
### Using Package Managers
**Chocolatey**
```bash theme={null}
choco install bruno
```
**Winget**
```bash theme={null}
winget install Bruno.Bruno
```
**Scoop**
```bash theme={null}
scoop install bruno
```
### Direct Download
Download the latest Linux package from the [official downloads page ↗](https://www.usebruno.com/downloads).
### Using Package Managers
**APT (Debian/Ubuntu)**
```bash theme={null}
# Create keyrings directory
sudo mkdir -p /etc/apt/keyrings
# Update and install GPG and curl
sudo apt update && sudo apt install gpg curl
# List existing keys (optional)
sudo gpg --list-keys
# Add the Bruno repository key
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x9FA6017ECABE0266" | gpg --dearmor | sudo tee /etc/apt/keyrings/bruno.gpg > /dev/null
# Set permissions for the GPG key file
sudo chmod 644 /etc/apt/keyrings/bruno.gpg
# Add the Bruno repository
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
# Update and install Bruno
sudo apt update && sudo apt install bruno
```
**Flatpak**
```bash theme={null}
flatpak install flathub com.usebruno.Bruno
```
**Snap**
```bash theme={null}
sudo snap install bruno
```
## Nightly Builds
Nightly builds are **pre-release** and may contain bugs. Use them to preview
upcoming features and provide feedback - not for production.
Nightly builds are published automatically for early testing of upcoming features. To get started, visit the [bruno-nightly-builds ↗](https://github.com/usebruno/bruno-nightly-builds) repository on GitHub and download the asset that matches your OS.
Find the latest release in the [nightly builds repository ↗](https://github.com/usebruno/bruno-nightly-builds/releases) and download the installer for your platform:
| Platform | File to download |
| --------------------- | ------------------------------ |
| macOS (Apple Silicon) | `.dmg` (arm64) |
| macOS (Intel) | `.dmg` (x64) |
| Windows | `.exe` or `.msi` installer |
| Linux | `.AppImage`, `.deb`, or `.rpm` |
Install it the same way as a regular Bruno release.
The CLI nightly build is published as an npm tarball (`.tgz`). Installing it in an **isolated folder** keeps it separate from any globally installed version.
**1. Prepare a test folder**
Download the `.tgz` file from the [nightly builds repository ↗](https://github.com/usebruno/bruno-nightly-builds/releases) and keep the original filename unchanged. Then create a clean folder and move the file into it:
```bash theme={null}
mkdir cli-portable-nightly
mv usebruno-cli-*.tgz cli-portable-nightly/
cd cli-portable-nightly
```
**2. Install the bundle locally**
```bash theme={null}
npm init -y
npm install --offline ./usebruno-cli-.tgz
```
**3. Verify the installation**
```bash theme={null}
npx bru --version
# Expected output: the nightly version number, e.g. 3.5.0
```
**4. Run your collection using the local CLI**
The nightly binary is installed inside `cli-portable-nightly/node_modules/.bin/bru`. When you want to run a collection that lives in a **different folder**, you must use the full absolute path to that binary.
Using `./node_modules/.bin/bru` only works from inside the `cli-portable-nightly` folder itself.
```bash theme={null}
# Step into your collection folder
cd /path/to/your-collection
# macOS / Linux — use the absolute path to the nightly binary
/path/to/cli-portable-nightly/node_modules/.bin/bru run --env
# Windows
C:\path\to\cli-portable-nightly\node_modules\.bin\bru run --env
```
If you are not sure of the path, run `pwd` while inside `cli-portable-nightly` to get it:
```bash theme={null}
cd /path/to/cli-portable-nightly
pwd
# e.g. /Users/yourname/Downloads/cli-portable-nightly
```
Then use that output as the prefix:
```bash theme={null}
cd /path/to/your-collection
/Users/yourname/Downloads/cli-portable-nightly/node_modules/.bin/bru run --env
```
To avoid typing the full path every time, export it as a shell variable for your session:
```bash theme={null}
export NIGHTLY_BRU=/Users/yourname/Downloads/cli-portable-nightly/node_modules/.bin/bru
cd /path/to/your-collection
$NIGHTLY_BRU run --env
```
***
## Release Information
Bruno follows semantic versioning. You can check the [GitHub Releases ↗](https://github.com/usebruno/bruno/releases) page for:
* Latest version information
* Release notes
* Bug fixes
* New features included in each release
# Run a Collection
Source: https://docs.usebruno.com/get-started/bruno-basics/run-a-collection
Running a Bruno collection lets you exercise and test an API. You can run collections from the app (**Collection Runner**) or from the command line (**Bruno CLI**).
### Collection Runner
The built-in Collection Runner supports **unlimited** runs.
1. **Open the Collection Runner** using either method:
* In the sidebar, click **`···`** next to your collection, then select **Run**.
* Or click the **Collection Runner** icon in the top bar.
2. **Configure the run** (optional): In the runner, set **parameters**, choose a **delay** between requests, and adjust any other options you need.
3. **Start the run** to execute the collection from the runner.
The Collection Runner does not include gRPC and WebSocket requests. Only HTTP requests will be executed when running a collection.
**Data-driven testing** (running a collection with a **CSV** data file) is available in [Bruno Pro](https://www.usebruno.com/pricing) and [Bruno Ultimate](https://www.usebruno.com/pricing).
### Automating Runs in CI/CD
1. **Use the Bruno CLI** to run collections from scripts and pipelines (no GUI required).
2. **Follow the CLI docs** to install the CLI, run a collection, and wire it into your CI/CD jobs: [Bruno CLI ↗](https://docs.usebruno.com/bru-cli/overview)
# JavaScript Sandbox
Source: https://docs.usebruno.com/get-started/configure/javascript-sandbox
Bruno provides two modes for executing JavaScript code in your collections:
1. Safe Mode
2. Developer Mode
**Bruno CLI v3.0.0 Update**: When using Bruno CLI, the default mode is now **Safe Mode**. To use Developer Mode features in CLI, you must pass the `--sandbox=developer` flag. Learn more in the [Bruno CLI documentation](/bru-cli/runCollection#running-with-developer-mode).
## Safe Mode
JavaScript code is executed in a secure sandbox and cannot access your filesystem or execute system commands. We recommend Safe Mode for most users.
When in doubt, leave the Collection in Safe Mode. You can always switch to Developer Mode later.
## Developer Mode
JavaScript code has access to the filesystem, can execute system commands and access sensitive information.
#### When to use Developer Mode
* You trust the collection source/authors (Ex: Collection maintained by you/your team) and Safe Mode is not enough for your use case.
* You need to use external npm packages in your scripts
* Your collection needs access to filesystem / system commands
#### When to avoid Developer Mode
* You are running a collection that you do not trust (Ex: Downloaded from the internet)
Developer Mode can be dangerous and should only be used when you fully understand the implications.
## Detecting Sandbox Mode in Scripts
You can programmatically detect the current sandbox mode in your pre-request, post-request, and test scripts using the `bru.isSafeMode()` API.
**Use Case:** Some collections may require explicit Developer Mode features. By detecting the sandbox mode, you can log appropriate error messages or take alternative actions when the collection is run in Safe Mode.
**Example:**
```javascript theme={null}
// Check if running in Safe Mode
if (bru.isSafeMode()) {
console.log('⚠️ This collection requires Developer Mode.');
console.log('Please switch to Developer Mode to use filesystem and external packages.');
} else {
console.log('✓ Running in Developer Mode - all features available.');
// Proceed with Developer Mode features
const fs = require('fs');
// ... use filesystem operations
}
```
**API Reference:** See the complete documentation in [JavaScript API Reference](/testing/script/javascript-reference#detecting-sandbox-mode).
# Configuring Proxy Settings in Bruno
Source: https://docs.usebruno.com/get-started/configure/proxy-config
A proxy is an intermediary server that sits between a client application (like Bruno) and the destination server that the client is communicating with (like an API). The proxy server acts as a security barrier, making requests on your behalf to websites and other internet resources, and preventing others from accessing your internal network.
If your machine sits behind a corporate network or firewall, you may need to configure the proxy settings in Bruno in order for requests to be made.
## Try it out
Try the [proxy\_configuration](https://github.com/bruno-collections/proxy_configuration) sample collection to see proxy setups in action:
Starting from v3.1.0+, **System Proxy is enabled by default for new users** and Existing users retain their current preference settings.
Below are steps for configuring your proxy settings at different levels within Bruno.
## 1. Accessing Proxy Settings
1. Open Bruno.
2. Click on the **Preferences** section from the bottom left corner.
3. Navigate to the **Proxy** tab in the sidebar.
## 2. Configuring Proxy Mode
Bruno provides four options for proxy mode:
* **Off**: Disables proxy usage.
* **On**: Enables proxy and routes requests through a manually configured proxy.
* **System Proxy**: Uses the system-wide proxy settings.
* **PAC**: Uses a [Proxy Auto-Configuration (PAC)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file) file to determine the proxy for each request.
Select **On** if you want to configure a custom proxy, or **PAC** if your network uses a PAC file for automatic proxy configuration.
Bruno automatically detects and uses the system proxy configuration, including PAC (Proxy Auto-Config) settings configured at the operating system level.
## 3. Selecting Proxy Protocol
Bruno supports different proxy protocols. Choose one of the following:
* **HTTP**
* **HTTPS**
* **SOCKS4**
* **SOCKS5**
## 4. Entering Proxy Details
Fill in the required fields:
* **Hostname**: The proxy server's domain or IP address (e.g., `example.com`).
* **Port**: The port number for the proxy connection (e.g., `3000`).
## 5. Enabling Authentication (Optional)
If your proxy requires authentication:
1. Check the **Auth** checkbox.
2. Enter the **Username**.
3. Enter the **Password** (Click the eye icon to reveal the password if needed).
## 6. Configuring PAC proxy
When you select the **PAC** proxy mode, Bruno resolves the appropriate proxy for each request using a PAC file. You can provide the PAC file in two ways:
* **URL**: Enter the URL to your PAC file (e.g., `https://example.com/proxy.pac`). The URL must use the `http://`, `https://`, or `file://` protocol.
* **File**: Click **Choose file...** to select a `.pac` file from your local filesystem.
Bruno evaluates the `FindProxyForURL` function in the PAC file for each request and routes traffic through the returned proxy.
## 7. Configuring Proxy at Collection Level
There may be instances in which your collection needs to use a different proxy than what you have configured globally. Configuring proxy settings at the collection level will override any configuration you've made in the preferences section.
Below are instructions on proxy settings at the collection level:
1. Open the collection settings.
2. Navigate to the **Proxy** tab.
### **Choose the Configuration Mode:**
* **inherit**: Uses the main proxy settings from Preferences.
* **Enabled**: Enables a custom proxy for this collection.
* **Disabled**: Disables the proxy for this collection.
3. Choose **inherit, enabled, or disabled**.
4. Enter the **Hostname, Port, Protocol, Username, and Password** if needed.
5. Click **Save** to apply the settings.
# Settings
Source: https://docs.usebruno.com/get-started/configure/settings
You can customize Bruno settings according to your preferences. Bruno offers a variety of settings that can be tailored to suit your needs. Key settings include:
* [General](#general)
* [Themes](#themes)
* [Display](#display)
* [Proxy](#proxy)
* [License](#license)
* [Keybindings](#keybindings)
* [Secrets](#secrets)
* [Support](#support)
* [About](#about)
* [Telemetry](#telemetry)
These are the core settings that you can adjust to optimize your Bruno experience.
## General
The General settings include options for SSL/TLS, Custom CA certificates, and Cookies. You can customize how cookies are handled by choosing to store and send them automatically, or simply uncheck the box if you prefer not to store cookies.
## Themes
Bruno offers a variety of themes to personalize your workspace. You can choose from different color schemes to match your preference and enhance your visual experience while working with APIs.
## Display
In the Display section, you can control the appearance of the Bruno, choosing between Dark, Light, or System mode, based on your preference. Additionally, you can customize the code editor font to suit your style.
## Proxy
You can customize the Proxy settings by selecting On, Off, or System, depending on your needs. For more details, you can refer to the [Proxy guide here](./proxy-config) to learn more. By default, the Proxy setting is Off in Bruno.
## License
Bruno offers Ultimate and Pro plans for premium users. You can refer to the [Licensing guide](/license-management/overview) to learn more about the features and benefits of each plan.
## Keybindings
Keybinding is available in Bruno **3.3.0+** or higher versions.
You can customize the keybindings to suit your preferences.
1. Go to **Keybindings** tab from **Preferences** bottom left corner.
2. Click and enter the keybinding you want to customize.
When defining keybindings, use at least one modifier key. On macOS, supported modifiers are Command, Option, Control, or Shift. On Windows, use Ctrl, Alt, or Shift.
Operating system level keybindings cannot be used as shortcuts in Bruno.
Click on **Reset Default** button to reset all keybindings to their default values.
## Secrets
There are three primary ways to manage secrets in Bruno, which you can learn about in the [Secret Management Overview](/secrets-management/overview).
This setting controls the integration with a Vault for secret management, which is available in Bruno **Ultimate Edition**.
## Support
If you encounter issues or need assistance, you can access a range of troubleshooting resources to help resolve your problem quickly.
## About
View application details and enable the auto-update feature to keep your app up to date automatically.
## Telemetry
Bruno collects limited, anonymized usage telemetry to help improve the product. You have full control over this and can opt out at any time for privacy or compliance reasons.
The **Telemetry** toggle is available in the global preferences panel under the **About** tab (refer to the [About](#about) section for more details).
The telemetry is enabled by default but you can disable this from the About page in Preferences.
# Exporting Collections
Source: https://docs.usebruno.com/get-started/import-export-data/export-collections
If your whole team isn't using Bruno (yet), no fear! You can export Bruno collections to other formats.
Our preferred recommendation for sharing collections is using native Git versioning, but we realize there are other scenarios where exporting makes sense.
Bruno allows you to export as:
* Bruno Collection
* Postman Collection
* OpenAPI Specification
To export:
1. Simply hover on the Collection you'd like to export
2. Click the `...` menu and select `Share`
3. You can then chose the format you'd like to export to and then designate a location for the file to be saved.
# Import collections
Source: https://docs.usebruno.com/get-started/import-export-data/import-collections
Bruno supports importing collections from multiple tools and formats, making it easy to transition from other API clients or work with API specifications. This guide provides step-by-step instructions for each import format.
## How to Access Import Collection
You can import collections in two ways:
**Option 1: From Home Screen**
Click **Import Collection** on the Bruno home screen.
**Option 2: From the + menu**
Click the **+** button in the top-left corner and select **Import Collection**.
**Import Options Dialog**
Once the import dialog opens, you'll see all available import formats. Select the one that matches your source.
You can also import collections from GitHub directly with in App. See [GitHub Provider](/git-providers/github) for more details.
***
## Import Formats
### 1. Bruno Collection
**Use this when:** A colleague shares their Bruno collection with you or you're restoring a backup.
**Steps:**
1. Open Bruno
2. Click the **+** button in the top-left corner and select **Import Collection**.
3. Browse and select your Bruno collection folder
4. Click **Import**
Your collection with all requests, folders, and configurations will be imported.
***
### 2. Postman Collection
**Use this when:** You're migrating from Postman or importing collections shared by Postman users.
**Steps:**
1. **Export from Postman:**
* Open Postman
* Click on your collection → **Export**
* Choose **Collection** format
* Save the `.json` file
2. **Import to Bruno:**
* Open Bruno
* Click the **+** button in the top-left corner and select **Import Collection**.
* Drag and drop your exported `.json` file, or click to browse
* Click **Import**
**Learn more:** [Postman Migration](/get-started/import-export-data/postman-migration)
***
### 3. Postman Data Export
**Postman Data Export** is available in our **Ultimate Edition**. This allows you to import all of your Postman data including collections, environments, and globals in one go.
**Use this when:** You want to migrate all of your Postman data to Bruno.
**Steps:**
1. **Export from Postman:**
* Open Postman
* Go to **Settings** → **Data**
* Click **Export Data**
* Choose what to include (collections, environments, globals)
* Download the export file
2. **Import to Bruno:**
* Open Bruno
* Click the **+** button in the top-left corner and select **Import Collection**.
* Select your exported data file
* Click **Import**
**Complete guide:** [Postman Migration Guide](/get-started/import-export-data/postman-migration)
***
### 4. Insomnia Collection
**Use this when:** You're migrating from Insomnia or importing collections from Insomnia users.
**Steps:**
1. **Export from Insomnia:**
* Open Insomnia
* Click on your workspace/collection
* Go to **Application** menu → **Preferences** → **Data**
* Click **Export Data**
* Choose **Export Collections** or **Export Workspace**
* Save the exported file
Insomnia v4 exports collections in `.json` format, while Insomnia v5 and later export in `.yaml` format. Bruno supports importing both formats.
2. **Import to Bruno:**
* Open Bruno
* Click the **+** button in the top-left corner and select **Import Collection**.
* Drag and drop your exported file (`.yaml` or `.json`), or click to browse
* Click **Import**
***
### 5. Cloning a Git Repository
**Use this when:** Your team stores Bruno collections in a Git repository for version control and collaboration.
**Prerequisites:**
* Make sure the Git repository contains a valid Bruno collection
* Ensure you have access credentials for the repository
**Steps:**
1. Open Bruno
2. Click the **+** button in the top-left corner and select **Import Collection**.
3. Select **Clone from Git Repository**
4. Enter the Git repository URL
5. Choose the local destination folder
6. Click **Clone**
The collection will be cloned and you can start using it immediately. Any changes you make can be committed back to the repository.
**Complete guide:** [Git Integration Overview](/git-integration/overview)
***
### 6. OpenAPI Specification
**Use this when:** You have an OpenAPI (Swagger) specification file and want to generate API requests from it.
#### Import from OpenAPI File
**Steps:**
1. **Obtain your OpenAPI file:**
* Get your OpenAPI specification file (`.json` or `.yaml` format)
* Use a valid **OpenAPI 2.0** (Swagger 2.0) or **OpenAPI 3.x** spec
2. **Import to Bruno:**
* Open Bruno
* Click the **+** button in the top-left corner and select **Import Collection**.
* Drag and drop your OpenAPI file (`.json` or `.yaml`), or click to browse
* Click **Import**
#### Import from OpenAPI URL
**Steps:**
1. Open Bruno
2. Click the **+** button in the top-left corner and select **Import Collection**.
3. Enter the OpenAPI specification URL (e.g., `https://api.example.com/openapi.json`)
4. Click **Import**
Bruno will automatically generate requests for all API endpoints defined in the specification.
**Learn more:** [OpenAPI Guide](/open-api/importOAS)
***
### 7. WSDL (Web Services Description Language)
**Use this when:** You're working with SOAP-based web services and have a WSDL file.
#### Import from WSDL File
**Steps:**
1. **Obtain your WSDL file:**
* Get the `.wsdl` file from your SOAP service provider
* Or download it from the service endpoint (usually available at `?wsdl`)
2. **Import to Bruno:**
* Open Bruno
* Click the **+** button in the top-left corner and select **Import Collection**.
* Drag and drop your `.wsdl` file, or click to browse
* Click **Import**
**Complete guide:** [SOAP Requests](/converters/wsdl-to-bruno)
***
## After Import
Once your collection is imported:
1. **Review the requests** - Check that all requests imported correctly
2. **Configure environments** - Set up variables for different environments (dev, staging, production)
3. **Test the requests** - Execute a few requests to ensure they work as expected
4. **Organize** - Create folders and organize requests if needed
5. **Set up authentication** - Configure authentication methods if required
## Need Help?
If you encounter issues during import:
* Check the format of your export file
* Ensure you're using the latest version of Bruno
* Visit our [GitHub Discussions](https://github.com/usebruno/bruno/discussions) for community support
* Report bugs on [GitHub Issues](https://github.com/usebruno/bruno/issues)
# Import Export Environment Variables
Source: https://docs.usebruno.com/get-started/import-export-data/import-export-environments
Make sure you have Bruno version 2.14 or later installed to use this feature.
You can import and export environment variables (collection and global) to share them across teams or back them up for later use.
## Accessing Import/Export Options
1. Go to the **Environments** section (top right corner).
2. Click on **Configure** to access the environment settings.
3. You will see the **Import** and **Export** options in the left sidebar.
## Importing Environment Variables
The import feature accepts environment files in both **Bruno** and **Postman** formats.
### Steps to Import
1. Click on the **Import** button in the environment configuration screen.
2. Select the environment file you want to import.
3. The variables will be added to your environment (global or collection, depending on which you're configuring).
## Exporting Environment Variables
### Steps to Export
1. Go to the **Environments** section.
2. Click on **Configure**.
3. Click the **Export** button.
4. If you have multiple environments, select which environments you want to export.
5. Choose the location where you want to save the exported JSON file.
6. The environment variables will be saved as a JSON file that can be shared or backed up.
The export procedure is the same for both collection and global environment variables.
Secret variables (variables marked as secret) will not be included in the exported JSON file for security reasons.
# Import and Export Workspace
Source: https://docs.usebruno.com/get-started/import-export-data/import-export-workspace
Bruno workspaces allow you to group multiple collections together in a single workspace. You can import and export entire workspaces to easily share them with your team or migrate between environments.
A workspace can contain multiple collections, along with global environment variables and workspace-level settings.
## Export Workspace
Exporting a workspace allows you to package all collections and configurations into a single file for sharing or backup.
### Steps to Export a Workspace
1. Open Bruno and navigate to your workspace
2. Click the `...` (ellipsis) menu at the top
3. Select **Export Workspace**
4. After export you will get `.zip` file with all collections and configurations.
***
## Import Workspace
You can import a workspace in two ways: from a ZIP file or by cloning from a Git repository.
### Option 1: Import from ZIP File
**Use this when:** You have an exported workspace file or received a workspace backup from a colleague.
**Steps:**
1. Open Bruno
2. Go to **Workspace** section and select **Import workspace** option from the dropdown menu.
3. Select **Import Workspace**
4. Choose **From ZIP File**
5. Browse and select your workspace `.zip` file
6. Choose a destination folder on your filesystem
7. Click **Import**
8. After import you will see all collections and configurations in the workspace.
***
### Option 2: Import from Git Repository
**Use this when:** Your workspace is stored in a Git repository for version control and team collaboration.
**Prerequisites:**
* The Git repository must contain a valid Bruno workspace
* You need appropriate access credentials for the repository
**Steps:**
1. Open Bruno
2. Go to **Workspace** section and select **Import workspace** option from the dropdown menu.
3. Select **Import Workspace**
4. Choose **Clone from Git Repository**
5. Enter the Git repository URL
6. Choose a local destination folder
7. Click **Clone**
The workspace will be cloned with full Git history. Any changes you make can be committed and pushed back to the repository.
***
# Migrating from Postman
Source: https://docs.usebruno.com/get-started/import-export-data/postman-migration
Bruno makes migrating from Postman easy. All you need to do is export your collections and environments and import them to Bruno.
## Collection Export
Open Postman and select the collection you want to migrate. Click on the `···` followed by `View more actions` to open the dropdown menu and scroll down until you find `Export`, then click on it.
A popup dialog will appear, select either `Collection v2` or `Collection v2.1` format and click on the `Export` button.
The collection will be downloaded as a JSON file.
Now simply follow the instructions for [Importing Collections](/get-started/import-export-data/import-collections) and you're done!
## Export Data Dump
Bulk Import from Postman is included in Bruno Ultimate Edition
You can export a data dump of all your collections and environments in Postman. You can then import the data into any Bruno.
**To [export a data dump ↗](https://learning.postman.com/docs/getting-started/importing-and-exporting/exporting-data/#export-data-dumps), do the following in Postman:**
* Select the settings icon in the header, then select `Settings`.
* Select the Data tab, then select `Request Data Export`.
* Select `Export Data`, then select the data types you want to export. You can export collections, environments, or both.
* Select `Request Data Export`.
**To import your Data Dump to Bruno:**
* Open the import menu and select `Import Collections`
* Select the zip file where the export was saved
* You will now see a list of all collections available for import. By default, all collections will be selected. You can chose to omit any from the import that you'd like.
* Designate a location for the collections and press `import`
Bruno automatically translates common Postman script APIs (such as `pm.test`, `pm.environment`, `pm.globals`, `pm.collectionVariables`, and `pm.response`) to their Bruno equivalents during import.
See the [Scripts Translator](/get-started/import-export-data/script-translator) page for the full list of supported translations.
## Environment Import and Export
Environments in API clients are sets of variables that allow you to run requests against different server configurations (development, staging, production) without changing your request URLs.
### Exporting Environment from Postman
1. Open Postman and locate the **Environments** section in the sidebar
2. Find the environment you want to export
3. Click on the three dots (**...**) next to the environment name
4. Select **Export** from the dropdown menu
5. Choose a location to save the JSON file and click **Save**
### Importing Environment into Bruno
#### Importing Your First Environment
1. Open Bruno
2. Click on the **Environments** icon in the top-right corner
3. Click on **Configure environments**
4. You'll see options to **Create environment** and **Import environment**
5. Click on **Import environment**
6. Select the Postman environment JSON file you exported
7. The environment will be imported with all its variables
#### Adding to Existing Environments
1. Open Bruno
2. Click on the **Environments** icon in the top-right corner
3. Click on **Configure environments**
4. Look for the **Import** button at the left sidebar
5. Click on **Import environment**
6. Select the Postman environment JSON file you exported
7. The environment will be added to your existing environments
With environments properly configured, your API requests will work seamlessly across different deployment environments without manual changes.
Note - Bruno environment names and variables must only contain alphanumeric characters, "-", "\_", "." and cannot start with a digit. Non-conforming characters (such as "/") will be converted to "-".
Postman allows variable values to be numbers, booleans, or objects, but Bruno stores all variable values as strings. During import, non-string values are automatically converted — for example, `5000` becomes `"5000"`, `true` becomes `"true"`, and objects are serialized as JSON strings.
# Scripts Translator
Source: https://docs.usebruno.com/get-started/import-export-data/script-translator
Welcome to the **Postman** to **Bruno** scripts translator.
This is the same function that runs behind the **Import Postman Collection** feature in Bruno.
## Try it out
# Collaboration via Azure DevOps
Source: https://docs.usebruno.com/git-integration/azure-devops
Azure DevOps is a comprehensive suite of development tools provided by Microsoft that enables teams to plan, develop, test, and deliver software efficiently. It offers integrated services including Azure Repos for Git repositories, Azure Pipelines for CI/CD, Azure Boards for work tracking, and more—making it a complete DevOps platform for enterprise teams.
Bruno's Git functionality allows you to work with Azure DevOps to manage, collaborate, and maintain your API development workflow.
## Configure Azure DevOps
You need to set up an SSH key with Azure DevOps. While you can also set up personal access tokens (HTTPS), this requires you to enter your token on every operation. SSH is a more convenient and secure way to work with Azure DevOps.
### Create Repository on Azure DevOps
To get started, you have to create a project/repository on Azure DevOps. Follow these steps:
1. Log into your Azure DevOps account at [dev.azure.com](https://dev.azure.com) (create one if you don't have one)
2. Click on **+ New project** or select an existing project
3. If creating a new project, fill in the required fields (Project name, Description, Visibility)
4. Click **Create**
5. Navigate to **Repos** in the left sidebar
6. If you need to create a new repository within the project, click **+ New repository**
Once you have a repository, click on the **Clone** button in the top-right corner and copy the SSH URL, which looks similar to:
```bash theme={null}
git@ssh.dev.azure.com:v3///
```
### Configure SSH Key
Follow these steps to configure an SSH key on your local system:
#### 1. Generate SSH Key
Open your terminal and execute:
```bash theme={null}
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_azuredevops
```
#### 2. Add SSH Key to SSH Agent
Add the SSH key to your SSH agent so it can be used for authentication:
```bash theme={null}
ssh-add ~/.ssh/id_ed25519_azuredevops
```
#### 3. Add the SSH Key to Your Azure DevOps Account
1. Go to **Azure DevOps** > **User Settings** (click your avatar in the top-right) > **SSH public keys**
2. Click **+ New Key**
3. Give your key a name (e.g., "Bruno Development")
4. Paste your `id_ed25519_azuredevops.pub` content in the **Public Key Data** field
5. Click **Add**
## Use Azure DevOps with Bruno
1. Create a Collection inside Bruno (if you haven't already)
2. Click on the **Git** icon (next to Safe Mode) and select **Initialize repo**
3. Click on **main** and select **Remotes** from the dropdown
4. Add **origin** as the name of the remote and paste your repository URL (copied previously)
Now you can perform push, pull, and other Git operations directly from Bruno.
For more detailed information about Git operations in Bruno, check out the [Collaboration via GUI documentation](/git-integration/using-gui/intro).
# Collaboration via Bitbucket
Source: https://docs.usebruno.com/git-integration/bitbucket
Bitbucket is a Git hosting web-based platform created by Atlassian. It's a popular choice for teams and organizations for collaborating and maintaining software projects. It's easy to integrate with Jira, Wiki, and other Atlassian products to manage everything in one place.
Bruno's Git functionality allows you to work with Bitbucket to manage, collaborate, and maintain your API development workflow.
## Configure Bitbucket
You need to set up an SSH key with Bitbucket. While you can also set up app passwords (HTTPS), this requires you to enter your password on every operation. SSH is a more convenient and secure way to work with Bitbucket.
### Create Repository on Bitbucket
To get started, you have to create a project/repository on Bitbucket. Follow these steps:
1. Log into your Bitbucket account (create one if you don't have one)
2. Click on the **Create** button and select **Repository** from the dropdown list
3. Fill in all required fields and click **Create Repository**
Once you create a repository, click on the **Clone** button and copy the URL, which looks similar to:
```bash theme={null}
git@bitbucket.org:/.git
```
### Configure SSH Key
Follow these steps to configure an SSH key on your local system:
#### 1. Generate SSH Key
Open your terminal and execute:
```bash theme={null}
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_bitbucket
```
#### 2. Add SSH Key to SSH Agent
Add the SSH key to your SSH agent so it can be used for authentication:
```bash theme={null}
ssh-add ~/.ssh/id_ed25519_bitbucket
```
#### 3. Add the SSH Key to Your Bitbucket Account
1. Go to **Bitbucket** > **Personal Settings** > **SSH Keys**
2. Click **Add Key**
3. Paste your `id_ed25519_bitbucket.pub` content
## Use Bitbucket with Bruno
1. Create a Collection inside Bruno (if you haven't already)
2. Click on the **Git** icon (next to Safe Mode) and select **Initialize repo**
3. Click on **main** and select **Remotes** from the dropdown
4. Add **origin** as the name of the remote and paste your repository URL (copied previously)
Now you can perform push, pull, and other Git operations directly from Bruno.
For more detailed information about Git operations in Bruno, check out the [Collaboration via GUI documentation](/git-integration/using-gui/intro).
# Fetch in Bruno Button
Source: https://docs.usebruno.com/git-integration/embed-bruno-collection
Bruno allows you to share your Git-synchronized workspace and collections via an embedded "Fetch in Bruno" (FiB) button turning the import/cloning process into a single click!
This button can be placed in websites, articles, and documentation, with support for both HTML and Markdown formats.
## Import workspace and collections from Git
This feature helps you to share your workspace and collections in an easy and flexible way - meeting the user wherever they are. Once imported, the user now has a Git-synced collections and can seamlessly pull down new changes as updates are made.
1. [How to share your collections](#how-to-share-your-collections)
2. [How to share your workspace](#how-to-share-your-workspace)
### Things You Need
1. Bruno installed on your system.
2. A collection or workspace that has been initialized as a Git repository [either through the UI](https://docs.usebruno.com/git-integration/using-gui/provider#initializing-git) or from [the CLI](https://docs.usebruno.com/git-integration/using-cli#initializing-git-in-bruno-collection).
### How to Share Your Collections
1. Open Bruno.
2. Either go to the collection Overview tab or click on the `...` and select `Share`.
3. Go to **Embed** tab and copy the generated link (HTML or Markdown).
### How to Share Your Workspace
1. Go to workspace settings (double tap on the workspace name) and click on the `...` and select `Export`.
2. Go to **Embed** tab and copy the generated link (HTML or Markdown).
Embed the link into your website, article, or documentation(e.g. GitHub Readme).
### Manual Embedding Options
#### HTML Format
```html theme={null}
```
#### Markdown Format
```
[](https://fetch.usebruno.com?url=https://linktoGitCollection.git "target=_blank rel=noopener noreferrer")
```
### How to Open Your Shared Workspace and Collections
The same process will follow for both workspace and collections.
1. Click on any Fetch in Bruno button.
2. A popup will appear. Click on **Open In Bruno**.
3. Choose the location where you'd like to store your collection.
4. You will see a list of available Bruno collections in the repository.
Once the collection is loaded in Bruno, you'll be able to see the folder, requests, and all associated parameters.
### Customizations
You can replace the Fetch in Bruno title and logo with your preferred logo and name. Simply follow the changes below in the markdown link:
```bash theme={null}
[
```
#### Markdown Format
```
[](https://fetch.usebruno.com?url=https://linktoOpenAPIspec.json&type=openapi "target=_blank rel=noopener noreferrer")
```
2. Replace `https://linktoOpenAPIspec.json` with your OpenAPI specification URL.
3. Embed this code in your website, documentation, or any HTML page.
When users click the button, Bruno will automatically:
* Fetch the OpenAPI specification from the provided URL
* Convert it into a Bruno collection
* Open it in the Bruno app and prompt the user to choose a location to save the collection
# Choosing a Git Strategy
Source: https://docs.usebruno.com/git-integration/git-strategies
Bruno was designed to fit naturally into developer workflows. Some teams prefer to keep collections and workspaces alongside the code they are testing while others prefer to keep them in a separate repository to keep things isolated. The flexibility means it is really up to you and what fits your workflow best.
There are three strategies to consider when using Bruno with Git. Picking the right one early saves you from messy remote origin conflicts down the line.
***
## Workspace-level Git
With workspace-level Git, you initialize Git at the **workspace root**. Every collection inside that workspace is tracked under a single repository — one remote origin, one commit history, one place for your team to collaborate.
**Best suited for:**
* Monorepos where API collections live alongside your main source code
* Teams that want a single source of truth for all their API tests
* Projects where all services are tightly coupled and released together
**How it works:**
1. Open Bruno and go to your workspace.
2. Initialize Git at the workspace level (via the Git panel or CLI at the workspace directory).
3. All collections under that workspace are tracked automatically.
4. Push to GitHub, GitLab, Bitbucket, or any Git provider — your team can clone and immediately open the workspace in Bruno.
**File structure:**
```
my-workspace/
├── .git/ # Git repository at workspace root
├── workspace.yml
├── collections/
│ ├── auth-service/
│ │ ├── opencollection.yml
│ │ ├── environments/
│ │ │ └── development.yml
│ │ └── users/
│ │ ├── create-user.yml
│ │ └── get-user.yml
│ └── payment-service/
│ ├── opencollection.yml
│ ├── environments/
│ │ └── development.yml
│ └── orders/
│ └── create-order.yml
```
***
## Collection-level Git
With collection-level Git, you initialize Git **inside a specific collection**. Each collection has its own remote origin, completely independent of other collections.
**Best suited for:**
* Microservices architectures where each service is deployed and versioned independently
* Teams that own separate services (e.g. Auth team, Payments team, OTP team) and want isolated API test repositories
* Connecting a Bruno collection directly to an existing source code repository so it lives alongside that service
**How it works:**
1. Open a collection in Bruno.
2. Initialize Git inside that collection folder (or point it to an existing repo).
3. Each collection has its own remote — push, pull, and collaborate independently per service.
**File structure:**
```
auth-service/
├── .git/ # Git repository inside the collection
├── opencollection.yml
├── environments/
│ └── development.yml
└── users/
├── create-user.yml
└── get-user.yml
```
***
## Initialize Separate Git Repositories in Workspace
This strategy lets you link separate Git repositories to individual collections within a workspace that is already initialized with Git. You can keep common services tracked under the workspace repository while linking specific collections to their own independent Git remotes.
**Best suited for:**
* Teams where most collections belong together but one or two collections need their own repository
* Projects that share a workspace but have a collection tied to a separate service's source code repository
* Gradually migrating collections from a shared workspace repo into independent repos
Things to keep in mind:
* A workspace with Git already tracks **all** collections inside it.
* You **cannot** run `git init` inside a collection folder to make it a separate repo — the parent workspace Git will conflict with it.
* To link a collection to its own remote repository, use the **Connect to Git** option instead.
**How it works:**
1. Open Bruno and go to your workspace settings (click on home icon).
2. Initialize Git at the workspace level (via the Git panel or CLI at the workspace directory).
3. All collections under that workspace are tracked automatically.
4. To link a separate Git repository to a specific collection, go to workspace overview and click on the (`...`) menu on the collection you want to initialize separately and select **Connect to Git**.
5. Enter the Git remote URL and click **Connect**.
6. After connecting, a Git icon will appear next to the collection name in the workspace overview.
7. Your `workspace.yml` file will be updated with the Git remote URL for that collection:
```yaml theme={null}
opencollection: 1.0.0
info:
name: "my-workspace"
type: workspace
collections:
- name: "auth-service"
path: "collections/auth-service"
- name: "payment-service"
path: "collections/payment-service"
remote: "https://github.com//.git"
```
**What to expect:**
* When you push the workspace to a remote repository, **all collections are tracked except the collection that is initialized separately**. The separately linked collection will not be part of the workspace's Git history — it has its own independent remote.
* Git history is **managed separately** — the workspace Git panel shows history for workspace-tracked collections, and the collection Git panel shows history for the independently linked collection.
* If you add a **new collection** to a Git-initialized workspace, it will automatically be tracked by the workspace's Git. To give it its own separate repository, you must use the **Connect to Git** option from the workspace overview — do not try to manually run `git init` inside the collection folder.
**Workspace file structure** (Git initialized at the root with two collections):
```
my-workspace/
├── .git/ # Workspace Git repository
├── workspace.yml
├── collections/
│ ├── auth-service/ # Tracked by workspace Git
│ │ ├── opencollection.yml
│ │ ├── environments/
│ │ │ └── development.yml
│ │ └── users/
│ │ ├── create-user.yml
│ │ └── get-user.yml
│ └── payment-service/ # Linked to separate Git repo (excluded from workspace Git)
│ ├── .git/ # Separate Git repository
│ ├── opencollection.yml
│ ├── environments/
│ │ └── development.yml
│ └── orders/
│ └── create-order.yml
```
**Collection file structure** (independently linked collection):
```
payment-service/
├── .git/ # Own Git repository with independent remote
├── opencollection.yml
├── environments/
│ └── development.yml
└── orders/
└── create-order.yml
```
# Collaboration via GitLab
Source: https://docs.usebruno.com/git-integration/gitlab
GitLab is widely recognized as a leading all-in-one platform for teams and organizations to efficiently manage and collaborate on software development projects. Offering a comprehensive suite of DevOps tools, GitLab streamlines the entire software lifecycle from planning and coding to testing, deployment, and monitoring—enabling seamless collaboration and faster delivery.
Bruno's Git functionality allows you to work with GitLab to manage, collaborate, and maintain your API development workflow.
## Configure GitLab
You need to set up an SSH key with GitLab. While you can also set up personal access tokens (HTTPS), this requires you to enter your token on every operation. SSH is a more convenient and secure way to work with GitLab.
### Create Repository on GitLab
To get started, you have to create a project/repository on GitLab. Follow these steps:
1. Log into your GitLab account (create one if you don't have one)
2. Click on the **New Project** button
3. Choose **Create blank project** or **Create from template**
4. Fill in all required fields (Project name, Description, Visibility level)
5. Click **Create project**
Once you create a repository, click on the **Clone** button and copy the SSH URL, which looks similar to:
```bash theme={null}
git@gitlab.com:/.git
```
### Configure SSH Key
Follow these steps to configure an SSH key on your local system:
#### 1. Generate SSH Key
Open your terminal and execute:
```bash theme={null}
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_gitlab
```
#### 2. Add SSH Key to SSH Agent
Add the SSH key to your SSH agent so it can be used for authentication:
```bash theme={null}
ssh-add ~/.ssh/id_ed25519_gitlab
```
#### 3. Add the SSH Key to Your GitLab Account
1. Go to **GitLab** > **User Settings** (click your avatar) > **SSH Keys**
2. Click **Add new key**
3. Paste your `id_ed25519_gitlab.pub` content
4. Give your key a title (e.g., "Bruno Development")
5. Click **Add key**
## Use GitLab with Bruno
1. Create a Collection inside Bruno (if you haven't already)
2. Click on the **Git** icon (next to Safe Mode) and select **Initialize repo**
3. Click on **main** and select **Remotes** from the dropdown
4. Add **origin** as the name of the remote and paste your repository URL (copied previously)
Now you can perform push, pull, and other Git operations directly from Bruno.
For more detailed information about Git operations in Bruno, check out the [Collaboration via GUI documentation](/git-integration/using-gui/intro).
# Collaboration in Bruno
Source: https://docs.usebruno.com/git-integration/overview
Bruno's superpower is the ability to collaborate natively using version control systems like [Git](https://en.wikipedia.org/wiki/Git), [CVS](https://en.wikipedia.org/wiki/Concurrent_Versions_System), etc, and platforms like GitHub, Gitlab, Bitbucket, etc. This gives you and your teammates confidence that a collection is current, and provides benefits of co-location if you choose to store collections directly alongside your source code.
The process of collaborating on a Bruno collection is more or less the same in all of the above-mentioned platforms. For beginners, you can take a look at this tutorial on version control [here](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup).
There are two ways to collaborate via native Git in Bruno:
1. Using the terminal or command line to execute Git commands
2. Using Bruno's GUI-based Git functionality
**Bruno 3.0.0+**: Core Git UI features (init, view diffs, check for updates, pull, clone) are now available in the free version. Advanced features like commit, push, branching, and merge conflict resolution require Pro or Ultimate editions.
These approaches are documented separately for ease of access. So let's dive into the next section.
* [Collaboration via CLI](/git-integration/using-cli)
* [Collaboration via GUI](/git-integration/using-gui)
* [Fetch in Bruno — embed a **workspace** (v3) or **collection**](/git-integration/embed-bruno-collection)
# Collaboration via CLI
Source: https://docs.usebruno.com/git-integration/using-cli
In the open-source version of Bruno, Git operations are performed manually using the terminal or tools like VSCode's Git features.
This includes:
* [Initializing a Git repository](#initializing-git-in-bruno-collection)
* [Adding and committing changes](#adding-and-committing-changes)
* [Pushing and pulling updates](#pushing-and-pulling-changes)
Below is a step-by-step guide on how to use Git with Bruno in the open-source version.
### Initializing Git in a Collection
To add your collection to version control, you need to initialize a Git repository in the directory where your .bru files are stored using the following steps:
1. **Navigate to the Bruno Collection Location:**
* Navigate to the location on your PC where you created the Bruno collection, and copy the path. \[^1]
2. **Change directory in Terminal:**
* Run the following command in the terminal:
```bash theme={null}
cd path/to/your/bruno/collection
```
3. **Initialize Git:**
* Run the following command to initialize Git in the Bruno collection directory:
```bash theme={null}
git init
```
### Adding and Committing Changes
After initializing the Git repository, you can add and commit changes to your collection.
1. **Creating a New HTTP Request in Bruno App**
* Hover over the collection name in the Bruno app.
* Click on the three dots, then click on "New Request."
* Type in the name of the request and populate the URL field with `https://example.com`.
* After populating the fields with appropriate values, click on the "Create" button, or you can press ENTER/RETURN on your keyboard to create the request. This will create a new `.bru` file with the name that you just specified.
2. **Adding and committing the changes**
* Stage all the changes made inside the collection:
```bash theme={null}
git add .
```
* Commit the changes:
```bash theme={null}
git commit -m "Created my first Bruno request 🐶"
```
### Pushing and Pulling Changes
To share your changes with others, you can push and pull your commits to/from a remote repository.
1. **Pushing changes**
```bash theme={null}
git push -u origin main
```
2. **Pulling changes**
```bash theme={null}
git pull origin main
```
# Consumer Guide
Source: https://docs.usebruno.com/git-integration/using-gui/consumer
This guide covers Git operations for team members who clone and consume shared collections.
All features on this page are available in the **free version** starting from Bruno 3.0.0.
## Cloning Collections from GitHub
To clone a collection from GitHub into **Bruno**, you need to have an existing collection repository hosted on GitHub.
For example, you can use this [Bruno repository](https://github.com/usebruno/github-rest-api-collection) and follow the steps below.
1. Launch **Bruno** and click the **Import Collection** button in the Quick Actions section on the Overview page. Alternatively, click the **+** icon in the sidebar and select **Import collection**.
2. In the Import Collection dialog, select the **Git Repository** tab.
3. Enter the **Git repository URL** and click the **Clone** button.
4. Choose a **Location** to clone the collection to.
5. After the cloning process completes, select the **Collection** you want to open and click **Open**.
Your collection will now be open in **Bruno**.
## Checking for Updates
You can check if there are new changes available in the remote repository.
1. Go to **Git UI**.
2. Click the **Fetch** button.
3. Bruno will display if there are incoming changes available.
This allows you to see if your team has pushed new changes before you pull them.
## Pulling Changes
Pull the latest changes from the remote repository to stay synchronized with your team.
1. Go to **Git UI**.
2. Click the **Fetch** button.
3. Click the **Pull** button.
Now you can see all your remote changes pulled in your collection.
If you have local uncommitted changes when pulling, you may be prompted to stash them first. The [Stash Changes](../using-gui/provider#stash-changes) feature (Pro/Ultimate) helps manage this workflow.
# Collaboration via GUI
Source: https://docs.usebruno.com/git-integration/using-gui/intro
Bruno offers an intuitive **GUI-based Git collaboration** feature that makes it easier for both engineering and non-engineering teams to collaborate seamlessly on collections.
Git must be
[installed **↗**](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) on
your machine to use this feature.
## Feature Availability
**Bruno 3.0.0+**: Core Git UI features are now available in the free version, making Git collaboration accessible to all users.
### Free Features (Bruno 3.0.0+)
The following Git UI features are available in the free/open-source version of Bruno:
| Feature | Description |
| -------------------------------------------------------------- | ---------------------------------------------------------- |
| [Initialize Git](./provider#initializing-git) | Set up version control for your collection |
| [View Diffs](./provider#viewing-diffs) | See changes made to your collection files |
| [Check for Updates](./consumer#checking-for-updates) | Check if remote repository has new changes |
| [Pull Changes](./consumer#pulling-changes) | Download and apply remote changes to your local collection |
| [Clone Repository](./consumer#cloning-collections-from-github) | Clone a collection from a Git repository |
### Pro & Ultimate Features
The following advanced Git UI features require [Pro **↗**](https://www.usebruno.com/pricing) or [Ultimate **↗**](https://www.usebruno.com/pricing) editions:
| Feature | Description |
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [Create GitHub Repository](./provider#create-a-new-github-repository-from-bruno) | Create a new GitHub repo and push your collection without leaving Bruno |
| [Add & Commit Changes](./provider#adding-and-committing-changes) | Stage and commit your local changes |
| [Push Changes](./provider#pushing-changes) | Push local commits to remote repository |
| [Branch Creation](./provider#branch-creation-and-checkout) | Create new branches for features or fixes |
| [Branch Checkout](./provider#branch-creation-and-checkout) | Switch between different branches |
| [Connect Remote](./provider#connect-to-an-existing-repository) | Add and manage existing remote repository connections |
| Stash Changes | Temporarily save uncommitted changes before pulling |
| Merge Conflict Resolution | Visual editor to resolve Git merge conflicts |
| [Request Git History](./provider#request-git-history) | Browse the full commit history for any individual request and inspect diffs |
## Quick Links
* [Provider Guide](./provider) - For team members creating and pushing collections
* [Consumer Guide](./consumer) - For team members cloning and pulling collections
# Provider
Source: https://docs.usebruno.com/git-integration/using-gui/provider
This guide covers Git operations for team members who create and share collections.
## Free Features
The following features are available in the **free version** starting from Bruno 3.0.0.
### Initializing Git
Initializing Git in Bruno is the first step towards managing your API collections with version control. It's easy and quick to set up. Follow the steps below to initialize Git.
1. Launch the **Bruno app**.
2. Navigate to the collection you want to initialize with Git.
3. Click on the **Git Icon** in the top-right corner of the navbar.
4. Click the **Initialize** button to set up your collection with Git.
This allows you to perform other Git operations such as viewing diffs, pulling changes, and more.
### Viewing Diffs
Before committing changes, you can view the differences between your local changes and the previous version. Bruno provides two diff viewing modes to help you understand changes better.
1. Go to **Git UI**.
2. Click on any modified file to see the diff view.
3. Review the changes highlighted in the diff viewer.
#### Text-Based Diff View
The **Text-Based Diff** view displays changes in a traditional side-by-side or unified format, showing additions and deletions line-by-line. This is ideal for reviewing detailed code changes.
#### Visual-Based Diff View
The **Visual-Based Diff** view provides a more intuitive, visual representation of changes. This mode highlights modifications in a user-friendly format, making it easier to understand structural changes at a glance.
You can switch between these views to choose the format that best suits your workflow and helps you understand exactly what has changed before deciding to commit.
***
## Pro & Ultimate Features
The following features require [Pro or Ultimate](https://www.usebruno.com/pricing) editions.
### Connecting to Remote Repository
Once you have initialized your collection, the next step is to connect it to a remote Git repository. Bruno gives you two ways to do this:
* **[Create a new GitHub repository directly from Bruno](#create-a-new-github-repository-from-bruno)** — recommended for new collections. Bruno creates the repo on GitHub for you, wires up the remote, and pushes the initial commit, all without leaving the app.
* **[Connect to an existing repository](#connect-to-an-existing-repository)** — use this if you already have a repo on GitHub, GitLab, Bitbucket, or any other Git provider.
#### Create a new GitHub repository from Bruno
After you initialize Git in a collection that has no remote yet, Bruno shows a **Create a repository on GitHub** prompt in the Git UI. This lets you create the repo on GitHub, set it as the remote, and push your local branch — all in one flow.
1. Open the **Git initialized** Bruno collection.
2. In the **Git UI**, click the **Create a Repo on GitHub** button.
3. If this is your first time, Bruno will prompt you to sign in to GitHub (via OAuth or a Personal Access Token). Authorize Bruno to manage repositories on your behalf.
4. Fill in the **Create GitHub Repository** dialog:
* **Owner** — choose your personal account or any organization you have access to.
* **Repository name** — the name of the new repo on GitHub.
* **Description** *(optional)* — a short summary that appears on the GitHub repo page.
* **Visibility** — pick **Private** (you and invited collaborators only) or **Public** (anyone on the internet can view).
5. Click **Create Repository**.
Bruno will then:
* Create the new repository on GitHub via the GitHub API.
* Set it as the **origin** remote on your local collection automatically.
* Push your local branch to GitHub so the repo is ready for collaboration immediately.
This flow is ideal for users without deep Git experience — you never have to visit github.com or copy-paste a remote URL. Once the repo is created, all standard Git operations (commit, push, pull, branches) continue to work the same way as before.
#### Connect to an existing repository
If you already have a repository on GitHub, GitLab, Bitbucket, or any other Git provider, click the **Add Remote** link on the same prompt (or use the **Remotes** option under **Quick Links**) to wire it up manually.
To connect Bruno to GitHub via this manual flow, you need to set up either an SSH key or a Personal Access Token (PAT) on GitHub. Follow this [official GitHub guide](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) to set it up.
1. Create an empty Git repository on GitHub (or your provider of choice).
2. Open the **Git initialized** Bruno collection.
3. Click on **Quick Links** (bottom-left corner).
4. Select **Remotes** from the dropdown.
5. Click the **Add Remote** button.
6. Enter **Remote Name** and **URL** (HTTPS or SSH) and click **Save**.
After completing the above steps, your collection will be connected to your remote repository. You'll need to **add and commit** (see below) some changes and push them to populate the remote.
Once your collection is connected to the remote repository, you can manage it directly from the Bruno app.
### Adding and Committing Changes
Once you've made changes to your collection in Bruno, you can easily commit those changes using the Git UI. This allows you to track changes and collaborate with your team efficiently.
Follow these steps to add and commit changes:
1. Go to **Git UI**.
2. Click the **Add Icon** to stage your changes.
3. Click the **Commit Changes** button.
4. Write a commit message and click **Commit**.
> You can commit all changes in a single click by pressing the **Add All Changes** button.
By following these steps, your changes are now committed and tracked in Git, ready to be pushed or shared with others.
### Pushing Changes
The GUI-based Git integration in Bruno allows both engineering and non-engineering teams to work together in a fast, efficient, and accurate manner. This integration ensures that your collections remain consistently up-to-date across all team members.
> Before you can push or pull changes, ensure that the **remote repository** is linked to your project.
Push your local changes to the remote repository with just a few clicks.
1. Go to **Git UI** and click the **Fetch** button.
2. Click the **Push** button.
Now you can see all your local changes pushed to the remote repository.
> Follow the Consumer guide to learn about the pulling changes feature.
### Branch Creation and Checkout
With branches in **Bruno**, you can manage your code effectively by isolating different features or tasks.
#### Creating Branches
Follow the steps below to create branches in Bruno.
1. Go to **Git UI**.
2. In the bottom left corner, click on **main** (or the default branch name).
3. Enter a **Branch Name**.
4. Click the **Create branch** button.
Once the branch is created, you can start making changes to the branch.
#### Branch Checkout
1. Click on **main**.
2. Select the branch you want to switch to.
3. Now you're switched to your selected branch.
### Stash Changes
Stashing allows you to temporarily save your uncommitted local changes (both staged and unstaged modified tracked files) and revert your working directory to a clean state matching the last commit. This is especially useful for quickly switching context or handling urgent tasks without having to commit unfinished work.
1. Go to **Git UI**.
2. When you have unstaged changes, click on **Stashes**.
3. Enter a **Stash Message** and click the **Stash files** button.
4. Your local changes are saved temporarily so you can work on something else or switch branches without losing your changes.
5. Stashes can be viewed, deleted, or applied to restore your changes later.
### Merge Conflict Resolution
When conflicts arise during pull or stash operations, Bruno provides a visual conflict resolution interface.
#### Conflict Detection
When conflicts are detected, Bruno provides information and options to resolve conflicts:
* **Commits behind and ahead of remote**
* **Conflicting changes identified**
* **Abort** - Abort the operation and restore previous state
#### Visual Conflict Editor
The conflict editor provides:
* **File List** - Shows all conflicted and non-conflicted files with status indicators
* **Diff View** - For each conflict, choose to:
* Accept Incoming (remote version)
* Accept Current (local version)
* Accept Both (merge both versions)
Once all conflicts are resolved, enter a commit message and click the **Lock** icon to complete the merge.
***
### Request Git History
The **History** tab inside the Git UI lets you browse the full commit history of any individual request. You can see every commit that touched that request — complete with commit message, author, date, and short hash — and click any commit to inspect the exact diff.
#### Viewing commit history for a request
1. Open the collection in Bruno and navigate to any request.
2. Click the **History** tab in the request panel (alongside Params, Body, Headers, etc.).
If Git has not been initialized for the collection yet, the tab shows the **Not a git repository** prompt with an **Initialize Git** button.
Once Git is initialized and you have at least one commit, the History tab lists every commit that modified the current request:
| Column | Description |
| ----------- | ------------------------------------------- |
| **Message** | The commit message |
| **Author** | The Git author name |
| **Date** | Relative timestamp (e.g. "Few seconds ago") |
| **Hash** | Shortened commit SHA |
#### Inspecting a commit diff
Click any row in the commit list to open the diff for that commit. Bruno displays the file-level diff — additions and deletions — for the selected commit, using the same text and visual diff viewer available elsewhere in the Git UI.
Request-level history is particularly useful during code review or debugging — you can pinpoint exactly when a URL, header, or body parameter changed and who changed it, without scanning through the full collection history.
# GitHub Provider
Source: https://docs.usebruno.com/git-providers/github
You can search, filter, import and clone collections from public and private repositories on GitHub directly from the app. You can also connect your GitHub account to use your personal access token or GitHub CLI to access private repositories.
## Access and import public repositories
Public repositories on GitHub are searchable from Bruno without signing in.
1. Open **Import collection** dialog box and go to the **GitHub** tab.
2. Search any public repository by name or keywords.
3. Select the repository you want to import and click on the **Clone** button.
Bruno by default clone repositories to the `~/Documents/bruno` folder. You can change the location by clicking on the **Browse** button.
4. Select the collections you want to import and click on the **Open** button.
5. You will see the collections you imported in the sidebar.
After clone, Bruno **scans** the repo and lists **Bruno collections** found inside. Select one or more and click **Open** to add them to your workspace.
***
## Private repositories and authentication
Authentication, searching and importing private repositories at organization and individual level is available in Bruno Pro and Ultimate editions.
Private repositories and **organization-scoped** search need GitHub to know who you are. Bruno supports:
* **Personal Access Token (PAT)** (recommended)
* **GitHub CLI (`gh`)** — sign in with the official GH CLI
After you connect:
* You can import **private** repos and narrow search to **your user** and **organizations** you belong to (dropdown entries such as **you**, **org**).
* You can scope search to an org (for example `your-org`) and see private repos (often shown with a lock icon in the list).
***
## Configure a Personal Access Token (recommended)
Use a PAT when you want predictable, documented access for Bruno on your machine.
### 1. Open Git provider settings
1. Open **Preferences** settings from bottom left corner.
2. Go to **Git providers** in the sidebar.
3. Select **GitHub** and click on **Configure** button.
### 2. Enable GitHub
1. Turn **Enabled** on.
2. Go to **Personal Access Token** and click on **Generate a token on GitHub** link.
If you already have a token, you can paste it in the **Enter your GitHub personal access token** field.
### 3. Create and paste the token
1. Click on **Generate a token on GitHub** and sign in to GitHub.
2. Sign in to GitHub and create a token with the scopes your org requires (repo read for private repos, also follow your company policy for fine-grained vs classic tokens).
3. Copy the token value, return to Bruno, paste it into **Enter your GitHub personal access token**.
4. You should see a **connected** state (eg **Connected as @your-username via Personal Access Token**) with options to **Refresh** or **Clear token** when you need to rotate credentials.
Advanced options are available to configure the enterprise GitHub API URL.
### 4. Import again from the GitHub tab
1. Return to **Import collection** → **GitHub**.
2. Use the scope dropdown to pick **your user** or an **organization**.
Private repositories should appear in search results; clone and select collections as in [Access and import public repositories](#access-and-import-public-repositories).
You can add multiple GitHub account (up to 5 users) to your workspace by entering GitHub username on the **Add account** input field.
***
## GitHub CLI
You can authenticate with GitHub CLI if you prefer not to paste a PAT:
1. In **Preferences** → **Git providers** → **GitHub**, open the **GitHub CLI** tab.
2. Click on **Install** button to install GitHub CLI and authenticate with it in Bruno.
3. You should see a **connected** state (eg **Connected as @your-username via GitHub CLI**) with options to **Refresh** or **Clear token** when you need to rotate credentials.
We still **recommend a PAT** for straightforward setup, rotation, and use in environments where the CLI is not installed.
***
## GitHub Enterprise and custom API URL
For **[GitHub Enterprise Server](https://docs.github.com/en/enterprise-cloud@latest/admin/overview/about-github-for-enterprises)** (self-hosted) or a different API base than public GitHub, set the REST **API URL** Bruno uses for search and repo metadata.
1. In **Preferences** → **Git providers** → **GitHub**
2. Expand **Advanced**.
3. Replace the default **`https://api.github.com`** with your Enterprise API root. For **GitHub Enterprise Server**, the REST API is often at `https:///api/v3`. Your administrator or [About GitHub’s REST APIs](https://docs.github.com/en/enterprise-server/rest) for your Enterprise version documents the exact base URL.
4. Connect with PAT or CLI as above.
After saving, org-scoped search and private repos on that host should resolve against your Enterprise instance instead of [api.github.com](https://api.github.com).
***
## Quick comparison
| Goal | What to use |
| ----------------------------------- | ----------------------------------------------------------------------------------- |
| Import a **public** repo by search | **GitHub** tab, scope **All of GitHub**, no login |
| Import **private** or **org** repos | Enable **GitHub** under **Git providers** + **PAT** (recommended) or **GitHub CLI** |
| **Enterprise** GitHub host | Same + **Advanced** → set **API URL** to your server’s API base |
| Any Git URL without search | **Git repository** tab + paste URL |
***
## Related reading
* [Git integration overview](/git-integration/overview) — init, remotes, commit, push, pull.
* [Collaboration via GUI — Provider](/git-integration/using-gui/provider) — remotes and Git workflow with a GitHub remote.
* [Fetch in Bruno](/git-integration/embed-bruno-collection) — one-click open for a GitHub repo from a README or site.
# Git providers and Bruno
Source: https://docs.usebruno.com/git-providers/overview
**Git providers** is available in Bruno 3.3.0+ version.
Bruno now offers first-class support for Git providers, allowing you to search across hosts, import collections, and sync repositories without needing to manually enter raw Git URLs. GitHub is available today, with more providers, such as GitLab, planned for future releases.
You can filter repositories at both the organization and user levels, including the ability to search for private repositories (based on your authentication permissions).
* **[GitHub](/git-providers/github)** — Complete control to clone and import collections from GitHub directly from the app.
For generic Git workflows (init, diff, pull, push) on any remote, see **[Git integration](/git-integration/overview)**.
# Feedback & Community
Source: https://docs.usebruno.com/introduction/feedback-community
Bruno is building a community of API practitioners who want to challenge the state of tooling available. Our roadmap and releases are driven by your feedback and, as an open-source project, sometimes directly from your PRs.
Help us continue building the most powerful, logical, and dev-friendly API tool available today!
### How to Engage
* Raise a [GitHub Issue ↗](https://www.github.com/usebruno/bruno/issues)
* Star us on [Github ↗](https://github.com/usebruno/bruno)
* Join our [Discord ↗](https://discord.com/invite/fAPz4PrYnK)
* Email our [Support Team](mailto:support@usebruno.com)
# Getting Started
Source: https://docs.usebruno.com/introduction/getting-started
Bruno is a Git-friendly, offline-first API client built for developers who want fast local workflows, plain-text collections, and better collaboration through Git.
Hands-on walkthrough: create collections, send requests, write tests, and collaborate with Git in the app.
Hands-on walkthrough: install Bruno CLI, run collections, generate reports, and integrate with CI/CD.
Import your Postman collections and environments into Bruno in minutes.
***
## Start with a task
Run collections from the command line and automate API workflows in CI.
Add pre-request and post-response scripts to customize and test requests.
Use variables across requests, environments, and collections.
## Explore Public Collections
Want to see Bruno in action? [Bruno Public Collections](https://github.com/bruno-collections) is a GitHub repository of ready-to-use API collections. Clone one and open it in Bruno to test APIs.
## Why Bruno?
* **[Git-friendly collaboration](/git-integration/overview)** — store collections as files and review API changes like code.
* **Offline-first by design** — work locally without requiring a cloud account.\*
* **Plain text collections** — store requests in YAML for readable, versionable collections.
\* Bruno does not require an account to use. Your email is only used to issue a license key if you purchase a license.
# Manifesto
Source: https://docs.usebruno.com/introduction/manifesto
It's time for a revolution in API client technology!
We refuse to be shoehorned into a proprietary version control system for collaborating on API collections and we don't want the details of our APIs, API requests, or API responses synced to the cloud and potentially be made public.
We are an opensource project, rising up against the monopoly of bloated and closed systems. We believe that API collections should be co-located within your source code repository, serving as a living set of examples on how to use the API.
With Bruno, collections become first-class citizens, co-located with related information and easily version controlled. We say goodbye to bloated workspaces and global environments, and embrace the benefits of co-location.
We dream of a world where developers can clone a code repo, get it up and running, use Bruno to browse the examples on how to use the API and start playing with it. No more horror stories of *"Tim had the collections for the payment-api service, but he left the company last month."*
Join us in our mission to create a better API client for developers. Say goodbye to bloatware and hello to simplicity, efficiency and freedom. Try Bruno today and experience the difference for yourself.
# Quick Start
Source: https://docs.usebruno.com/introduction/quick-start
Bruno is a Git-friendly, local-first, open-source API client designed for developers. This guide will take you from the basics to more advanced features, helping you build a strong understanding of Bruno.
## Prerequisites
* A basic understanding of HTTP and REST
* [Bruno installed](https://www.usebruno.com/downloads) (v3.x)
* **[Git](https://git-scm.com/install/)** (Needed for collaboration via Git)
* **[Node.js](https://nodejs.org/en/download/)** (Needed for the CLI workflow)
## Getting started
You can refer to the **[Bruno Starter Guide](https://github.com/bruno-collections/bruno-starter-guide)** collection for solutions:
**Fetch in Bruno** button to open the collection in Bruno, **or** git clone the repository [Bruno Starter Guide](https://github.com/bruno-collections/bruno-starter-guide).
Use the **[Bruno Starter Guide](https://github.com/bruno-collections/bruno-starter-guide)** collection as a reference solution while following this guide. If you get stuck at any step, open the collection to see the expected request setup.
Work through the challenges **in order**. Each step assumes the previous one.
***
## 1. Create a collection
**Objective:** Create a collection (sequence of requests) to store your API requests.
1. Open Bruno (workspace home).
2. Click the **+** icon (top-left) → **Create collection**.
3. Enter a name **Bruno Starter Guide** (click on settings icon for advanced options) and choose the desired folder on your system.
4. Select file format (YAML or BRU -> YAML recommended) and click **Create**.
Congratulations! You have successfully created your first collection. The UI should look like this:
**Success Criteria:**
* Collection is visible in the sidebar with name **Bruno Starter Guide**.
* Collection is created with the desired file format (YAML recommended).
***
## 2. Create a request
**Objective:** Create and execute your first request from the collection.
1. Click the collection context menu (...) → create a **New Request**.
2. Select:
* `type` HTTP - (GraphQL, gRPC, WebSocket, or cURL is also supported)
* `Request Name` as `01-github-api`
* `URL` as `https://api.github.com/users/usebruno`.
3. Execute request by clicking on Send button or pressing Cmd/Ctrl + Enter.
You have successfully sent your first request. The UI should look like this:
**Success Criteria:**
* Status is **200** (Green text - 200 OK)
* Response body with details of the `usebruno` user.
***
## 3. Send a request with data
**Objective:** Create a request with **POST** method and send data with API request.
1. Create a new request with:
* Request type as **HTTP**
* Request name as `02-echo-bru`
* method as **POST**
* URL as `https://echo.usebruno.com`
2. Go to the **Body** tab and select → **JSON** as an option
3. Add the following JSON data to the body of the request:
```json theme={null}
{
"title": "Bruno",
"msg": "Loved by developers. Built for developers."
}
```
4. Execute request by clicking on Send button or pressing Cmd/Ctrl + Enter.
You have successfully sent **JSON** data to the API. This is Bruno's echo endpoint, so you will receive the same data in the response.
**Success Criteria:**
* JSON data is sent to the API.
* Response body contains the same JSON data.
***
## 4. Create an environment
**Objective:** Create an environment variable to store a **URL** and reuse it with `{{baseURL}}`.
1. Open the **Environments** settings (top-right — **No environment**).
2. Click the **Create** button in the **Collection** section.
3. Name the environment `local` in the environments popup
4. Add the following variable to the environment:
* **Name:** `baseURL`
* **Value:** `https://echo.usebruno.com`
5. Click the **Save** button.
6. Go back to `02-echo-bru` request and set the `URL` to `{{baseURL}}` and select the `local` environment from the environment dropdown.
You have successfully created your first environment variable and reused it in the request. Bruno supports multiple types of variables; read more about them: [Environment variables](/variables/overview)
**Success Criteria:**
* Environment is created with the name `local`.
* Environment variable `baseURL` is added with the value `https://echo.usebruno.com`.
* Request `02-echo-bru` is executed successfully with the `URL` set to `{{baseURL}}`
* Response status is **200**.
***
## 5. Write a test case
**Objective:** Write an assertion and test script to validate the response of the request.
1. Open your **02-echo-bru** request.
2. Go to the **Assert** tab and enter the following details:
| Expression | Operator | Value |
| ---------------- | -------- | ------- |
| `res.status` | `equals` | `200` |
| `res.body.title` | `equals` | `Bruno` |
3. Execute the request and go to the **Tests** tab in the response section to confirm the assertion passes (green).
4. Go to the **Tests** tab in the request section and copy and paste the snippet below:
```javascript theme={null}
test("body has title", function () {
expect(res.getBody()).to.have.property("title");
});
test("200 status code", function () {
expect(res.getStatus()).to.eql(200);
});
```
5. Execute the request (Cmd/Ctrl + Enter) and confirm tests pass (green).
**Success Criteria:**
* **Assert** and **Tests** pass (green) without any errors.
Read more about test and assertions: [Tests introduction](/testing/tests/introduction)
***
## 6. Write a script
**Objective:** Send request with pre-request script to set the body of the request.
`req` object is only available in pre-request script.
1. Create a new request with:
* Request type as **HTTP**
* Request name as `03-script-request`
* method as **POST**
* URL as `https://echo.usebruno.com`
2. Go to the **Script** → **Pre-request** tab and enter the snippet below:
```javascript theme={null}
const body = {
name: "Bruno",
msg: "I love opensource devtools"
};
req.setHeader("Content-Type", "application/json");
req.setBody(body);
console.log(req.getName());
```
3. Execute the request and verify that the response body matches the body of the request.
Open **Dev Tools** from the bottom-right corner and you will see the console output of the script.
Congratulations, you have successfully written a script to set the body of the request with header values and console logs visible in **Dev Tools**.
**Success Criteria:**
* Script is executed successfully and console output is visible in the **Dev Tools**.
* Response body is set to the body of the request with `req.setBody()` function.
* Header is set to the json content type with `req.setHeader()` function.
Read more about script: [JavaScript Reference](/testing/script/javascript-reference)
***
## 7. Authentication
**Objective:** Send a request with authentication to the API.
1. Create a new request with:
* Request type as **HTTP**
* Request name as `04-auth-request`
* method as **GET**
* URL as `https://httpbin.org/basic-auth/usebruno/1234`
2. Go to request **Auth** tab and select **Basic Auth** from the Auth Type dropdown list.
3. Enter the username and password as `usebruno` and `1234` respectively.
4. Execute the request (Cmd/Ctrl + Enter).
You'll see `authenticated` set to `true` in the response body.
You have successfully sent a request with authentication to the API.
**Success Criteria:**
* Authentication is set to Basic Auth with the username and password.
* Response body contains `authenticated` set to `true`.
Read more about authentication: [Authentication](/auth/overview)
## 8. Collection Runner
**Objective:** Run all requests in the collection using collection runner.
1. Go to the collection context menu (...) and click **Run**.
2. Click on **Configure requests to run** and select all requests in the collection.
3. Click **Run Selected Requests** button to start the run.
You'll see all requests running one by one in the collection runner.
**Success Criteria:**
* All requests are running one by one in the collection runner.
* Response status is **200** for all requests.
* Response body contains the data of the request.
Read more about collection runner: [Collection Runner](/testing/automate-test/data-driven-testing)
***
## 9. API Documentation
**Objective:** Create automated API documentation for the collection.
1. Go to collection context menu (...) and select **Generate Docs**.
2. Click on the **Generate** button to generate automated API documentation.
3. Save the documentation file (html) to your desired location.
You have successfully generated API documentation for the collection. The generated documentation is in html format and can be opened in any browser.
You can share it with your team or deploy it to your own server.
Click on **Try** button to open it as API playground.
**Success Criteria:**
* API documentation is created for the collection.
* HTML file is generated with the content of the collection.
Read more about API documentation: [API Documentation](/api-docs/overview)
## 10. Git collaboration
**Objective:** Initialize Bruno collection as a Git repository, push it to GitHub.
**Bruno 3.0+:** Git basic UI features are available in the open-source app. See [Git integration (GUI)](/git-integration/using-gui/intro).
1. Open the **Git** panel (top bar) and click **Initialize** to turn the collection folder into a repository.
2. Stage and commit your changes from the Git panel (or use `git add` and `git commit` commands in a terminal).
3. Go to [GitHub](https://github.com/)
* Create an empty repository (click on the **+** button and select **New repository**)
* Enter name **bruno-starter-guide** and other details and copy the HTTPS URL
```bash theme={null}
https://github.com/<>/bruno-starter-guide.git
```
4. Go to collection **Git** panel and click on **Remotes** from bottom-left corner.
5. Click the **Add Remote** button and enter the following details:
* **Remote Name:** `origin`
* **URL:** `https://github.com/<>/bruno-starter-guide.git`
6. Push your default (main) branch to the remote repository.
Go to your remote repository and you will see the collection is pushed to the remote repository. You can share it with your team and collaborate on it.
**Success Criteria:**
* Git repository is initialized for the collection.
* Remote repository is created and connected.
* Collection is pushed to the remote repository.
More: [Using the Git UI](/git-integration/using-gui/intro)
***
## 11. Run a request through the CLI
**Objective:** Install **Bruno CLI** and run all requests in the collection from a terminal.
Make sure you have Node.js installed on your system.
1. Install Bruno CLI:
```bash theme={null}
npm install -g @usebruno/cli
```
2. Open a terminal and navigate to the **Bruno Starter Guide** collection (folder with `bruno.json`).
```bash theme={null}
cd path/to/bruno-starter-guide
```
3. Run all requests:
```bash theme={null}
bru run
```
4. Optional: run with a named environment, e.g.:
```bash theme={null}
bru run --env local
```
You will see all requests running one by one in the collection runner, along with an execution summary table showing metrics and results.
**Success Criteria:**
* All requests are running one by one in the collection runner.
* Response status is **200** for all requests.
* Response body contains the data of the request.
More: [Bruno CLI](/bru-cli/overview)
***
## 12. OpenAPI specification
**Objective:** Export your collection as an **OpenAPI** document (shareable API description).
1. Open the collection context menu (...) and select the **Share** option.
2. Go to the **Export** tab, select **OpenAPI Specification** and click the **Proceed** button.
3. Enter the name of the file and location, then click the **Create** button.
You have successfully exported your collection as an OpenAPI document. The OpenAPI file is created with a `.yaml` extension and stored in your file system.
You can view and import the OpenAPI file in Bruno. Please refer to [OpenAPI Specification](/open-api/overview) for more details.
**Success Criteria:**
* OpenAPI file is created where you chose and opens without errors
* Exported OpenAPI file has a `.yaml` extension.
More: [OpenAPI Specification](/open-api/overview)
***
## 13. Response Visualizer
**Objective:** Use Bruno's response visualizer to render API response data as an interactive table.
1. Create a new request with:
* Request type as **HTTP**
* Request name as `05-visualizer`
* method as **POST**
* URL as `https://echo.usebruno.com`
2. Go to the **Script** → **Post-response** tab and enter the snippet below:
```javascript theme={null}
const rowData = [
{ id: 1, name: "John", role: "Developer" },
{ id: 2, name: "Smith", role: "Designer" },
{ id: 3, name: "Harry", role: "Product Manager" }
];
const columnDefinitions = [
{ field: "id", filter: true, floatingFilter: true },
{ field: "name", filter: true, floatingFilter: true },
{ field: "role", filter: true, floatingFilter: true }
];
bru.visualize('table', {
name: 'tableEmp',
provider: 'ag-grid',
props: {
rowData,
columnDefinitions
}
});
```
3. Execute request by clicking on Send button or pressing Cmd/Ctrl + Enter.
4. Go to the **Visualize** tab in the response section to see the rendered table.
**Success Criteria:**
* The post-response script runs without errors.
* The **Visualize** tab displays an interactive table with three rows (John, Smith, Harry) and filterable columns.
More: [Response Visualizer](/advanced-guides/visualize)
***
Congratulations on completing the Quick Start guide! You have learned the basics of Bruno and how to use it to create API requests. Feel free to dive into the advanced guides to learn more about Bruno.
* **Auth** — [Authentication](/auth/overview)
* **Secrets** — [.env and secret managers](/secrets-management/dotenv-file)
* **Git Collaboration** — [Git integration (GUI)](/git-integration/using-gui/intro)
***
# DotEnv File
Source: https://docs.usebruno.com/secrets-management/dotenv-file
Environment variables are used to store sensitive data such as API keys, tokens, and configuration settings outside the source code. This helps keep your code secure and makes it easier to manage different settings for various environments (e.g., local, staging, production).
## DotEnv File for Secret Management
In **Bruno**, you can store your secrets (e.g., API keys, JWT tokens) in a `.env` file located at the **root** of your collection folder. This approach is inspired by how developers typically manage secrets in their codebase.
### Folder Structure Example
Below is an example folder structure for your collection:
## Creating and Using the `.env` File
1. Create a `.env` file manually in the root of your collection folder. This file will store your sensitive environment variables.
2. Define your secrets in the `.env` file. For example:
```bash filename=".env" showLineNumbers theme={null}
JWT_TOKEN=your_jwt_token_value
API_KEY=your_api_key_value
```
These secrets will be accessible in your Bruno collection via the `process.env` object.
Bruno will automatically load the secrets from this file and make them available to your collection via `process.env.`.
Your environment file at `environments/local.bru` would look like
```bash filename="local.bru" theme={null}
vars {
baseURL: https://echo.usebruno.com
JWT_TOKEN: {{process.env.JWT_TOKEN}}
API_KEY: {{process.env.API_KEY}}
}
```
In this example, the `JWT_TOKEN` secret from the `.env` file is referenced using `process.env.JWT_TOKEN`. This will be replaced with the actual value of `JWT_TOKEN` when the collection is executed.
## Managing Secrets
1. Always add the `.env` file to your `.gitignore` file to ensure secrets are not accidentally pushed to version control.
2. If you need to share the structure of your environment variables with other developers, create a `.env.sample` file without actual secret values.
### Handling Variables with Dots
When using environment variables that contain dots in their names, use square bracket notation:
```bash theme={null}
# In .env file
example.test=mysecretvalue
```
```javascript theme={null}
// In your request
// Won't work
"secret": "{{process.env.demo.example.test}}"
// Works correctly
"secret": "{{process.env['example.test']}}"
```
This happens because Bruno interprets dots as object path separators. Square brackets tell Bruno to treat the entire string as a single variable name.
## Manage Environment Credentials
**Bruno v3.1.0**: You can now create, view, and delete environment variables directly from Bruno at the workspace level without manually editing `.env` files.
### Accessing Environment Variables
1. Navigate to **Workspace** → **Global Environment** section
2. Create or edit environment variables with your credentials
3. Use these variables across all collections in your workspace using `{{process.env.}}` syntax.
This feature provides a centralized UI to create, view, and delete environment credentials directly from Bruno, eliminating the need to manually edit `.env` files while maintaining the same security and accessibility benefits.
# Secret Management
Source: https://docs.usebruno.com/secrets-management/overview
In any collection, there are secrets that need to be managed. These secrets can be anything such as API keys, passwords, or tokens.
A common practice is to store these secrets in environment variables.
There are two ways in which developers share bruno collections:
* Check-in the collection folder to source control (like git)
* Export the collection to a file and share it
In both these cases we want to ensure that the secrets are stripped out of the collection before it is shared.
Bruno stores secrets on your local machine. The location depends on the OS. The secrets are encrypted using OS level encryption when available or falls back to AES256 encryption.
## Solution
Bruno offers three (3) approaches to manage secrets in collections.
* [Secret Variables](/secrets-management/secret-variables)
* [DotEnv File](/secrets-management/dotenv-file)
* [Integration with a Secret Manager](/secrets-management/secret-managers)
# Adding AWS Secrets Manager
Source: https://docs.usebruno.com/secrets-management/secret-managers/aws-secrets-manager/adding-a-secret-provider
AWS Secrets Manager can be integrated with Bruno to securely manage and retrieve secrets for your collections. This guide walks you through the process of adding AWS Secrets Manager as a secret provider in Bruno.
## Configuration Steps
1. **Access Secrets Manager Settings**
* Navigate to the **Preferences** settings (bottom-left sidebar).
* Go to the **Secrets Manager** section.
2. **Add a New Secret Provider**
* Click the `+ Add Secret Provider` button.
* Select **AWS Secrets Manager** as the provider type.
3. **Enter AWS Credentials**
* Fill in the required fields:
* **Access Key ID**
* **Secret Access Key**
* **Session Token** (if applicable)
* **Region**
4. **Test the Provider**
* Use the `Test Provider` feature to verify your connection and credentials.
5. **Save the Configuration**
* Click on the `Save` button to save the secret provider configuration.
## Next Steps
Once you have successfully added AWS Secrets Manager as a secret provider, you can:
* [Configure and fetch secrets](./configuring-and-fetching-secrets) - Learn how to set up secret mappings and retrieve secrets from AWS
* [Using secrets in Bruno](./using-secrets) - Learn how to use fetched secrets in your API requests
# Configuring and Fetching Secrets
Source: https://docs.usebruno.com/secrets-management/secret-managers/aws-secrets-manager/configuring-and-fetching-secrets
## Overview
This guide walks you through the process of configuring and fetching secrets from AWS Secrets Manager in Bruno. You'll learn how to set up secret mappings and retrieve them securely for use in your API requests.
Secrets in Bruno are environment-specific, so ensure you have a collection environment selected before proceeding.
## Prerequisites
* AWS Secrets Manager provider already configured in Bruno
* Collection environment selected
* Appropriate AWS permissions to access secrets
## Configuration Steps
### Step 1: Access Collection Secrets
1. Open your collection in Bruno
2. Navigate to **Collection Settings**
3. Go to the **Secrets** section
### Step 2: Select AWS Secrets Manager Provider
1. In the Secrets section, click on **Select Provider**
2. Choose **AWS Secrets Manager** from the dropdown menu
### Step 3: Add Secret Mappings
1. Click the **+ Add Secret** button to create a new secret mapping
2. In the configuration dialog:
* **Name**: Enter a friendly name for the secret (this will be used in Bruno)
* **AWS Secret Name/ARN**: Specify the exact AWS secret name or ARN
### Step 4: Fetch Secrets from AWS
1. Click the **Fetch Secrets** button in the top-right corner
2. Select the appropriate AWS account from the dropdown
3. Click **Fetch** to retrieve the secrets from AWS Secrets Manager
After successful fetching, you should see the secret values populated in the Secrets section and available for use in your requests.
# Overview
Source: https://docs.usebruno.com/secrets-management/secret-managers/aws-secrets-manager/overview
# AWS Secret Manager
AWS Secrets Manager is a secure and scalable service designed to store and retrieve sensitive information such as API keys, database credentials, and other secrets.
Bruno allows you to easily integrate with AWS Secrets Manager and securely access secrets during test execution without exposing them in test scripts or environment variables.
## Try it out
Explore the [aws-secret-manager](https://github.com/bruno-collections/aws-secret-manager) sample collection to see Bruno's AWS Secrets Manager integration in action:
This guide provides detailed steps to configure and utilize AWS Secrets Manager in Bruno.
* [Adding a secret provider](./adding-a-secret-provider)
* [Configuring and fetching secrets](./configuring-and-fetching-secrets)
* [Using secrets in Bruno](./using-secrets)
# Adding HashiCorp Vault Secret Manager
Source: https://docs.usebruno.com/secrets-management/secret-managers/hashicorp-vault/adding-a-secret-provider
You have the option to integrate [HashiCorp Cloud Vault ↗](https://developer.hashicorp.com/hcp), [Vault Enterprise Server ↗](https://developer.hashicorp.com/vault/docs/enterprise), or a [Local Vault server ↗](https://developer.hashicorp.com/vault/docs/install) as your secret provider.
## Setting Up Vault Server
To configure a **Vault server** secret provider, you can opt for either **Token** or **AppRole** authentication.
1. Go to the **Preferences** settings and navigate to the **Secrets Manager** section.
2. Click on the `+ Add Secret Provider` button.
3. Choose **Vault Server** as the provider type and select your preferred authentication method.
4. Enter `http://localhost:8200` if using a local server, or the URL of the hosted server (e.g., `https://vault.example.com`).
5. Provide the necessary [Token ↗](https://developer.hashicorp.com/vault/docs/commands/token) or [AppRole ↗](https://developer.hashicorp.com/vault/tutorials/auth-methods/approle) credentials.
6. Click on **Add** to save the secret provider.
## Setting up Vault Cloud
To set up a **Vault Cloud** secret provider, follow these steps:
1. Go to the **Preferences** settings and navigate to the **Secrets Manager** section.
2. Click on the `+ Add Secret Provider` button.
3. Select **Vault Cloud** as the provider type.
4. Maintain the default settings for `Token Endpoint` and `Secrets Endpoint`, or modify them if necessary.
5. Provide the required [Client Credentials ↗](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals).
6. Add one or more Projects (Apps) to the secret provider.
7. Click on **Add** to save the secret provider.
# Configuring and Fetching secrets
Source: https://docs.usebruno.com/secrets-management/secret-managers/hashicorp-vault/configuring-and-fetching-secrets
1. Go to **Collection Settings** and navigate to the **Secrets** tab.
2. Select `Vault` as the provider.
3. Choose an environment if not already selected. (Secrets are environment-specific)
4. Add a new secret by clicking on the `+ Add Secret` button.
5. Provide a name for the secret and specify the relative path to where the secret is stored in Vault.
6. Click on the `Fetch Secrets` button on the top right corner, choose one of the secret providers, and click on the `Fetch` button.
7. The fetched secrets will be displayed in the table along with their names and paths.
# Overview
Source: https://docs.usebruno.com/secrets-management/secret-managers/hashicorp-vault/overview
# HashiCorp Vault
HashiCorp Vault is a tool for managing secrets and protecting sensitive data. It is designed to provide a secure, reliable, and scalable solution for managing secrets such as API keys, passwords, certificates, and other sensitive data. Vault provides a centralized platform for storing and accessing secrets, and includes features such as encryption, access control, auditing, and more.
Bruno allows you to easily integrate Hashicorp Vault and access your secrets securely. With Bruno, you can securely store your Vault credentials and access them without exposing sensitive information.
## Try it out
Explore the [hashiCorp-vault-server](https://github.com/bruno-collections/hashiCorp-vault-server) sample collection to see Bruno's HashiCorp Vault integration in action:
In this guide, we will show you how to set up HashiCorp Vault with Bruno.
* [Adding a secret provider](./adding-a-secret-provider)
* [Configuring and fetching secrets](./configuring-and-fetching-secrets)
* [Using secrets in Bruno](./using-secrets)
# Using secrets
Source: https://docs.usebruno.com/secrets-management/secret-managers/hashicorp-vault/using-secrets
## Using Secrets in Request Fields
Secrets are accessed in the same way as collection and environment variables. The secrets can be accessed in headers, query, body, auth input fields similar to collection and environment variables.
Secrets need to be prefixed with `$secrets` followed by the `secret name` and then the `key name`, all separated by periods.
Pattern: `$secrets`.``.``.
If you have a secret named dbCredentials with a key username, you can reference it as: `$secrets.dbCredentials.username`
## Using Secrets in Scripts
You can also access secrets from within Pre-request and Post-request scripts using the `bru.getSecretVar()` function.
```javascript theme={null}
const secretValue = bru.getSecretVar('.');
console.log(secretValue); // This will log the value of your secret
// Example
const apiKey = bru.getSecretVar('payment-service.api-key');
req.setHeaders('x-api-key: ' + apiKey);
```
This approach keeps your sensitive data secure while allowing you to leverage it in your API automation scripts.
# Using with Bruno CLI
Source: https://docs.usebruno.com/secrets-management/secret-managers/hashicorp-vault/using-with-cli
## Pre-requisites
Add the vault connection configuration to the collection's **secrets.json** file (create one if it doesn't exist).
You can use environment variables in the configuration to avoid hardcoding sensitive information.
This is the only additional configuration required to use the vault secrets in the collection using the CLI.
## Usage
There are 3 different CLI configurations that can be used.
#### Vault server with token authentication
```json filename="secrets.json" theme={null}
{
"type": "vault",
"cli": {
"type": "vault-server",
"vaultServerConfig": {
"url": "http://localhost:8200",
"namespace": "bruno",
"auth": {
"method": "token",
"token": "{{authToken}}"
}
}
},
"data": [
{
"environment": "Prod",
"secrets": [
{
"name": "db",
"path": "secret/db",
"enabled": true
}
]
}
]
}
```
Corresponding CLI command:
```bash copy theme={null}
bru run --env Prod --env-var authToken=your-vault-token
```
#### Vault server with appRole authentication
```json filename="secrets.json" theme={null}
{
"type": "vault",
"cli": {
"type": "vault-server",
"vaultServerConfig": {
"url": "http://localhost:8200",
"namespace": "bruno",
"auth": {
"method": "app_role",
"appRole": {
"role": "bruno",
"roleId": "{{roleId}}",
"secretId": "{{secretId}}"
}
}
}
},
"data": [
{
"environment": "Prod",
"secrets": [
{
"name": "db",
"path": "secret/db",
"enabled": true
}
]
}
]
}
```
Corresponding CLI command:
```bash copy theme={null}
bru run --env Prod --env-var roleId=your-role-id --env-var secretId=your-secret-id
```
#### Vault cloud with client credentials authentication
```json filename="secrets.json" theme={null}
{
"type": "vault",
"cli": {
"type": "vault-cloud",
"vaultCloudConfig": {
"auth": {
"method": "client-credentials",
"clientCredentials": {
"tokenEndpoint": "{{tokenEndpoint}}",
"secretsEndPoint": "{{secretsEndpoint}}",
"clientId": "{{clientId}}",
"clientSecret": "{{clientSecret}}"
}
},
"project": {
"name": "{{projectName}}",
"projectId": "{{projectId}}",
"organizationId": "{{organizationId}}"
}
}
},
"data": [
{
"environment": "Prod",
"secrets": [
{
"name": "db",
"path": "secret/db",
"enabled": true
}
]
}
]
}
```
Corresponding CLI command:
```bash copy theme={null}
bru run --env Prod --env-var tokenEndpoint=your-token-endpoint --env-var secretsEndpoint=your-secrets-endpoint --env-var clientId=your-client-id --env-var clientSecret=your-client-secret --env-var projectName=your-project-name --env-var projectId=your-project-id --env-var organizationId=your-organization-id
```
# Overview
Source: https://docs.usebruno.com/secrets-management/secret-managers/overview
# Integration with Secret managers
For users of Bruno Ultimate, you can take your secret management one step further and perform an integration with a secret vault.
Bruno stores secrets on your local machine. The location depends on the OS. The secrets are encrypted using OS level encryption when available or falls back to AES256 encryption.
Currently, we support integrations with:
* [HashiCorp vault](/secrets-management/secret-managers/hashicorp-vault)
* [AWS Secrets Manager](/secrets-management/secret-managers/aws-secrets-manager)
* [Azure Key Vault](/secrets-management/secret-managers/azure-key-vault)
# Secret Masking in Reports
Source: https://docs.usebruno.com/secrets-management/secret-masking
## Overview
Bruno automatically masks sensitive information in reports to protect your secrets from being exposed. This documentation explains what gets masked.
## What gets masked?
### 1. Sensitive Headers (Always Masked)
Bruno automatically masks these header names regardless of their values:
| Header Name | Example | Masked Result |
| --------------- | -------------------------------- | ----------------- |
| `Authorization` | `Bearer eyJhbGciOiJIUzI1NiIs...` | `Bearer ********` |
| `X-API-Key` | `sk-1234567890abcdef` | `********` |
| `Cookie` | `session=abc123; auth=xyz789` | `********` |
| `Set-Cookie` | `session=abc123; HttpOnly` | `********` |
| `X-Auth-Token` | `token123456` | `********` |
| `Client-Secret` | `secret_abc123` | `********` |
**Complete list of sensitive headers:**
* `authorization`, `proxy-authorization`
* `x-api-key`, `x-auth-token`, `x-csrf-token`, `x-xsrf-token`
* `cookie`, `set-cookie`
* `api-key`, `x-access-token`
* `session-token`, `x-session-token`, `x-refresh-token`
* `x-id-token`, `x-jwt-assertion`
* `client-secret`, `secret-key`
* `x-wsse`, `www-authenticate`
### 2. Secret Environment Variables
Bruno masks all values of environment variables marked as secret in the UI:
* **`baseUrl` variable**: Marked as secret (orange checkmark in Secret column), so its value is masked as `****` in the interface
* **`JWT_TOKEN` variable**: Not marked as secret (unchecked Secret column), so the value will be displayed in plain text coming from `.env` file.
**Result:** Every instance where `baseUrl` is referenced appears masked in the report.
### 3. External Secrets
Bruno masks secrets fetched from external providers:
* **HashiCorp Vault** secrets
* **AWS Secrets Manager** values
* **Azure Key Vault** secrets
### In .env Files
All values in `.env` files are treated as secrets:
```env theme={null}
API_KEY=sk-1234567890abcdef
CLIENT_SECRET=secret_abc123
DATABASE_URL=postgresql://user:pass@host:5432/db
```
## Where Masking Appears
Bruno masks secrets in:
* **HTML reports**
* **JSON reports**
* **JUnit reports**
# Secret Variables
Source: https://docs.usebruno.com/secrets-management/secret-variables
Bruno stores secrets on your local machine. The location depends on the OS.
The secrets are encrypted using OS level encryption when available or falls back to AES256 encryption.
In this approach, you can check the `secret` checkbox for any variable in your environment.
Bruno will manage your secrets internally and will not write them into the environment file.
Your environment file at `environments/local.bru` would look like
```bash filename="local.bru" theme={null}
vars:secret [
baseUrl
]
```
And now you can safely check in your collection to source control without worrying about exposing your secrets.
When you export your collection as a file, Bruno will not export the secret variables.
# Body Data
Source: https://docs.usebruno.com/send-requests/REST/body-data
The **Body** tab in Bruno allows you to send specific data with your API requests. You can choose from various data formats, such as Form, Raw, and others, using the dropdown menu.
## Send Body Data with API Requests
You can select the data type based on your API request requirements. For example, if you're working with a request where you need to send specific details about a particular item, you’ll likely choose JSON as the data type and include the data in the body of the request.
### Form-Data
#### 1. Multipart Form
Used for sending files along with form data, allowing multiple parts with different content types in a single request. In multipart form data, each part of the request is separated by a boundary.
1. Navigate to the **Body** tab.
2. Select **Multipart Form** from dropdown.
3. Click on **Add File** and upload one or more files.
4. (optional) You can also select **Add Param** for sending key-value pair data.
5. Save and execute the request.
#### Upload multiple files in a single field
A file field can hold more than one file. Each selected file appears as a chip in the field — use the upload button to add more files, or the **×** on a chip to remove an individual file. Removing the last file converts the field back to a text param.
* File paths are normalized relative to the collection so collections stay portable.
* Selecting the same file again is deduplicated.
* The `Content-Type` for the field is auto-detected from the file list; you can override it from the field's settings.
#### 2. URL Encoded
It is commonly used for simple form submissions where data is sent as key-value pairs. Each key and value are separated by an equal sign (=), and multiple pairs are separated by an ampersand (&).
1. Navigate to the **Body** tab.
2. Select **Form URL Encoded** from dropdown.
3. Click on **Add Param** and add key-value pair.
4. Save and execute the request.
### Raw Data
Raw Data allows you to send data in its raw, unprocessed form in the body of an API request. It doesn’t require a predefined structure like key-value pairs or multi-part boundaries. Instead, you can send data exactly as it is, which is useful when dealing with structured data formats such as **JSON, XML, Sparql or plain text**.
1. Navigate to the **Body** tab.
2. In the RAW section, select your preferred data format.
3. Add appropriate data for your API request.
4. Save and execute the request.
### Other (File / Binary / No Body)
The **Other** option on the **Body** tab provides quick choices to send a file or to send no body.
1. Navigate to the **Body** tab.
2. From the dropdown, choose **Other**.
3. Two controls are available:
* **File / Binary**: Send a file directly as the request body. Click **Select File** (or drag-and-drop) to pick the file. Bruno sends the file as-is (Content-Type will be inferred or set it in **Headers**).
* **No Body**: Send the request with an empty body (useful for requests that require no payload).
4. (optional) Verify the **Content-Type** header if your server expects a specific media type for the uploaded file.
5. Save and execute the request.
# Code Generator
Source: https://docs.usebruno.com/send-requests/REST/code-generator
Bruno makes it easy to generate code snippets from your API requests, allowing you to quickly integrate and test your API in various programming languages. This feature supports multiple languages (35+) and can help you save time when writing client code for your API.
## How Code Snippets Work in Bruno
Bruno’s Code Snippet feature automatically generates the necessary code to make the same API request in various programming languages, such as **Python, JavaScript, cURL,** and others. This makes it easy to share and implement API calls directly into your application.
### Steps to Generate Code Snippets in Bruno
1. Create or Open an API request.
2. Go to the **Generate Code** Section (next to the request).
3. Select your desired programming language from the dropdown to generate the code snippet.
## Import a request from cURL
You can create a request by pasting a **cURL** command into Bruno (the reverse of generating cURL from a saved request).
1. In the sidebar, click **`···`** on the **collection** → **New Request**.
2. Set **Request type** to **From cURL**, then paste your cURL into the input.
3. Click **Create** to import the request.
Bruno opens the new request with method, URL, headers, and body filled from the cURL command.
For more ways to create requests, see [Create a request](/get-started/bruno-basics/create-a-request).
# Overview
Source: https://docs.usebruno.com/send-requests/REST/overview
A REST API (Representational State Transfer) is a popular architectural style for designing APIs and web services. It enables communication between client and server through standard HTTP methods such as GET, POST, PUT, DELETE, and PATCH to transfer data.
REST APIs are stateless, meaning the server does not store any information about the client between requests. This stateless nature makes REST APIs easier to maintain and scale.
### HTTP Methods
| HTTP Method | Usage |
| -------------------- | ------------------------------------------ |
| `GET /users` | Retrieve a list of users. |
| `POST /users` | Create a new user. |
| `PUT /users/{id}` | Update the information of a specific user. |
| `DELETE /users/{id}` | Delete a user. |
### HTTP Status Codes
HTTP status codes are issued by a server in response to a client's request made to the server. They provide information about the outcome of the request. Here's a table of some common HTTP status codes and their descriptions:
| **Status Code** | **Description** |
| --------------- | --------------- |
| 100 | Informational |
| 200 | Successful |
| 300 | Redirection |
| 400 | Client Error |
| 500 | Server Error |
# Parameters
Source: https://docs.usebruno.com/send-requests/REST/parameters
Bruno enables you to send data with your HTTP requests. You can include both query and path parameters in the request and define their corresponding values.
1. [Query Parameters](#query-parameters)
2. [Path Parameters](#path-parameters)
## Query Parameters
Query parameters are a way to pass additional information to a server via the URL when making an HTTP request. They are commonly used in GET requests to filter, sort, or specify the data you're asking for.
### How Query Parameters Work
1. Query parameters are appended to the end of the URL after a `?` symbol.
2. Each parameter consists of a **key-value** pair.
3. If there are multiple query parameters, they are separated by an `&` symbol.
### Create Query Parameters in Bruno
1. Create request inside collection.
2. Navigate to the **Params** tab.
3. Enter the **Name** of the parameter and its corresponding **Value**.
You’ll notice that the values are appended to the URL, starting with a `?`.
You can choose which parameters to include or exclude by clicking the checkbox next to each one.
## Path Parameters
A path parameter is a dynamic value embedded directly in the URL path of an API request. It is used to specify particular resources or data points within an API endpoint.
### How Path Parameters Work
1. Path parameters are embedded directly in the URL path.
2. They are represented as placeholders preceded by a colon (`:`).
3. Parameter names must start with a letter or underscore, followed by alphanumeric characters or underscores (e.g., `:id`, `:userId`, `:_type`). A colon followed by a digit (e.g., `:00`) is not treated as a path parameter.
4. When making the request, the placeholder is replaced with a dynamic value, which is part of the URL.
**Example**:
```bash theme={null}
https://api.example.com/users/:id
```
`:id` is a path parameter.
Colons followed by digits in URLs (such as in datetime values like `T00:00:00`) are not interpreted as path parameters.
### Create Path Parameters in Bruno
1. Create request inside collection.
2. Navigate to the **Params** tab.
3. Add the path parameter variable in the URL. e.g. `/users/:user`
4. Enter the value in the **path parameter** section for the corresponding variable.
5. Save and execute the request.
# Request headers
Source: https://docs.usebruno.com/send-requests/REST/req-header
Request headers are **key-value** pairs sent along with an HTTP request to provide additional information to the server about the request. The request headers give the server more information about how to process the incoming request.
**Default Headers**: Default headers are predefined headers that are part of the HTTP specification.
**Custom Headers**: Custom headers are headers that are **user-defined** and are not part of the official HTTP specification.
## Create request headers in Bruno
1. Create request inside collection.
2. Navigate to the **Headers** tab.
3. Enter the **Name** of the parameter and its corresponding **Value**.
4. Save and execute the request.
To view default headers, navigate to the **Headers** tab in your request. For
custom headers, check the **Timeline** tab.
# Request Settings
Source: https://docs.usebruno.com/send-requests/REST/request-settings
Bruno provides advanced request execution options that can be configured on a per-request basis through the **Settings** tab in the request pane. These settings allow you to fine-tune how individual requests behave during execution, overriding global or default configurations.
## Accessing Request Settings
1. **Open a request** in your collection
2. **Click on the Settings tab** in the request pane (alongside existing tabs like Headers, Body, etc.)
3. **Configure the desired options** for your request
## Available Settings
### Automatically Follow Redirects
Controls whether HTTP redirects should be followed automatically during request execution.
**Options:**
* **Enabled (true)**: Automatically follow redirects (default behavior)
* **Disabled (false)**: Do not follow redirects, return the redirect response
### Max Redirects
Limits the maximum number of redirects that can be followed in a single request.
**Configuration:**
* **Type**: Numeric input
* **Range**: 1 to 50 (validation enforced)
* **Default**: 5 redirects
### Request Timeout
Sets the timeout duration for the request in milliseconds.
**Configuration:**
* **Type**: Numeric input (milliseconds)
* **Range**: 1000 to 300000 (1 second to 5 minutes)
* **Default**: 30000 (30 seconds)
### URL Encoding
Controls whether URL parameters should be automatically encoded during request execution.
**Options:**
* **Enabled (true)**: Automatically encode URL parameters (default behavior)
* **Disabled (false)**: Do not encode URL parameters, send raw values
# REST API request
Source: https://docs.usebruno.com/send-requests/REST/rest-api
Bruno enables you to send API requests using both HTTP and GraphQL protocols. You can include various details in the request, such as parameters, body, headers, and more, based on the API specifications.
You can select multiple HTTP methods such as `GET` and `POST` while working with HTTP protocol.
## Create new request
1. Create new request inside collection.
2. Select **HTTP** as request type.
3. Provide **URL** and **Name** in the input box.
4. Click on **Create** button.
Once you create the new request, you can execute it by clicking the arrow button or pressing Cmd + Enter. After executing the request, the response from the server will be displayed in the Response tab.
If the URL is missing a scheme (e.g. `example.com/users`), Bruno automatically prepends `http://` when sending the request. Provide `https://` explicitly when you need a secure connection.
## Select multiple request methods
In Bruno, the **GET** method is set as the default HTTP method for new requests. However, you can select other HTTP methods (such as **POST**, **PUT**, **DELETE**, etc.) based on your API requirements to send data.
## Custom HTTP Methods
Bruno supports custom HTTP methods beyond the standard ones (GET, POST, PUT, DELETE, etc.). This is useful for APIs that use non-standard methods like Vault's LIST method or other custom protocols.
### Adding a Custom HTTP Method
1. **Expand the HTTP method dropdown** in the request pane
2. Click **"Add Custom"** from the dropdown options
3. **Type your custom method name** in the input field (e.g., `LIST`, `PATCH`, `SEARCH`)
## Request Settings
Bruno provides advanced request execution options through the **Settings** tab in the request pane. You can configure per-request settings for:
* **Automatically Follow Redirects**: Control whether redirects should be followed automatically
* **Max Redirects**: Limit the number of redirects (e.g., max\_redirects: 3)
* **Request Timeout**: Set request timeout in milliseconds (e.g., timeout: 10000)
These settings override global configurations and persist per request. For detailed information, see [Request Settings](/send-requests/REST/request-settings).
## Get Started with REST APIs
Explore our sample GitHub REST API collection to see practical examples and test different HTTP methods:
# GraphQL Request
Source: https://docs.usebruno.com/send-requests/graphql/graphql-api
Bruno provides the capability to send GraphQL requests, allowing you to interact with APIs that use the GraphQL query language.
## Create GraphQL request
1. Create new request from collection context menu (...) and select **New Request**.
2. Select **GraphQL** as request type with:
* **URL**: The URL of the GraphQL endpoint.
* **Name**: The name of the request.
3. Click on **Create** button.
## GraphQL request with query
After creating the request:
* Go to **Query** section where you can write and execute all your GraphQL queries
* You can also use the **Variables** section to pass variables to your query
**Example Query:**
You can test this query using the below GraphQL API: `https://graphqlzero.almansi.me/api`
```graphql theme={null}
query Albums {
albums {
data {
id
title
}
}
}
```
## Get Started with GraphQL APIs
Explore our sample [REST and GraphQL collection](https://github.com/bruno-collections/rest-graphql-collection) to see practical examples and test GraphQL endpoints:
# GraphQL Variables
Source: https://docs.usebruno.com/send-requests/graphql/graphql-variables
Variables in GraphQL allow you to create dynamic, reusable, and secure queries. Instead of embedding values directly in your query string, you can use variables to pass values separately. This approach offers several benefits:
* **Security**: Prevents GraphQL injection attacks
* **Reusability**: Same query can be used with different values
* **Type Safety**: Variables are strongly typed
* **Readability**: Makes queries cleaner and easier to understand
## Adding Variables to Your GraphQL Request
1. Create a GraphQL Request
2. Add variables in the **Variables** section (bottom of query section)
3. Write your query or mutation with variables using the `$` prefix to declare variables and reference variables using the `$variableName` syntax.
**Variables (JSON format):**
```json theme={null}
{
"title": "usebruno",
"userId": "1"
}
```
In this example, the `$title` and `$userId` are variables defined as a string and number respectively, and its value `"usebruno"` and `1` are passed separately in the Variables section.
# Overview
Source: https://docs.usebruno.com/send-requests/graphql/overview
Bruno can send requests using GraphQL, an open-source query language and runtime for APIs. Each GraphQL request has a URL (the data endpoint) and a query (defining the data to retrieve or modify).
## GraphQL request interface
GraphQL APIs let clients request only the data they need through a single endpoint. It’s schema-driven, allowing clients to introspect and avoid overfetching or underfetching data.
**[Query builder](/send-requests/graphql/query-builder)** is also supported to help you build your queries easily.
The API schema defines available fields, and the request may include authentication, headers, and settings as needed.
GraphQL requests support three operations:
* **Query**: Retrieves data, specifying fields and optional arguments for precise results.
* **Mutation**: Modifies data (create, update, delete), with specified fields returned after the operation.
* **Subscription**: Provides real-time updates by listening for changes to specific data fields over a persistent connection.
1. [GraphQL API](./graphql-api)
2. [GraphQL Variables](./graphql-variables)
3. [Query Builder](./query-builder)
# Query builder
Source: https://docs.usebruno.com/send-requests/graphql/query-builder
Build GraphQL queries visually using an interactive schema explorer.
The query builder lets you construct GraphQL queries, mutations, and subscriptions through a visual interface. Instead of writing query syntax manually, you select fields from your schema using checkboxes and the query is generated automatically.
## Prerequisites
* A GraphQL request created in your collection
* Access to a GraphQL endpoint that supports introspection, or a local schema file
## Open the query builder
1. Open a GraphQL request and navigate to the **Query** tab.
2. Click the **sidebar toggle icon** in the toolbar on the right side of the query editor.
The query builder opens as a resizable panel to the left of the query editor. You can drag the panel border to adjust its width.
## Load a schema
The query builder requires a GraphQL schema to display available fields. You can load a schema in two ways:
* **Load from introspection** — Sends an introspection query to the endpoint URL and builds the schema from the response.
* **Upload schema file** — Loads a schema from a local `.graphql` or `.json` file on your machine.
Once loaded, the schema is cached locally so you don't need to reload it each time.
You can also load or refresh the schema from the dropdown menu in the query editor toolbar.
## Build a query
After the schema loads, the query builder displays the available root operation types (Query, Mutation, Subscription) with their field counts.
**Mutation:**
### Select fields
* Expand a root type by clicking the chevron to see its fields.
* Check a field's checkbox to include it in the generated query.
* Expand non-leaf fields to see nested child fields, then check the ones you need.
* Selecting a nested field automatically selects its parent fields.
Only one operation type can be active at a time. If you have fields selected under Query, the Mutation and Subscription sections are disabled.
### Work with arguments
When you expand a field that accepts arguments, an **Arguments** section appears above its child fields.
* Each argument has a checkbox to enable or disable it.
* Required arguments (marked with `!`) are automatically enabled when you select the field.
* Enabled arguments display a value input where you can enter the argument value.
* The input type adapts to the argument — enum types show a dropdown, booleans show a true/false selector, and other scalars show a text input.
Arguments are automatically converted to GraphQL variables. The query builder adds `$variable` declarations to the operation and populates the variables JSON below the query editor.
### Work with union types
For fields that return union types, the query builder displays inline fragment options (`... on TypeName`). You can expand and select fields for each union member independently.
### Search fields
Use the search box at the top of the query builder to filter root-level fields by name. This is useful when working with schemas that have many operations.
## Bidirectional sync with the query editor
The query builder and the query editor stay in sync:
* **Builder to editor** — When you check fields, toggle arguments, or set values in the builder, the query text updates automatically in the editor.
* **Editor to builder** — When you edit the query text directly, the builder updates its selections to match.
You can freely switch between the visual builder and the text editor, or use both at the same time.
The query builder supports a single operation at a time. If the editor contains multiple operations, a sync error message is displayed. Combine them into one operation to restore sync.
## Limitations
* The query builder supports one operation per request.
* Nested depth is limited to 7 levels.
* List arguments for complex input object types (for example, `[InputType!]`) are not supported in the visual builder. Use the query editor directly for these cases.
# gRPC Proto Files
Source: https://docs.usebruno.com/send-requests/grpc/grpc-proto
Proto files (`.proto`) are the foundation of gRPC services, defining the contract between clients and servers. Bruno supports adding proto files to enhance your gRPC development experience with better IntelliSense, method discovery, and type safety.
## Proto File Options
You have two ways to add Proto files for enhanced IntelliSense and method discovery:
**Option 1: Request Level**
1. Open your gRPC request in Bruno
2. In the gRPC interface, locate the **Using Reflection** section
3. Click on the **Browse for proto file** button or toggle the proto file option
4. Click the file browser to select `.proto` files from your local system
**Option 2: Collection Level**
1. Go to collection level settings
2. Navigate to the **gRPC** tab
3. Click the **Add Proto File** to select `.proto` files from your local system
Add Proto files at the collection level for reuse across all requests.
Bruno will validate that the selected files are valid proto files.
You can add multiple proto files if your service uses imports or multiple definitions.
## Import Paths
When working with proto files that import other proto files, Bruno will always try to resolve imports within the folder where the proto file resides. However, when working with multiple services, you may have common types (like `google/protobuf` types or shared definitions) that are used across different services. Instead of duplicating these common proto definitions in each service folder, you can use import paths to define these common types in a single location and reference them from all dependent services.
### Adding Import Paths
To configure import paths for your proto files:
1. Go to **Collection Settings**
2. Navigate to the **Protobuf** tab
3. In the **Import Paths** section, click **Add Import Path**
4. Specify the directory path where Bruno should look for imported proto files
### How Import Paths Work
When Bruno encounters an import statement in a proto file:
```protobuf theme={null}
import "common/user.proto";
import "google/protobuf/timestamp.proto";
```
Bruno will search for these files in the following order:
1. The same directory as the importing proto file
2. All specified import paths (in the order they were added)
3. Standard protobuf library paths
### Example Setup
Consider this directory structure:
```
my-api/
├── collection/
│ ├── user_service.proto
│ └── order_service.proto
├── shared/
│ ├── common/
│ │ └── user.proto
│ └── types/
│ └── timestamp.proto
└── external/
└── google/
└── protobuf/
└── timestamp.proto
```
To make this work, you would add these import paths in collection settings:
* `../shared` (for common/user.proto and types/timestamp.proto)
* `../external` (for google/protobuf/timestamp.proto)
### Automatic Import Path Management
Bruno automatically manages import paths when you add proto files at the request level:
1. When you add a proto file to a request, Bruno automatically adds its directory to the collection's import paths
2. This ensures that any imports within that proto file are properly resolved
3. The import paths are shared across all requests in the collection
# gRPC Request
Source: https://docs.usebruno.com/send-requests/grpc/grpc-request
Bruno provides comprehensive support for gRPC requests, enabling you to test and interact with gRPC services directly from the interface. With Bruno's gRPC support, you can:
* **Add Proto files** for enhanced IntelliSense and method discovery
* **Use Server Reflection** as an alternative to Proto files for automatic method suggestions
* **Call server methods** with full parameter support and type validation
* **Test real-time streaming APIs** including unary, server streaming, client streaming, and bidirectional streaming
## gRPC Interface
Bruno gRPC interface provides an intuitive way to interact with gRPC services. The interface automatically adapts based on whether you're using Proto files or Server Reflection.
## Creating a gRPC Request
### Step 1: Create a gRPC Request
1. Open the collection context menu and select **New Request** from the dropdown
2. Select **gRPC** as your request type
3. Enter the **service endpoint** in the URL field (e.g., `https://grpcb.in/`)
### Step 2: Configure the Request
1. Click on **Reflection** and choose the **method** you want to call from the dropdown
**Refresh**: The gRPC refresh feature caches server information in memory for better performance. If you're running a local gRPC server and make changes to your service definitions, click the **Refresh** button next to the **Using Reflection** toggle to reload the latest changes from your server.
2. Use the **search functionality** in the dropdown to quickly find your desired method by typing the method name
### Step 3: Send the Request
1. Add any **required inputs** in the message section (auto generate with **Auto Fill** button)
2. Click the **Send** button to execute your gRPC request.
You'll see the response in the **Response** section with detailed information about the request execution.
To learn about proto files, navigate to the [Adding Proto file section.](./grpc-proto)
# gRPC Streams
Source: https://docs.usebruno.com/send-requests/grpc/grpc-streams
gRPC streaming enables real-time, bidirectional communication between clients and servers. Bruno supports all four types of gRPC streaming patterns, allowing you to test and interact with streaming APIs efficiently.
## Understanding gRPC Streaming
gRPC streaming allows you to send and receive multiple messages over a single connection, making it ideal for:
* **Unary streaming** (simple request-response, CRUD operations, authentication)
* **Client streaming** (batch processing, data collection)
* **Server streaming** (real-time notifications, live data feeds, progress updates)
* **Bidirectional streaming** (chat applications, collaborative editing, gaming)
## Streaming Workflow
### Step 1: Configure Request
1. Open your collection and create a new gRPC request
2. Add your gRPC server URL (e.g., `https://grpcb.in/`)
3. Select a streaming method from the dropdown
### Step 2: Auto-Generate or Add Proto Files
You have two options to add message schema:
**Option A: Auto-Generate Messages**
* Bruno can auto-generate message templates based on server reflection (Click the **Auto Fill** 🔄 button)
* This works when your gRPC server has reflection enabled
**Option B: Add Proto Files**
* Upload your `.proto` files (request or collection level) for enhanced IntelliSense
* Provides better type safety and method discovery
### Step 3: Build Stream Connection
1. **Start Stream**: Click the send button to establish the bidirectional connection
### Step 4: Send Multiple Messages
Once the stream is active:
1. **Send Message**: Click the **Send gRPC Message** button to transmit your message (next to **Auto Fill** button)
2. **Add More Messages**: Continue sending additional messages using **Add Message**
### Step 5: End Stream and View Timeline
1. **End Message Stream**: Click the ✓ icon (end stream button) to stop sending messages
2. **View Response Timeline**: See the complete conversation timeline with timestamps
# Overview
Source: https://docs.usebruno.com/send-requests/grpc/overview
GRPC support is available from Bruno 2.10.0.
gRPC (Google Remote Procedure Call) is a modern, high-performance Remote Procedure Call (RPC) framework developed by Google. It's designed for efficient communication when one service needs to interact with another. Imagine your Authentication Service needs to tell your Notification Service to send an OTP.
In a high-traffic scenario, traditional request-response calls (like REST over HTTP/1.1) can be less efficient and introduce latency. gRPC solves this by faster communication, efficient data transfer and streaming capabilities.
## Launch gRPC in Bruno
Explore our sample [gRPC collection](https://github.com/bruno-collections/gRPC-api-collection) to get started:
1. Click the **Fetch in Bruno** button below
2. Choose a location to store the collection locally and start exploring gRPC feature
# Request History
Source: https://docs.usebruno.com/send-requests/history
The History feature in Bruno allows you to view a detailed log of all the requests you've executed and worked with within your collections. This feature helps you track and revisit past actions, making it easier to manage and troubleshoot your workflow.
## How It Works
The History feature automatically tracks the actions and requests made within your collections. Each time you run a request or make changes, it is logged in the history, providing you with a comprehensive timeline of your interactions.
### View History Logs
To view your history follow the below steps:
1. Navigate or create a request in Bruno.
2. Click on the **History** feature icon (top right corner).
3. You will have access to the complete request activity log.
# Overview
Source: https://docs.usebruno.com/send-requests/overview
Whether you're developing your own API or testing a third-party one, you can use Bruno as an **API Testing Client** to send requests and receive responses. With Bruno, you can create, update, delete, and send data with custom parameters and authorization details.
When you send a request and receive a response through Bruno, the interface displays the information clearly, allowing you to debug errors, handle responses, and resolve any issues efficiently.
In the above example we are sending `Post` request to Bruno echo server with json body. The echo server will respond with same data we sent in a request as a output.
### Getting Started with API Requests in Bruno
Bruno supports the following protocols for sending API requests:
* [REST API](./REST/overview)
* [GraphQL API](./graphql/overview)
* [SOAP API](./soap/soap-request)
* [gRPC](./grpc/overview)
* [WebSocket](./websocket/overview)
# Cookies in Bruno
Source: https://docs.usebruno.com/send-requests/res-data-cookies/cookies
Cookies are used to store user information, session data, and provide personalized content. Bruno allows you to add, send, view, and delete request cookies. After executing the request, you can easily inspect the cookies by clicking on the **Cookies** icon.
## Cookie Attributes
* **Domain**: Specifies the domain where the cookie is valid.
* **Path (optional)**: Specifies the URL path where the cookie will be sent.
* **Key**: The name/identifier of the cookie.
* **Value**: The data that the cookie will store.
* **Expiration**: The expiration date and time for the cookie.
* **Secure**: Ensures the cookie is only sent over HTTPS.
* **HTTP Only**: Restricts access to the cookie from JavaScript.
## Configuring Cookies in Bruno
### Add Cookie
1. Go to cookies section (bottom-right corner).
2. Click on **Add Cookie** button.
3. Add the cookie attributes and select **Secure** or **HTTP Only** according to your preference.
4. Click on **Save** button.
5. You can enable **Raw Edit** mode to quickly customize and make direct changes to your cookies.
### Update Cookie
You can update and delete cookie(s) by clicking the icons next to it.
1. Click on (pencil icon) to update cookie.
2. Click on delete icon (red colour) to delete cookie.
We restrict editing of values like Domain, Path, and Key once a cookie is
created due to security concerns.
To manage cookies programmatically, see the [Cookie Management](/testing/script/javascript-reference#cookie-management) section in the JavaScript API Reference.
# Response Data and Cookies
Source: https://docs.usebruno.com/send-requests/res-data-cookies/overview
When an API request is executed, the server returns response data. Bruno helps you inspect, debug, and troubleshoot APIs, and even allows you to download the response data as an HTML document. Additionally, you can manage and interact with the cookies associated with the API.
## Response Data
Once the request is executed, Bruno displays the response data from the API. You can view the following in the response panel:
* Request and response headers in the timeline
* Response body
* Test results
You can also:
* Save response as an example for documentation
* Clear the response data
* View the request status code
* View the API response in both web and raw formats
* Export response data to file
## Cookies
Bruno shows the cookies associated with each request. Cookies are typically used for storing user sessions and personalized content.
## Debugging
Bruno includes a Chrome Console and DevTools, which are valuable tools for inspecting requests and troubleshooting issues. Some useful features in DevTools include:
* JSON to YAML conversion
* JWT decoding
* Other features to help you work through complex issues when dealing with APIs
# Response Data
Source: https://docs.usebruno.com/send-requests/res-data-cookies/res-data
The API returns data as a response from the server after the request is executed. The response panel in Bruno provides you with an organized view of the API's response, including the body, headers, HTTP status code, raw response code, and more.
### Response Panel
After executing a request, the response panel provides detailed information on the API's response. It shows the response body, headers, status code, and other relevant data. You can also search within the response using the `cmd+f` or `ctrl+f` keyboard shortcut.
### Data Type Selector
Bruno provides a powerful response format selector that allows you to view API responses in different formats, making it easier to inspect and analyze response data. This feature includes both an **Editor** view for viewing formatted code and a **Preview** view for visualizing the response content.
### Headers
The request headers are displayed to show what was sent to the API. This can help in debugging and understanding the context of the request.
### Timeline
The **Timeline** tab lists every HTTP call from a request run along with the main request plus any calls made by scripts. See [Timeline](/debugging/timeline) for context badges (`main`, `sendRequest`, `runRequest`), filter tabs, and per-request details.
### Response Actions
Bruno provides multiple options to manage your response data, including saving as examples, downloading to files, copying, and clearing the response panel.
* **Save as Example** - Save the response as a sub-request under your main request for documenting different API behaviors and scenarios
* **Download Response** - Export the response data to a file in various formats such as HTML
* **Copy Response** - Quickly copy the response data to your clipboard for use in other applications
* **Change Layout** - Switch between different layout views to customize how the response is displayed
* **Clear Response** - Reset or clear the response data from the panel
**Learn more:** [Response Examples](/send-requests/res-data-cookies/response-examples)
# Response Examples
Source: https://docs.usebruno.com/send-requests/res-data-cookies/response-examples
Response examples allow you to save and document different response scenarios directly from your API requests. This feature makes it easy to capture, organize, and share various API response states without cluttering your main request.
Response examples are perfect for documenting success cases, error scenarios, edge cases, and different data states your API might return.
## What are Response Examples?
Response examples are saved snapshots of API responses that are stored as sub-requests under your main request. Each example captures:
* **Request details**: Parameters, headers, and body used to generate the response
* **Response data**: The complete response returned by the API
* **Response headers**: All headers returned with the response
This makes it easy to:
* Document different API behaviors
* Share examples with team members
* Test with realistic data
* Compare responses over time
* Maintain API documentation with real examples
## Saving a Response as an Example
When you execute a request and receive a response, you can save it as an example for future reference.
### Steps to Save Response Example
1. **Execute your request** and wait for the response
2. **Locate the Save Response icon** in the Response tab (top-right corner)
3. **Click the Save Response icon**
4. **Enter a name** for your example in the dialog that appears
5. **Click Save** to create the example
The example will be created as a sub-request under your main request and will be instantly accessible.
## Understanding Example Requests
Once saved, example requests appear as nested items under your main request in the sidebar.
### Example Request Structure
Example requests have a **simplified interface** with only the essential tabs:
**Request Pane:**
* **Params** - View the query parameters used
* **Headers** - View the request headers sent
* **Body** - View the request body (for POST, PUT, PATCH requests)
**Response Pane:**
* **Response** - View the saved response data
* **Headers** - View the response headers received
Example requests are **read-only** by design. They serve as documentation and reference, not as executable requests.
# SOAP API request
Source: https://docs.usebruno.com/send-requests/soap/soap-request
Bruno enables you to make HTTP calls using Simple Object Access Protocol (SOAP), a messaging protocol used for exchanging structured information between systems over a network. SOAP defines a standard for request-response communication and is based on XML data format.
**Working with WSDL files?** Bruno can automatically import WSDL files and generate SOAP request collections. Learn more in the [WSDL to Bruno Converter guide](/converters/wsdl-to-bruno).
### Key Components of a SOAP Message
1. **Envelope**: The outermost element that defines the start and end of the message.
2. **Header**: (Optional) Contains meta-information or additional information about the message.
3. **Body**: The main part of the message that contains the actual data or the request/response payload
## Create SOAP request
You have two options for creating SOAP requests in Bruno:
### Option 1: Import from WSDL (Recommended)
If you have a WSDL file, Bruno can automatically generate SOAP requests with pre-configured envelopes:
1. Right-click on a collection and select **Import Collection**
2. Choose **WSDL** and upload your `.wsdl` file
3. Bruno will generate all SOAP operations with proper XML structure
[Learn more about WSDL import →](/converters/wsdl-to-bruno)
### Option 2: Create Manually
1. Create new request inside a collection in Bruno.
2. Select **HTTP** and add URL and name.
3. Click on **Create** button.
## Adding body data
1. After creating request, Go to **Body**.
2. Select **XML** from the dropdown list.
3. Add your XML data in the textarea section.
4. Save and Execute the request.
If your request is successful, Bruno will display the response in the **Response** tab.
# Create WebSocket Request
Source: https://docs.usebruno.com/send-requests/websocket/create-request
Creating a WebSocket request in Bruno allows you to establish real-time connections and test bidirectional communication with WebSocket servers.
## Creating a WebSocket Request
### Step 1: Add New Request
1. **Open your collection** in Bruno
2. **Right-click** on the collection or folder where you want to add the request
3. **Select "New Request"** from the context menu
4. **Choose "WebSocket"** as the request type
### Step 2: Configure WebSocket URL
1. **Enter the WebSocket URL** in the address bar
* Use `ws://` for unsecured connections
* Use `wss://` for secured connections (recommended for production)
**Example URLs:**
```
wss://echo.websocket.org
```
### Step 3: Configure Headers
1. **Click the Headers tab**
2. **Add custom headers** as needed:
* **Authorization**: Bearer tokens, API keys
* **Custom Headers**: Any additional metadata
* **User-Agent**: Custom user agent strings
**Common Headers:**
```
Authorization: Bearer your-token-here
X-API-Key: your-api-key
User-Agent: Bruno-WebSocket-Client
```
### Step 4: Set Connection Options
1. **Click the Settings tab**
2. **Configure connection settings**:
**Connection Timeout**
* Set the maximum time to wait for connection establishment
* Default: 30 seconds
* Range: 1-300 seconds
**Auto-reconnect**
* Enable automatic reconnection on connection loss
* Useful for testing resilient WebSocket implementations
### Step 5: Configure Subprotocols (Optional)
Bruno supports WebSocket subprotocols, allowing you to specify the application-level protocol to use over the WebSocket connection.
**Setting a Subprotocol**
WebSocket subprotocols enable your client to specify which protocol variant it wants to use. To set a subprotocol in Bruno:
* Add the `Sec-WebSocket-Protocol` header in the Headers tab
* Specify one or multiple subprotocols
**Example:**
```
Sec-WebSocket-Protocol: mqtt, wamp
```
**Setting WebSocket Version**
You can specify the WebSocket protocol version using the `Sec-WebSocket-Version` header. This is useful when testing compatibility with servers that support multiple WebSocket versions:
```
Sec-WebSocket-Version: 13
```
WebSocket protocol version 13 (RFC 6455) is the current standard. Most modern servers use this version by default.
**Server Rejections**
If the server doesn't support the requested subprotocol or version:
* The connection will be rejected
* You'll see a **400 Bad Request** response
* Check the response details to understand why the server rejected the connection
**Advanced Header Overrides**
Bruno allows you to override other `Sec-WebSocket-*` headers if needed for testing specific scenarios:
* `Sec-WebSocket-Key`: Custom key for handshake (auto-generated by default)
* `Sec-WebSocket-Extensions`: Specify compression or other extensions
Simply add these headers in the Headers tab like any other custom header.
# WebSocket Message Types
Source: https://docs.usebruno.com/send-requests/websocket/message-types
Bruno supports various message types for WebSocket communication, allowing you to send and receive different data formats based on your application's requirements.
## Supported Message Types
Bruno supports the following WebSocket message types:
### Text Messages
Plain text messages for simple communication and human-readable data exchange.
**Characteristics:**
* **Format**: UTF-8 encoded text
* **Use Case**: Simple messages, commands, status updates
* **Size**: Limited by WebSocket frame size (typically 64KB)
* **Encoding**: UTF-8
**Example Text Messages:**
```
Hello, WebSocket server!
PING
GET_STATUS
USER_JOINED:john_doe
```
**When to Use:**
* Simple commands or instructions
* Status messages and notifications
* Human-readable communication
* Protocol-level messages (PING/PONG)
### JSON Messages
Structured data in JSON format for complex data exchange and API-like communication.
**Characteristics:**
* **Format**: JSON (JavaScript Object Notation)
* **Use Case**: Structured data, API responses, complex objects
* **Validation**: JSON syntax validation and formatting
* **Size**: Limited by WebSocket frame size
**Example JSON Messages:**
```json theme={null}
{
"type": "message",
"content": "Hello from Bruno!",
"timestamp": "2025-01-09T10:30:00Z",
"user": {
"id": 123,
"name": "Bruno User"
}
}
```
**Chat Application Example:**
```json theme={null}
{
"type": "chat_message",
"room": "general",
"user": "john_doe",
"message": "Hello everyone!",
"timestamp": 1704795000000
}
```
**API Response Example:**
```json theme={null}
{
"status": "success",
"data": {
"user_id": 123,
"balance": 1000.50,
"currency": "USD"
},
"message": "Balance retrieved successfully"
}
```
**When to Use:**
* Structured data exchange
* API-like communication
* Complex objects and arrays
* When you need data validation
### XML Messages
Structured data in XML format for legacy systems and XML-based protocols.
**Characteristics:**
* **Format**: XML (eXtensible Markup Language)
* **Use Case**: SOAP messages, legacy systems, XML-based APIs
* **Validation**: XML syntax validation and formatting
* **Size**: Limited by WebSocket frame size
**Example XML Messages:**
```xml theme={null}
notificationHello from Bruno!2025-01-09T10:30:00Z123Bruno User
```
**SOAP Message Example:**
```xml theme={null}
123
```
**When to Use:**
* Legacy systems requiring XML format
* SOAP-based WebSocket services
* XML-based messaging protocols
* When working with systems that expect XML
# Overview
Source: https://docs.usebruno.com/send-requests/websocket/overview
WebSocket support is available from Bruno 2.13.0.
Bruno now supports WebSocket connections, enabling you to test real-time communication protocols and bidirectional data exchange. This feature allows you to establish WebSocket connections, send messages, and receive responses in real-time.
## What are WebSockets?
WebSockets provide a persistent, full-duplex communication channel between a client and server. Unlike traditional HTTP requests that follow a request-response pattern, WebSockets maintain an open connection that allows both the client and server to send messages at any time.
## WebSocket vs HTTP
| Feature | HTTP | WebSocket |
| -------------- | ---------------------------- | ----------------------------- |
| **Connection** | Request-Response | Persistent |
| **Latency** | Higher (connection overhead) | Lower (persistent connection) |
| **Data Flow** | Unidirectional | Bidirectional |
| **Use Case** | Traditional APIs | Real-time applications |
| **Overhead** | Higher per request | Lower per message |
| **Complexity** | Simple | More complex |
## Next Steps
* WebSocket [Request Interface](./ws-interface)
* Learn how to [Create WebSocket Requests](./create-request)
# WebSocket Request Interface
Source: https://docs.usebruno.com/send-requests/websocket/ws-interface
The WebSocket request interface consists of several key components:
## Request Interface
### Connection Status
* **Disconnected**: Initial state, ready to connect
* **Connecting**: Establishing connection to server
* **Connected**: Successfully connected and ready to send messages
* **Disconnected**: Connection lost or manually closed
### Message Composition
* **Message Type**: Choose between Text, JSON, XML
* **Message Editor**: Compose your message with syntax highlighting
* **Send Button**: Send the composed message
### Message History
* **Sent Messages**: All messages you've sent (marked with →)
* **Received Messages**: All messages received from server (marked with ←)
* **Timestamps**: When each message was sent/received
* **Message Sorting**: Toggle between newest/oldest first
## Connection Management
### Establishing Connection
1. **Click the "Connect" button** to establish the WebSocket connection
2. **Monitor connection status** in the status indicator
3. **View connection logs** for detailed connection information
### Sending Messages
1. **Compose your message** in the message editor
2. **Select message type** (Text, JSON, XML)
3. **Click "Send"** to transmit the message
4. **View sent message** in the message history
### Receiving Messages
1. **Monitor the message history** for incoming messages
2. **View message details** including timestamp and content
3. **Use test scripts** to validate received messages
### Closing Connection
1. **Click "Disconnect"** to close the WebSocket connection
2. **Connection status** will update to "Disconnected"
3. **Message history** is preserved for review
# Running Automated Tests
Source: https://docs.usebruno.com/testing/automate-test/automate-test
The [Bruno CLI](/bru-cli/overview) is a command line utility that allows you to call tests from the terminal, or **integrate your test collections into a CI/CD pipeline**.
Continuous integration and continuous delivery (CI/CD) empower teams to ship software updates rapidly and reliably, maximizing user value without compromising quality. By integrating automated API tests into the build pipeline, you verify that every code change is validated and production-ready.
For more information on the Bruno CLI, go to that [specific section of the documentation](/bru-cli/overview).
# Iterate Using Data Files
Source: https://docs.usebruno.com/testing/automate-test/data-driven-testing
The **Collection Runner** feature allows you to iterate over data files, making it easy to automate and manage data-driven requests. Bruno supports both CSV and JSON files for running requests, so you can efficiently run tests or process multiple data inputs.
## How request bodies and data files work together
Understanding this avoids the most common confusion with data-driven runs:
1. **The data file does not replace your request body.**\
Uploading CSV/JSON does not merge row objects into the body or overwrite a hardcoded JSON payload. Row values are available as **iteration variables** for each run.
2. **To use row values in the body, reference them explicitly** with `{{columnName}}` (same names as CSV headers or JSON object keys). Example: `"name": "{{name}}"` uses the `name` field from the current row.
3. **If the body has no `{{...}}` placeholders** for your data columns, Bruno sends **exactly the body you typed on every iteration**. The file still controls **how many iterations** run, but each request uses the same static payload. In the runner, the **Data** column shows the **current iteration’s row** (iteration data), not “the body Bruno built from the file.”
4. **In scripts**, read the current row with `bru.runner.iterationData.get("columnName")` (or build the body there with `req.setBody(...)`). See [Runner iteration utilities](#runner-iteration-utilities) below.
A plain body like `{"name": "morpheus", "job": "leader"}` is fine for a **single** request or when you want **the same body every time** while the runner still steps through rows (e.g. for side effects or tests only). To send **different `name` / `job` per row**, the body must use `{{name}}` and `{{job}}` (or set the body from a script).
## Introduction
In this tutorial, we'll use the Collection Runner with a CSV or JSON file so each iteration sends **different** `name` and `job` values to `https://reqres.in/api/users`.
## Steps to Get Started
1. Open the Bruno app.
2. Create a collection called `runner-example`.
3. Create a POST request and name it `runner-request`.
4. Use the URL: `https://reqres.in/api/users`.
5. Set the request body to **JSON** and use **placeholders** that match your data file columns (`name` and `job`):
```json theme={null}
{
"name": "{{name}}",
"job": "{{job}}"
}
```
For a one-off request **without** a data file, you could use fixed values instead (e.g. `morpheus` / `leader`); that is unrelated to substituting CSV/JSON rows.
## Using the Collection Runner
We will create a sample data file `csv-example.csv` that includes input fields such as `name` and `job` to be used in data-driven testing. You need to create a CSV or JSON file according to the specific requirements of the API you're working with.
Since the API in this case expects two data inputs `name` and `job` the file should contain these fields. Here's an example of how to structure your data:
### 1. CSV Format Example
A sample CSV file might look like this:
```csv copy theme={null}
name,job
John Doe,Software Engineer
Jane Smith,Product Manager
Mark Lee,Data Scientist
```
### 2. JSON Format Example
A sample JSON file might look like this:
```json copy theme={null}
[
{ "name": "John Doe", "job": "Software Engineer" },
{ "name": "Jane Smith", "job": "Product Manager" },
{ "name": "Mark Lee", "job": "Data Scientist" }
]
```
Now you're ready to use the Collection Runner. You can access it in two ways:
### Using the Bruno App
1. Click on the runner icon in the right-hand navbar.
2. Select the file type: CSV or JSON and upload your data file.
3. Click **Run Collection** to start the iterations.
Once the execution is complete, you can review the results for each individual request and check their statuses.
#### How to Use Variables from CSV/JSON Files
When you upload a CSV or JSON file, each **column** (CSV) or **object key** (JSON) becomes an iteration variable. Use the `{{variableName}}` syntax in the URL, headers, or body so each iteration sends that row’s values.
##### Accessing Variables in Request Body
Template the body with names that match your file:
```json showLineNumbers filename="request-body" theme={null}
{
"name": "{{name}}",
"email": "{{email}}"
}
```
For a given iteration, Bruno resolves those placeholders from the current row—for example:
```json showLineNumbers filename="resolved-example-one-iteration" theme={null}
{
"name": "John Doe",
"email": "john.doe@example.com"
}
```
##### Accessing Variables in Scripts
In pre-request or post-response scripts, read the current iteration with **`bru.runner.iterationData.get("columnName")`**. Depending on your setup, **`bru.getVar()`** may also reflect runner variables.
```javascript theme={null}
console.log(bru.runner.iterationData.get("name"));
console.log(bru.getVar("name")); // May match iteration data when exposed by the runner
```
When you run the collection, check the **Timeline** for the **actual request body** sent on each iteration.
### Using the Bruno CLI
1. Navigate to the root directory of your Bruno collection.
2. Run the following command:
```bash copy theme={null}
bru run --reporter-html results.html --csv-file-path /path/to/csv/file.csv
```
It will create a `results.html` file in your Bruno collection's root directory. You can view this file in your browser.
For how to open and use test reports in the Bruno app (including downloaded HTML reports), see **[View test report](./manual-test#view-test-report)**.
### Command Overview
* `--reporter-html results.html`: Generates a human-readable HTML report.
* `--csv-file-path /path/to/csv/file.csv`: Specifies the path to the CSV file you want to use.
[Bruno CLI Overview](../../bru-cli/overview)
## Runner Iteration Utilities
Bruno provides various utility functions to access and manipulate data from attached data files (CSV/JSON) during collection runs.
### Accessing Iteration Data
#### Check if a variable exists
```javascript theme={null}
if (bru.runner.iterationData.has("username")) {
console.log("Username exists in current iteration");
}
```
#### Get a specific value
```javascript theme={null}
const username = bru.runner.iterationData.get("username");
console.log(`Current username: ${username}`);
```
#### Get all iteration data
```javascript theme={null}
const allData = bru.runner.iterationData.get();
console.log("All iteration data:", allData);
```
#### Convert to JSON string
```javascript theme={null}
const jsonData = bru.runner.iterationData.stringify();
console.log("JSON data:", jsonData);
```
#### Remove a variable
```javascript theme={null}
bru.runner.iterationData.unset("password");
```
### Iteration Information
#### Get current iteration index
```javascript theme={null}
const currentIteration = bru.runner.iterationIndex;
console.log(`Running iteration ${currentIteration}`);
```
#### Get total iterations
```javascript theme={null}
const total = bru.runner.totalIterations;
console.log(`Total iterations: ${total}`);
```
### Example Usage
#### Basic Data Access
```javascript theme={null}
// Get values from current iteration
const username = bru.runner.iterationData.get("username");
const password = bru.runner.iterationData.get("password");
// Use in request (pre-request script only)
req.setBody({
username: username,
password: password
});
```
#### Conditional Logic Based on Iteration
```javascript theme={null}
// Only modify data in first iteration
if (bru.runner.iterationIndex === 0) {
bru.runner.iterationData.unset("password");
}
// Check if variable exists
if (bru.runner.iterationData.has("password")) {
console.log("Password available");
}
```
### Data File Examples
#### CSV Format
```csv theme={null}
username,password
user1,pass123
user2,pass456
```
#### JSON Format
```json theme={null}
[
{ "username": "user1", "password": "pass123" },
{ "username": "user2", "password": "pass456" }
]
```
### Notes
* Variables removed with `unset()` only affect the current iteration.
* Each iteration runs with fresh data from the file.
* Supports both CSV and JSON data files.
* Data is automatically loaded from the attached file at the start of each iteration.
* **Iteration count** comes from the number of rows (CSV) or objects (JSON) in the file.
* **The request body is not ignored:** if you use no `{{}}` references, the same static body is sent every time; the data file still drives how many times the request runs and what appears under **Data** for each iteration.
# Running Tests Manually
Source: https://docs.usebruno.com/testing/automate-test/manual-test
Running tests manually in Bruno allows you to quickly validate functionality and immediately view results. The most common example of this would be simply hitting an endpoint and seeing if it is working.
### Running a Single Test
The smallest unit of testing in Bruno is a single request. The request itself tests the functionality of any given endpoint. You can add additional logic to requests by using the `Tests` tab.
To manually trigger a single test:
1. Open a Collection
2. Select the Request that you would like to run
3. Click Enter or press the send icon
4. View the results in the `Tests` section of the response pane
### Running a Test Suite
Often times a Bruno Collection can represent a test suite of different actions or workflows of an API. You can use the Collection Runner to execute this entire suite, and also iterate through data using [data driven testing](/testing/automate-test/data-driven-testing).
Follow these steps to run your collection, view the results, and optionally download the test report as an HTML document.
1. Open the **Bruno** collection
2. Click on the **Run** option from the dropdown menu.
3. Click **Run Collection** to initiate the test.
4. Once the test run is complete, a test report will be automatically generated.
### Download Test Report
After the test report is generated, you can download it as an HTML document (`report.html`).
1. Click the **Download Report** button to save the report.
## Managing Request Execution
Bruno allows you to filter which requests to run in your collection using tags. You can add tags to your requests and then filter them during collection runs.
### Tag-Based Request Filtering
When running a collection in the Bruno app, you can filter requests by tags:
1. **Add tags to your requests**: Go to request settings and add tags to categorize your requests
2. **Go to Collection Runner**: Open the collection runner to start filtering requests
3. **Use tag filter options**: Use the tag filter options to include or exclude requests based on their tags
4. **Run filtered collection**: Only requests matching your tag criteria will be executed
#### Tag Filtering Options
* **Include tags**: Only run requests that have ALL of the specified tags
* **Exclude tags**: Skip requests that have ANY of the specified tags
* **Combined filtering**: Use both include and exclude filters together
Learn more about tag filtering in Bruno CLI in the [CLI tag filtering documentation](/bru-cli/runCollection#filtering-requests-with-tags).
### Reordering Request Execution
You can reorder, select, and deselect requests by enabling the **Configure requests to run** option in the Collection Runner. This feature allows you to:
* **Select specific requests** to execute while ignoring others
* **Reorder requests** to control the execution sequence
* **Customize test runs** without modifying the collection structure
By default, the Collection Runner executes all requests in the collection. When you enable request configuration, only the selected requests will be executed.
## View Test Report
Once the report is downloaded, you can open the `report.html` file in any web browser to review the detailed test results.
# Overview
Source: https://docs.usebruno.com/testing/automate-test/overview
Bruno offers multiple ways to run your API tests. Testing is crucial for APIs, as a single failure can bring down the entire application. With Bruno, you can automate API tests using the [Collection Runner ↗](https://docs.usebruno.com/get-started/bruno-basics/run-a-collection) or the [Bruno CLI ↗](https://docs.usebruno.com/bru-cli/overview) to integrate testing into your CI/CD pipeline.
### Automated Testing in CI/CD
Bruno offers a command-line utility to run Collections. This allows you to integrate your tests into a CI and build process.
For more information, head to the page for [Bruno CLI ↗](https://docs.usebruno.com/bru-cli/overview)
1. [Run Manual Test](./manual-test)
2. [Run Automate Test](./automate-test)
# Dynamic Variables
Source: https://docs.usebruno.com/testing/script/dynamic-variables
Dynamic variables enable you to generate and use random data in your testing workflow. Bruno uses the [faker.js ↗](https://fakerjs.dev/) library to generate random data. You can use these variables like any other variables in Bruno. The syntax to use dynamic variables is `{{$randomData}}`, and you can use them in the request body, authentication, parameters, and other fields.
Dynamic variables are case-sensitive and follow the camelCase convention. This feature is available in Bruno version 2.x and above.
## Using Dynamic Variables in Scripts
To use dynamic variables in pre-request or post-response scripts, use the `bru.interpolate()` function. For example:
```javascript theme={null}
// Generate a random first name
const firstName = bru.interpolate('{{$randomFirstName}}');
console.log('Random first name:', firstName);
// Generate a random email
const email = bru.interpolate('{{$randomEmail}}');
console.log('Random email:', email);
// Generate multiple dynamic values in one call
const userInfo = bru.interpolate(`
Name: {{$randomFullName}}
Job: {{$randomJobTitle}}
Email: {{$randomEmail}}
`);
console.log(userInfo);
// Use with environment variables
const greeting = bru.interpolate('Hello {{user}} from {{$randomCity}}');
```
The `bru.interpolate()` function allows you to embed dynamic variables in strings and have them evaluated at runtime within scripts, just like they would be in request bodies or headers.
## Usage Examples
Here are some common use cases for dynamic variables:
1. **Test Data Generation**:
```json theme={null}
{
"user": {
"id": "{{$randomUUID}}",
"name": "{{$randomFullName}}",
"email": "{{$randomEmail}}",
"phone": "{{$randomPhoneNumber}}",
"address": {
"street": "{{$randomStreetAddress}}",
"city": "{{$randomCity}}",
"country": "{{$randomCountry}}"
}
}
}
```
2. **Authentication**:
```json theme={null}
{
"credentials": {
"username": "{{$randomUserName}}",
"password": "{{$randomPassword}}",
"lastLogin": "{{$isoTimestamp}}"
}
}
```
3. **Business Data**:
```json theme={null}
{
"company": {
"name": "{{$randomCompanyName}}",
"catchPhrase": "{{$randomCatchPhrase}}",
"department": "{{$randomDepartment}}",
"account": {
"number": "{{$randomBankAccount}}",
"type": "{{$randomTransactionType}}"
}
}
}
```
## Available Dynamic Variables
Bruno provides a comprehensive set of dynamic variables for generating test data. These variables are organized into categories based on their functionality.
### Basic Data Types
| Variable Name | Description | Examples |
| ------------------------- | ----------------------------------- | ------------------------------------ |
| `{{$guid}}` | A random UUID | 550e8400-e29b-41d4-a716-446655440000 |
| `{{$timestamp}}` | Current timestamp in seconds | 1562757107 |
| `{{$isoTimestamp}}` | Current timestamp in ISO format | 2024-03-20T12:34:56.789Z |
| `{{$randomUUID}}` | A random UUID | 550e8400-e29b-41d4-a716-446655440000 |
| `{{$randomNanoId}}` | A random Nano ID | V1StGXR8\_Z5jdHi6B5cEn4c8b8w |
| `{{$randomAlphaNumeric}}` | A random alpha-numeric character | 6, y, z |
| `{{$randomBoolean}}` | A random boolean value | true, false |
| `{{$randomInt}}` | A random integer between 0 and 1000 | 802, 494, 200 |
| `{{$randomColor}}` | A random color | red, fuchsia, grey |
| `{{$randomHexColor}}` | A random hex value | #47594a, #431e48, #106f21 |
| `{{$randomAbbreviation}}` | A random abbreviation | SQL, PCI, JSON |
| `{{$randomWord}}` | A random word | protocol, interface |
| `{{$randomWords}}` | Multiple random words | quick brown fox |
### Internet and Network
| Variable Name | Description | Examples |
| ------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `{{$randomIP}}` | A random IPv4 address | 241.102.234.100, 216.7.27.38 |
| `{{$randomIPV4}}` | A random IPv4 address | 192.168.1.1 |
| `{{$randomIPV6}}` | A random IPv6 address | dbe2:7ae6:119b:c161:1560:6dda:3a9b:90a9 |
| `{{$randomMACAddress}}` | A random MAC address | 33:d4:68:5f:b4:c7, 1f:6e:db:3d:ed:fa |
| `{{$randomPassword}}` | A random 15-character alpha-numeric password | t9iXe7COoDKv8k3, QAzNFQtvR9cg2rq |
| `{{$randomLocale}}` | A random two-letter language code (ISO 639-1) | ny, sr, si |
| `{{$randomUserAgent}}` | A random user agent | Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.9.8; rv:15.6) Gecko/20100101 Firefox/15.6.6 |
| `{{$randomProtocol}}` | A random internet protocol | http, https |
| `{{$randomSemver}}` | A random semantic version number | 7.0.5, 2.5.8, 6.4.9 |
| `{{$randomDomainName}}` | A random domain name | example.com |
| `{{$randomDomainSuffix}}` | A random domain suffix | .com, .org, .net |
| `{{$randomDomainWord}}` | A random domain word | example, test, demo |
| `{{$randomExampleEmail}}` | A random example email | [user@example.com](mailto:user@example.com) |
| `{{$randomEmail}}` | A random email address | [john.doe@example.com](mailto:john.doe@example.com) |
| `{{$randomUserName}}` | A random username | johndoe123 |
| `{{$randomUrl}}` | A random URL | [https://example.com](https://example.com) |
### Names and Personal Information
| Variable Name | Description | Examples |
| --------------------------- | ------------------------------------ | ------------------------------------------------ |
| `{{$randomFirstName}}` | A random first name | Ethan, Chandler, Megane |
| `{{$randomLastName}}` | A random last name | Schaden, Schneider, Willms |
| `{{$randomFullName}}` | A random first and last name | Connie Runolfsdottir, Sylvan Fay, Jonathon Kunze |
| `{{$randomNamePrefix}}` | A random name prefix | Dr., Ms., Mr. |
| `{{$randomNameSuffix}}` | A random name suffix | I, MD, DD |
| `{{$randomJobArea}}` | A random job area | Marketing, Engineering, Sales |
| `{{$randomJobDescriptor}}` | A random job descriptor | Senior, Lead, Junior |
| `{{$randomJobTitle}}` | A random job title | Software Engineer, Product Manager |
| `{{$randomJobType}}` | A random job type | Full-time, Contract, Part-time |
| `{{$randomPhoneNumber}}` | A random phone number | +1-555-123-4567 |
| `{{$randomPhoneNumberExt}}` | A random phone number with extension | +1-555-123-4567 ext. 123 |
### Location
| Variable Name | Description | Examples |
| -------------------------- | ----------------------- | ----------------------------- |
| `{{$randomCity}}` | A random city name | New York, London, Tokyo |
| `{{$randomStreetName}}` | A random street name | Main Street, Park Avenue |
| `{{$randomStreetAddress}}` | A random street address | 123 Main St, 456 Park Ave |
| `{{$randomCountry}}` | A random country name | United States, Japan, Germany |
| `{{$randomCountryCode}}` | A random country code | US, JP, DE |
| `{{$randomLatitude}}` | A random latitude | 40.7128, 35.6762 |
| `{{$randomLongitude}}` | A random longitude | -74.0060, 139.6503 |
### Images
| Variable Name | Description | Examples |
| --------------------------- | ------------------------- | -------------------------------------------------------------------------------------- |
| `{{$randomAvatarImage}}` | A random avatar image URL | [https://i.pravatar.cc/300](https://i.pravatar.cc/300) |
| `{{$randomImageUrl}}` | A random image URL | [https://picsum.photos/200/300](https://picsum.photos/200/300) |
| `{{$randomAbstractImage}}` | A random abstract image | [https://loremflickr.com/320/240/abstract](https://loremflickr.com/320/240/abstract) |
| `{{$randomAnimalsImage}}` | A random animal image | [https://loremflickr.com/320/240/animals](https://loremflickr.com/320/240/animals) |
| `{{$randomBusinessImage}}` | A random business image | [https://loremflickr.com/320/240/business](https://loremflickr.com/320/240/business) |
| `{{$randomCatsImage}}` | A random cat image | [https://loremflickr.com/320/240/cats](https://loremflickr.com/320/240/cats) |
| `{{$randomCityImage}}` | A random city image | [https://loremflickr.com/320/240/city](https://loremflickr.com/320/240/city) |
| `{{$randomFoodImage}}` | A random food image | [https://loremflickr.com/320/240/food](https://loremflickr.com/320/240/food) |
| `{{$randomNightlifeImage}}` | A random nightlife image | [https://loremflickr.com/320/240/nightlife](https://loremflickr.com/320/240/nightlife) |
| `{{$randomFashionImage}}` | A random fashion image | [https://loremflickr.com/320/240/fashion](https://loremflickr.com/320/240/fashion) |
| `{{$randomPeopleImage}}` | A random people image | [https://loremflickr.com/320/240/people](https://loremflickr.com/320/240/people) |
| `{{$randomNatureImage}}` | A random nature image | [https://loremflickr.com/320/240/nature](https://loremflickr.com/320/240/nature) |
| `{{$randomSportsImage}}` | A random sports image | [https://loremflickr.com/320/240/sports](https://loremflickr.com/320/240/sports) |
| `{{$randomTransportImage}}` | A random transport image | [https://loremflickr.com/320/240/transport](https://loremflickr.com/320/240/transport) |
| `{{$randomImageDataUri}}` | A random image data URI | data:image/png;base64,... |
### Finance
| Variable Name | Description | Examples |
| ---------------------------- | ---------------------------- | ---------------------------------- |
| `{{$randomBankAccount}}` | A random bank account number | 1234567890 |
| `{{$randomBankAccountName}}` | A random bank account name | Checking Account |
| `{{$randomCreditCardMask}}` | A masked credit card number | \*\*\*\* \*\*\*\* \*\*\*\* 1234 |
| `{{$randomBankAccountBic}}` | A random BIC code | DEUTDEFF |
| `{{$randomBankAccountIban}}` | A random IBAN | DE89370400440532013000 |
| `{{$randomTransactionType}}` | A random transaction type | deposit, withdrawal |
| `{{$randomCurrencyCode}}` | A random currency code | USD, EUR, JPY |
| `{{$randomCurrencyName}}` | A random currency name | US Dollar, Euro, Yen |
| `{{$randomCurrencySymbol}}` | A random currency symbol | \$, €, ¥ |
| `{{$randomBitcoin}}` | A random Bitcoin address | 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa |
### Business
| Variable Name | Description | Examples |
| ---------------------------------- | -------------------------------- | -------------------------------- |
| `{{$randomCompanyName}}` | A random company name | TechCorp Inc. |
| `{{$randomCompanySuffix}}` | A random company suffix | LLC, Inc., Ltd. |
| `{{$randomBs}}` | A random business buzz phrase | revolutionize end-to-end systems |
| `{{$randomBsAdjective}}` | A random business adjective | innovative, scalable |
| `{{$randomBsBuzz}}` | A random business buzz word | synergize, leverage |
| `{{$randomBsNoun}}` | A random business noun | solutions, platform |
| `{{$randomCatchPhrase}}` | A random company catch phrase | Innovate. Create. Transform. |
| `{{$randomCatchPhraseAdjective}}` | A random catch phrase adjective | innovative, revolutionary |
| `{{$randomCatchPhraseDescriptor}}` | A random catch phrase descriptor | next-generation, cutting-edge |
| `{{$randomCatchPhraseNoun}}` | A random catch phrase noun | solutions, platform |
### Database
| Variable Name | Description | Examples |
| ------------------------------ | ----------------------------- | -------------------------------------- |
| `{{$randomDatabaseColumn}}` | A random database column name | id, name, created\_at |
| `{{$randomDatabaseType}}` | A random database type | varchar, int, timestamp |
| `{{$randomDatabaseCollation}}` | A random database collation | utf8\_general\_ci, latin1\_swedish\_ci |
| `{{$randomDatabaseEngine}}` | A random database engine | InnoDB, MyISAM |
### Dates
| Variable Name | Description | Examples |
| ----------------------- | -------------------- | ------------------------ |
| `{{$randomDateFuture}}` | A random future date | 2024-12-31T23:59:59.999Z |
| `{{$randomDatePast}}` | A random past date | 2020-01-01T00:00:00.000Z |
| `{{$randomDateRecent}}` | A random recent date | 2024-03-15T12:34:56.789Z |
| `{{$randomWeekday}}` | A random weekday | Monday, Tuesday |
| `{{$randomMonth}}` | A random month | January, February |
### Files and System
| Variable Name | Description | Examples |
| --------------------------- | ------------------------------ | ----------------- |
| `{{$randomFileName}}` | A random file name | document.pdf |
| `{{$randomFileType}}` | A random file type | application/pdf |
| `{{$randomFileExt}}` | A random file extension | .pdf, .jpg |
| `{{$randomCommonFileName}}` | A random common file name | document.pdf |
| `{{$randomCommonFileType}}` | A random common file type | application/pdf |
| `{{$randomCommonFileExt}}` | A random common file extension | .pdf, .jpg |
| `{{$randomFilePath}}` | A random file path | /path/to/file.txt |
| `{{$randomDirectoryPath}}` | A random directory path | /usr/local/bin |
| `{{$randomMimeType}}` | A random MIME type | application/json |
### Commerce
| Variable Name | Description | Examples |
| ----------------------------- | -------------------------- | ---------------------- |
| `{{$randomPrice}}` | A random price | 99.99, 149.95 |
| `{{$randomProduct}}` | A random product | Chair, Table |
| `{{$randomProductAdjective}}` | A random product adjective | Ergonomic, Modern |
| `{{$randomProductMaterial}}` | A random product material | Wood, Metal |
| `{{$randomProductName}}` | A random product name | Ergonomic Wooden Chair |
| `{{$randomDepartment}}` | A random department name | Electronics, Clothing |
### Hacker and Lorem
| Variable Name | Description | Examples |
| ---------------------------- | ------------------------------- | --------------------------------------------------------------- |
| `{{$randomNoun}}` | A random noun | protocol, interface |
| `{{$randomVerb}}` | A random verb | hack, override |
| `{{$randomIngverb}}` | A random -ing verb | hacking, overriding |
| `{{$randomAdjective}}` | A random adjective | digital, virtual |
| `{{$randomPhrase}}` | A random phrase | Try to reboot the SQL bus |
| `{{$randomLoremWord}}` | A random Lorem Ipsum word | lorem, ipsum |
| `{{$randomLoremWords}}` | Multiple Lorem Ipsum words | lorem ipsum dolor |
| `{{$randomLoremSentence}}` | A random Lorem Ipsum sentence | Lorem ipsum dolor sit amet. |
| `{{$randomLoremSentences}}` | Multiple Lorem Ipsum sentences | Lorem ipsum dolor sit amet. Consectetur adipiscing elit. |
| `{{$randomLoremParagraph}}` | A random Lorem Ipsum paragraph | Lorem ipsum dolor sit amet... |
| `{{$randomLoremParagraphs}}` | Multiple Lorem Ipsum paragraphs | Lorem ipsum dolor sit amet...\n\nConsectetur adipiscing elit... |
| `{{$randomLoremText}}` | A random Lorem Ipsum text | Lorem ipsum dolor sit amet... |
| `{{$randomLoremSlug}}` | A random Lorem Ipsum slug | lorem-ipsum-dolor |
| `{{$randomLoremLines}}` | Multiple Lorem Ipsum lines | Lorem ipsum dolor\nConsectetur adipiscing elit |
# External Libraries
Source: https://docs.usebruno.com/testing/script/external-libraries
Bruno allows you to load any npm module or package for use in your scripting workflows.
Always turn on [Developer Mode](/get-started/configure/javascript-sandbox) when using external libraries in Bruno.
## Prerequisites
Before you begin, ensure that you have the following:
1. Node.js installed on your system.
2. Bruno installed.
## Project structure
The folder structure depends on your collection format.
Starting with Bruno v3.1.0, YAML (OpenCollection format) is the default file format when creating new collections. Learn more about create collection [here](/get-started/bruno-basics/create-a-collection).
## Configuration
You need a `package.json` file at the root of your Bruno collection.
1. Open your collection folder in **Bruno** or in an editor such as **VS Code**.
2. Open the integrated terminal (in Bruno or your editor) and go to the collection root:
```bash theme={null}
cd path/to/your/bruno-collection
```
3. Initialize `package.json`:
```bash theme={null}
npm init -y
```
4. Install the package (run this in the same terminal, from the collection root):
```bash theme={null}
npm install csv-parse
```
5. Confirm **`csv-parse`** appears under `dependencies` in `package.json`, for example:
```json theme={null}
"dependencies": {
"csv-parse": "^6.2.1"
}
```
Exact versions may differ, but what matters is that the dependency is listed after a successful install.
6. In Bruno, enable **[Developer Mode](/get-started/configure/javascript-sandbox)** for the collection so `require()` can load packages from `node_modules`.
## Example: Parse CSV with csv-parse
This example uses [csv-parse ↗](https://www.npmjs.com/package/csv-parse) to turn CSV text into JavaScript objects, then sends the first row as JSON on the request. See the [npm package page ↗](https://www.npmjs.com/package/csv-parse) for options, streaming APIs, and more.
Use a **Pre Request** script with **Developer Mode** on:
```javascript theme={null}
const { parse } = require('csv-parse/sync');
const csvData = `name,email
Smith,smith@example.com
John,john@example.com`;
const records = parse(csvData, {
columns: true,
skip_empty_lines: true
});
req.setHeader('Content-Type', 'application/json');
req.setBody(records[0]);
```
`records[0]` is the first data row as an object (for example `{ name: 'Smith', email: 'smith@example.com' }`) because `columns: true` uses the first line as headers.
### Reading CSV from a file
You can also pass CSV from a file in your collection (path is relative to the collection root):
```javascript theme={null}
const fs = require('fs');
const path = require('path');
const { parse } = require('csv-parse/sync');
const csvData = fs.readFileSync(path.join(__dirname, 'data.csv'), 'utf8');
const records = parse(csvData, {
columns: true,
skip_empty_lines: true
});
req.setHeader('Content-Type', 'application/json');
req.setBody(records[0]);
```
Adjust the path to match where you store your `.csv` file.
# Scripting
Source: https://docs.usebruno.com/testing/script/getting-started
Bruno allows you to write JavaScript code before sending a request (Pre-request) and after receiving a response (Post-response). Use scripts to generate dynamic data, manipulate requests, parse responses, and set variables.
* [Request Methods](/testing/script/javascript-reference#request)
* [Response Methods](/testing/script/javascript-reference#response)
* [Bruno Utilities](/testing/script/javascript-reference/#environments)
# Inbuilt Libraries
Source: https://docs.usebruno.com/testing/script/inbuilt-libraries
Bruno supports **CommonJS** syntax `require` for importing libraries. ES Modules `import/export` are not supported at this time.
Here's an example demonstrating how to use the **chai** library in Bruno:
```js copy theme={null}
const chai = require('chai');
const { expect } = chai;
function add(a, b) {
return a + b;
}
const result = add(2, 3);
expect(result).to.equal(5);
console.log('Test passed!');
```
You can include this code in the pre-request script section of Bruno to test it out.
## jsonwebtoken
[jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) lets you create signed JWTs with `jwt.sign`. In a **Pre Request** script you might sign only a sensitive field (here the `token` string), then put that JWT back on your payload and send it as JSON.
```js copy theme={null}
const jwt = require('jsonwebtoken');
const payload = {
name: 'usebruno',
id: 100,
token: 'abc1234'
};
const secret = 'your-secret';
// Sign only the token value (claims embedded in the JWT)
const signedToken = jwt.sign({ token: payload.token }, secret);
// Send the rest of the fields unchanged, with `token` replaced by the JWT
const updatedPayload = {
...payload,
token: signedToken
};
req.setHeader('Content-Type', 'application/json');
req.setBody(updatedPayload);
```
Use a real secret from your environment or collection variables in practice and never commit secrets in scripts.
Below is the list of inbuilt libraries supported by Bruno.
* [axios](https://www.npmjs.com/package/axios) - Promise based HTTP client for the browser and node.js
* [atob](https://www.npmjs.com/package/atob) - Turn base64-encoded ascii data back to binary.
* [btoa](https://www.npmjs.com/package/btoa) - Turn binary data to base64-encoded ascii.
* [chai](https://www.npmjs.com/package/chai) - BDD/TDD assertion library for node.js and the browser.
* [moment](https://momentjs.com) - Parse, validate, manipulate, and display dates and times in JavaScript.
* [uuid](https://www.npmjs.com/package/uuid) - For the creation of RFC4122 UUIDs.
* [nanoid](https://www.npmjs.com/package/nanoid) - A tiny, secure, URL-friendly, unique string ID generator for JavaScript.
* [crypto-js](https://www.npmjs.com/package/crypto-js) - JavaScript library of crypto standards.
* [tv4](https://www.npmjs.com/package/tv4) - Tiny Validator for JSON Schema v4.
* [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) - An implementation of JSON Web Tokens.
To use below inbuilt libraries in Bruno, you must be in **Developer Mode**. When using Bruno CLI (v3.0.0+), pass the `--sandbox=developer` flag.
* [node-fetch](https://www.npmjs.com/package/node-fetch) - A light-weight module that brings Fetch API to Node.js.
* [lodash](https://lodash.com) - A modern JavaScript utility library delivering modularity, performance & extras.
* [ajv](https://www.npmjs.com/package/ajv) - Ajv JSON schema validator.
* [cheerio](https://www.npmjs.com/package/cheerio) - Library for parsing and manipulating HTML and XML.
* [xml2js](https://www.npmjs.com/package/xml2js) - Simple XML to JavaScript object converter for Node.js.
# JavaScript API Reference
Source: https://docs.usebruno.com/testing/script/javascript-reference
Bruno offers powerful scripting capabilities that allow you to extend and automate your API testing workflows.
Here is the complete set of API reference for the scripting feature in Bruno.
## Request
The `req` variable represents the HTTP request object and is automatically available inside your scripting and testing context. It provides methods to access and modify the current request's properties such as URL, method, headers, body, and other configuration options before the request is sent to the server.
The `req` object is available in pre-request scripts and test scripts, allowing you to modify request properties before execution and access them after completion.
Here is a complete table for all available methods on the `req` object.
| Method | Description |
| ---------------------------- | -------------------------------------------------------------------------------------------------------- |
| req.getUrl() | Get the current request URL. |
| req.setUrl(url) | Set the current request URL. |
| req.getHost() | Get the hostname from the request URL. |
| req.getPath() | Get the path from the request URL. |
| req.getQueryString() | Get the raw query string from the request URL. |
| req.getPathParams() | Extract path parameters using the path template. |
| req.getAuthMode() | Get the current authentication mode. |
| req.getMethod() | Get the current request method. |
| req.setMethod(method) | Set the current request method. |
| req.getName() | Get the current request name. |
| req.getTags() | Get the current request tags as an array of strings. |
| req.getHeader(name) | Get the request header by name. |
| req.getHeaders() | Get all request headers. |
| req.setHeader(name, value) | Set a request header by name. |
| req.setHeaders(headers) | Set multiple request headers. |
| req.deleteHeader(name) | Remove a request header by name. |
| req.deleteHeaders(\[names]) | Remove multiple request headers by name. |
| req.headerList | PropertyList interface — rich read, search, iteration, transform, and write methods for request headers. |
| req.getBody(options?) | Get the current request body/payload (supports raw option). |
| req.setBody(body) | Set the request body/payload. |
| req.setMaxRedirects(count) | Set the maximum number of redirects to follow. |
| req.getTimeout() | Get the current timeout value of the request. |
| req.setTimeout(milliseconds) | Set a timeout for the request. |
| req.getExecutionMode() | Get the current active execution mode (runner or standalone). |
| req.getExecutionPlatform() | Get the platform on which the request is being executed (app or cli). |
| req.onFail(callback) | Handle request errors with a custom callback function. |
Below is the API documentation for the methods available on `req`.
### `req.getUrl()`
Returns the full URL of the current request, including any query parameters. Useful for logging, conditional logic, or passing the URL to another variable.
```javascript theme={null}
const url = req.getUrl();
console.log("Request URL:", url);
// Output: "https://echo.usebruno.com"
```
### `req.setUrl(url)`
Overrides the request URL with a new one. Use this in pre-request scripts when you need to dynamically compute or switch the endpoint before the request is sent.
```javascript theme={null}
// Switch to a different environment endpoint dynamically
const env = bru.getEnvVar("environment");
if (env === "staging") {
req.setUrl("https://staging.api.example.com/users");
} else {
req.setUrl("https://api.github.com/search/repositories?q=vue");
}
```
### `req.getHost()`
Get the hostname from the request URL.
```javascript theme={null}
const host = req.getHost();
console.log("Host:", host);
// Example output: "api.example.com"
```
### `req.getPath()`
Get the path from the request URL.
```javascript theme={null}
const path = req.getPath();
console.log("Path:", path);
// Example output: "/api/v1/users/123"
```
### `req.getQueryString()`
Get the raw query string from the request URL.
```javascript theme={null}
const queryString = req.getQueryString();
console.log("Query String:", queryString);
// Example output: "page=1&limit=10&sort=asc"
```
### `req.getPathParams()`
Extract path parameters using the path template defined in the request.
```javascript theme={null}
// For a URL like: https://echo.usebruno.com/:hello
// With path: /:hello/usebruno
const pathParams = req.getPathParams();
console.log("Path Variables:", pathParams);
// Output: [0:{ "name": "hello", "value": "usebruno", "type": "path" }]
// Access specific path parameter
console.log("Value:", pathParams[0].value);
// Output: "usebruno"
```
### `req.getMethod()`
Returns the HTTP method of the current request (e.g. `"GET"`, `"POST"`, `"PUT"`, `"DELETE"`). Useful for logging or branching logic based on the request type.
```javascript theme={null}
const method = req.getMethod();
console.log("HTTP method:", method);
// Output: "POST"
```
### `req.setMethod(method)`
Overrides the HTTP method of the request. Useful when you need to conditionally switch a request's method in a pre-request script.
```javascript theme={null}
// Upgrade to PATCH if an update flag is set
const isPartialUpdate = bru.getVar("partialUpdate");
if (isPartialUpdate) {
req.setMethod("PATCH");
} else {
req.setMethod("POST");
}
```
### `req.getName()`
Returns the display name of the current request as defined in Bruno. Useful for logging or identifying which request is running in a shared script.
```javascript theme={null}
const name = req.getName();
console.log("Running request:", name);
// Output: "Create User"
```
### `req.getAuthMode()`
Returns a string representing the active authentication type for the request. Possible values: `"bearer"`, `"basic"`, `"oauth2"`, `"oauth1"`, `"awsv4"`, `"digest"`, `"wsse"`, or `"none"`.
```javascript theme={null}
const authMode = req.getAuthMode();
console.log("Auth mode:", authMode);
// Output: "bearer"
// Only inject a token header when no auth is already set
if (authMode === "none") {
req.setHeader("x-api-key", bru.getEnvVar("apiKey"));
}
```
### `req.getTags()`
Get the current request tags as an array of strings. Useful for conditional logic, filtering, or organizing requests by tag.
**Returns:** Array of strings representing the request tags.
```javascript theme={null}
const tags = req.getTags();
console.log("Request tags:", tags);
if (tags.includes("smoke-test")) {
console.log("This is a smoke test request");
}
if (tags.includes("skip-in-ci")) {
bru.runner.skipRequest();
}
if (tags.includes("integration-test")) {
console.log("Running integration test validations");
}
```
### `req.getHeader(name)`
Returns the value of a specific request header by name. Returns `undefined` if the header does not exist. Header names are case-insensitive.
```javascript theme={null}
const transactionId = req.getHeader("transaction-id");
console.log("Transaction ID:", transactionId);
// Output: "txn_abc123"
// Only set the header if it hasn't been added already
if (!req.getHeader("x-request-id")) {
req.setHeader("x-request-id", bru.interpolate("{{$guid}}"));
}
```
### `req.getHeaders()`
Returns an object containing all request headers as key-value pairs. Useful for logging or inspecting headers before the request is sent.
```javascript theme={null}
const headers = req.getHeaders();
console.log("All request headers:", headers);
// Output: { "content-type": "application/json", "authorization": "Bearer abc..." }
```
### `req.setHeader(name, value)`
Adds or updates a single request header. If the header already exists, its value is overwritten.
```javascript theme={null}
// Inject an auth token from an environment variable
req.setHeader("authorization", "Bearer " + bru.getEnvVar("access_token"));
// Add a tracing header
req.setHeader("x-trace-id", bru.interpolate("{{$guid}}"));
```
### `req.setHeaders(headers)`
Adds or updates multiple request headers at once by passing an object. Existing headers with matching names are overwritten; others are left unchanged.
```javascript theme={null}
req.setHeaders({
"content-type": "application/json",
"x-api-version": "2",
"transaction-id": bru.getVar("txnId"),
});
```
### `req.deleteHeader(name)`
Removes a single request header by name. Has no effect if the header does not exist.
```javascript theme={null}
// Remove the authorization header to make an unauthenticated request
req.deleteHeader("authorization");
// Remove an internal header not needed for third-party APIs
req.deleteHeader("x-internal-trace");
```
### `req.deleteHeaders(names)`
Removes multiple request headers in one call. Pass an array of header names to remove.
```javascript theme={null}
// Strip all auth and tracing headers before forwarding to an external service
req.deleteHeaders(["authorization", "x-internal-trace", "x-api-key"]);
```
### `req.headerList`
`req.headerList` exposes the full **PropertyList** interface for request headers, giving you richer querying, iteration, and bulk-mutation capabilities beyond the basic `getHeader` / `setHeader` methods. All key lookups are **case-insensitive** per the HTTP spec.
`req.headerList` is available in **pre-request** and **test** scripts. Changes made via write methods take effect before the request is sent.
#### Read methods
| Method | Returns | Description |
| -------------------------- | --------------------- | --------------------------------------------------------------- |
| `req.headerList.get(name)` | `string \| undefined` | Value of the first header matching the key. |
| `req.headerList.one(name)` | `object \| undefined` | Full header object `{ key, value }` for the matching key. |
| `req.headerList.all()` | `object[]` | Cloned array of all header objects `{ key, value, disabled? }`. |
| `req.headerList.count()` | `number` | Number of headers, including disabled ones. |
```javascript theme={null}
// Read the value of a single header
const contentType = req.headerList.get("content-type");
console.log("Content-Type:", contentType);
// Output: "application/json"
// Get the full header object (key + value pair)
const authHeader = req.headerList.one("authorization");
console.log("Auth header object:", authHeader);
// Output: { key: "authorization", value: "Bearer abc123" }
// List every header before the request is sent
const allHeaders = req.headerList.all();
console.log(`Sending ${req.headerList.count()} headers:`, allHeaders);
```
#### Search methods
| Method | Returns | Description |
| ------------------------------------- | --------------------- | ------------------------------------------------------------------------------ |
| `req.headerList.has(name)` | `boolean` | `true` if any header with that key exists. |
| `req.headerList.has(name, value)` | `boolean` | `true` if a header with that key **and** exact value exists. |
| `req.headerList.has(object)` | `boolean` | `true` if a header matching `object.key` exists. |
| `req.headerList.find(fn, context?)` | `object \| undefined` | First header for which the predicate returns truthy. |
| `req.headerList.filter(fn, context?)` | `object[]` | All headers for which the predicate returns truthy. |
| `req.headerList.indexOf(item)` | `number` | Index of a header by string key or `{ key, value }` object; `-1` if not found. |
```javascript theme={null}
// Guard: only set x-request-id if it isn't already present
if (!req.headerList.has("x-request-id")) {
req.headerList.add("x-request-id", bru.interpolate("{{$guid}}"));
}
// Check for a specific key + value combination
if (req.headerList.has("content-type", "application/json")) {
console.log("JSON request — body will be parsed.");
}
// Find the first custom header (anything starting with "x-")
const firstCustom = req.headerList.find(h => h.key.startsWith("x-"));
console.log("First custom header:", firstCustom);
// Collect all disabled headers for debugging
const disabled = req.headerList.filter(h => h.disabled === true);
console.log("Disabled headers:", disabled);
```
#### Iteration methods
| Method | Returns | Description |
| ----------------------------------------------- | ------- | ------------------------------------------- |
| `req.headerList.each(fn, context?)` | `void` | Calls `fn(header, index)` for every header. |
| `req.headerList.map(fn, context?)` | `any[]` | Returns a new array of mapped values. |
| `req.headerList.reduce(fn, initial?, context?)` | `any` | Reduces all headers to a single value. |
```javascript theme={null}
// Log every header with its position
req.headerList.each((h, i) => {
console.log(`[${i}] ${h.key}: ${h.value}`);
});
// Extract just the header keys
const keys = req.headerList.map(h => h.key);
console.log("Header keys:", keys);
// Output: ["content-type", "authorization", "x-request-id"]
// Build a summary string of enabled headers
const summary = req.headerList.reduce((acc, h) => {
if (!h.disabled) acc[h.key] = h.value;
return acc;
}, {});
console.log("Active headers:", summary);
```
#### Transform methods
| Method | Returns | Description |
| --------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------- |
| `req.headerList.toObject(excludeDisabled?, caseSensitive?, multiValue?, sanitizeKeys?)` | `object` | Plain `{ key: value }` map. |
| `req.headerList.toString()` | `string` | HTTP wire format `Key: Value\n...`, skipping disabled headers. |
| `req.headerList.toJSON()` | `object[]` | Same as `all()` — suitable for `JSON.stringify()`. |
```javascript theme={null}
// Convert to a plain object for logging or assertion
const headersObj = req.headerList.toObject();
console.log("Headers as object:", headersObj);
// Output: { "content-type": "application/json", "authorization": "Bearer abc..." }
// Exclude disabled headers and preserve case
const activeHeaders = req.headerList.toObject(true, true);
// HTTP wire format (useful for debugging proxy issues)
console.log(req.headerList.toString());
// Output:
// Content-Type: application/json
// Authorization: Bearer abc123
```
#### Write methods
| Method | Returns | Description |
| -------------------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `req.headerList.add(headerObj \| name, value?)` | `void` | Sets a header. Accepts `{ key, value }`, a `"Key: Value"` string, or `(name, value)` two-arg form. Overwrites an existing header with the same key. |
| `req.headerList.upsert(headerObj \| name, value?)` | `boolean \| null` | Sets or replaces a header. Returns `true` if new, `false` if updated, `null` if input was nil. |
| `req.headerList.remove(predicate, context?)` | `void` | Deletes headers by key string, `{ key }` object, or predicate function. |
| `req.headerList.clear()` | `void` | Removes all headers, including disabled ones. |
| `req.headerList.populate(items \| string)` | `void` | Adds items from an array or multi-line string, skipping keys that already exist. |
| `req.headerList.repopulate(items \| string)` | `void` | Clears all headers, then populates with new items. |
| `req.headerList.assimilate(source, prune?)` | `void` | Merges headers from a PropertyList or array. When `prune` is `true`, removes any headers not present in the source. |
```javascript theme={null}
// Add a single header (two-arg form)
req.headerList.add("x-correlation-id", bru.getVar("correlationId"));
// Add using an object
req.headerList.add({ key: "x-feature-flag", value: "dark-mode" });
// Add from a raw HTTP-style string
req.headerList.add("Accept: application/json");
// Upsert — set if new, update if exists; returns true/false
const wasNew = req.headerList.upsert("x-api-version", "2");
console.log("Was a new header?", wasNew); // true or false
// Remove by key name
req.headerList.remove("x-internal-debug");
// Remove all headers matching a predicate
req.headerList.remove(h => h.key.startsWith("x-legacy-"));
// Populate from an array — skips keys already present
req.headerList.populate([
{ key: "accept-language", value: "en-US" },
{ key: "cache-control", value: "no-cache" },
]);
// Replace everything with a fresh set
req.headerList.repopulate([
{ key: "content-type", value: "application/json" },
{ key: "authorization", value: "Bearer " + bru.getEnvVar("token") },
]);
// Merge headers from another list; remove anything not in the source
req.headerList.assimilate(otherHeaderList, true);
```
### `req.getBody(options?)`
Returns the current request body. For JSON content types, Bruno automatically parses and returns the body as an object. Pass `{ raw: true }` to get the raw string instead.
**Parameters:**
* `options` (object, optional): `raw` (boolean) — when `true`, returns the raw unparsed body string; otherwise returns the parsed object (default).
```javascript theme={null}
// Get the parsed body object (for JSON content type)
const body = req.getBody();
console.log("Body title:", body.title);
// Output: "Bruno"
// Get raw body string (useful for non-JSON or binary data)
const rawBody = req.getBody({ raw: true });
console.log("Raw body:", rawBody);
// Output: '{"title":"Bruno","role":"Developer"}'
```
### `req.setBody(body)`
Replaces the request body with the provided value. For JSON content type, pass an object — Bruno will serialize it automatically. Only available in **pre-request scripts**.
```javascript theme={null}
// Build the body dynamically from variables
req.setBody({
username: bru.getEnvVar("username"),
password: bru.getEnvVar("password"),
requestedAt: new Date().toISOString(),
});
```
### `req.setTimeout(milliseconds)`
Sets a custom timeout (in milliseconds) for the request. If the server does not respond within the specified time, the request is aborted. Overrides the collection-level timeout for this request.
```javascript theme={null}
// Allow up to 10 seconds for a slow endpoint
req.setTimeout(10000);
// Use a short timeout for health checks
req.setTimeout(2000);
```
### `req.getTimeout()`
Returns the current timeout value for the request in milliseconds. Useful for logging or conditionally changing the timeout.
```javascript theme={null}
const timeout = req.getTimeout();
console.log("Current timeout:", timeout);
// Output: 5000
if (timeout < 3000) {
req.setTimeout(10000); // Extend for slow environments
}
```
### `req.setMaxRedirects(count)`
Sets the maximum number of HTTP redirects Bruno will follow for this request. Set to `0` to disable following redirects entirely.
```javascript theme={null}
// Follow up to 5 redirects
req.setMaxRedirects(5);
// Disable redirect following to inspect the 3xx response directly
req.setMaxRedirects(0);
```
### `req.getExecutionMode()`
Get the current active execution mode: `runner` (collection run) or `standalone` (single request).
```javascript theme={null}
const executionMode = req.getExecutionMode();
console.log(`Request is running in ${executionMode} mode`);
```
### `req.getExecutionPlatform()`
Get the platform: `app` (desktop) or `cli` (Bruno CLI).
```javascript theme={null}
const platform = req.getExecutionPlatform();
console.log(`Request is running on ${platform} platform`);
```
### `req.onFail(callback)`
Handle request errors with a custom callback. `error` includes details about the failure.
```javascript theme={null}
req.onFail((error) => {
console.error("Request failed:", error.message);
console.log("Error details:", {
status: error.status,
statusText: error.statusText,
url: error.url,
method: error.method,
});
bru.setVar("lastError", error.message);
});
```
The `onFail` function is only available in Developer mode and should be called in pre-request scripts. When using Bruno CLI (v3.0.0+), pass the `--sandbox=developer` flag.
## Response
The `res` variable represents the HTTP response object and is automatically available inside your scripting and testing context after a request is executed. It contains all the information about the response received from the server, including status codes, headers, body data, and timing information.
The `res` object is only available in post-request scripts and test scripts, as it contains the response data from the completed request.
Here is a complete table for all available properties and methods on the `res` object.
| Property / Method | Description |
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
| res.status | The HTTP response status code (e.g., 200, 404, 500). |
| res.statusText | The HTTP response status text (e.g., "OK", "Not Found"). |
| res.headers | An object containing all response headers. |
| res.body | The response body data (automatically parsed as JSON if applicable). |
| res.responseTime | The total time taken for the request in milliseconds. |
| res.url | The final response URL (after following redirects). |
| res.getStatus() | Get the response status code. |
| res.getStatusText() | Get the response status text. |
| res.getHeader(name) | Get a specific response header by name. |
| res.getHeaders() | Get all response headers. |
| res.headerList | PropertyList interface — rich read, search, iteration, and transform methods for response headers (read-only). |
| res.getBody() | Get the response body data. |
| res.setBody(body) | Set the response body data. |
| res.getResponseTime() | Get the response time in milliseconds. |
| res.getUrl() | Get the response URL (final URL after redirects). |
| res.getSize() | Get the response size in bytes (returns object with body, headers, total). |
The `body` property is automatically parsed as JSON if the response has a JSON content type. For other content types, it will be a string.
Below are the detailed descriptions for properties and methods available on the `res` object.
### `res.status`
The HTTP response status code as a number (e.g., `200`, `201`, `400`, `404`, `500`). Equivalent to calling `res.getStatus()`.
```javascript theme={null}
console.log(res.status); // 200
// Use in test assertions
test("should return 201 Created", () => {
expect(res.status).to.equal(201);
});
```
### `res.statusText`
The human-readable HTTP status message that accompanies the status code (e.g., `"OK"`, `"Created"`, `"Not Found"`).
```javascript theme={null}
console.log(res.statusText); // "OK"
test("status text should be OK", () => {
expect(res.statusText).to.equal("OK");
});
```
### `res.headers`
A plain object containing all response headers as key-value pairs. Header names are lowercased.
```javascript theme={null}
console.log(res.headers);
// Output: { "content-type": "application/json", "x-request-id": "abc123", ... }
// Read a specific header directly
const contentType = res.headers["content-type"];
console.log("Content-Type:", contentType);
```
### `res.body`
The response body, automatically parsed as a JavaScript object if the response has a JSON `Content-Type`. For all other content types, it is returned as a string.
```javascript theme={null}
console.log(res.body);
// Output: { id: 1, name: "John Doe", email: "john@example.com" }
// Access a nested field
console.log(res.body.data.token);
// Save to a variable for use in later requests
bru.setVar("userId", res.body.id);
```
### `res.responseTime`
The total round-trip time in milliseconds from sending the request to receiving the full response.
```javascript theme={null}
console.log(res.responseTime); // 245
// Use in a performance assertion
test("response should be fast", () => {
expect(res.responseTime).to.be.lessThan(1000);
});
```
### `res.url`
The final URL of the response after all redirects have been followed. If no redirects occurred, this is the same as the original request URL.
```javascript theme={null}
console.log(res.url);
// Output: "https://www.example.com/final-page"
test("should resolve to the canonical URL", () => {
expect(res.url).to.include("example.com");
});
```
### `res.getStatus()`
Returns the HTTP response status code as a number. Equivalent to reading `res.status`.
```javascript theme={null}
const status = res.getStatus();
console.log("Status code:", status);
// Output: 200
test("should return success", () => {
expect(res.getStatus()).to.equal(200);
});
```
### `res.getStatusText()`
Returns the HTTP status message string. Equivalent to reading `res.statusText`.
```javascript theme={null}
const statusText = res.getStatusText();
console.log("Status text:", statusText);
// Output: "Created"
```
### `res.getHeader(name)`
Returns the value of a specific response header by name. Returns `undefined` if the header does not exist. Header lookup is case-insensitive.
```javascript theme={null}
const transactionId = res.getHeader("x-transaction-id");
console.log("Transaction ID:", transactionId);
// Output: "txn_7f3c2e"
// Save a server-generated token from the response header
const newToken = res.getHeader("x-auth-token");
if (newToken) {
bru.setEnvVar("access_token", newToken);
}
```
### `res.getHeaders()`
Returns an object containing all response headers as key-value pairs. Header names are lowercased.
```javascript theme={null}
const headers = res.getHeaders();
console.log("All response headers:", headers);
// Output: { "content-type": "application/json", "x-rate-limit-remaining": "99", ... }
// Assert a required header is present
test("should return a request-id header", () => {
expect(res.getHeaders()).to.have.property("x-request-id");
});
```
### `res.headerList`
`res.headerList` exposes the full **PropertyList** interface for response headers, giving you richer querying and iteration capabilities beyond `getHeader` / `getHeaders`. All key lookups are **case-insensitive** per the HTTP spec.
`res.headerList` is **read-only**. Calling any write method (`add`, `upsert`, `remove`, `clear`, `populate`, `repopulate`, `assimilate`) throws a `"HeaderList is read-only"` error. Use `req.headerList` when you need to mutate headers.
`res.headerList` is available in **post-request** and **test** scripts.
#### Read methods
| Method | Returns | Description |
| -------------------------- | --------------------- | ------------------------------------------------------------- |
| `res.headerList.get(name)` | `string \| undefined` | Value of the first response header matching the key. |
| `res.headerList.one(name)` | `object \| undefined` | Full header object `{ key, value }` for the matching key. |
| `res.headerList.all()` | `object[]` | Cloned array of all response header objects `{ key, value }`. |
| `res.headerList.count()` | `number` | Number of response headers. |
```javascript theme={null}
// Read the Content-Type value
const contentType = res.headerList.get("content-type");
console.log("Content-Type:", contentType);
// Output: "application/json; charset=utf-8"
// Get the full header object
const rateLimit = res.headerList.one("x-rate-limit-remaining");
console.log("Rate limit header:", rateLimit);
// Output: { key: "x-rate-limit-remaining", value: "42" }
// Log how many headers the server returned
console.log(`Server returned ${res.headerList.count()} headers.`);
```
#### Search methods
| Method | Returns | Description |
| ------------------------------------- | --------------------- | ------------------------------------------------------------------------------ |
| `res.headerList.has(name)` | `boolean` | `true` if any response header with that key exists. |
| `res.headerList.has(name, value)` | `boolean` | `true` if a header with that key **and** exact value exists. |
| `res.headerList.has(object)` | `boolean` | `true` if a header matching `object.key` exists. |
| `res.headerList.find(fn, context?)` | `object \| undefined` | First response header for which the predicate returns truthy. |
| `res.headerList.filter(fn, context?)` | `object[]` | All response headers for which the predicate returns truthy. |
| `res.headerList.indexOf(item)` | `number` | Index of a header by string key or `{ key, value }` object; `-1` if not found. |
```javascript theme={null}
// Assert a required security header is present
test("should include strict transport security", () => {
expect(res.headerList.has("strict-transport-security")).to.be.true;
});
// Check for a specific cache-control directive
if (res.headerList.has("cache-control", "no-store")) {
console.log("Response must not be cached.");
}
// Find the first Set-Cookie header
const setCookie = res.headerList.find(h => h.key === "set-cookie");
if (setCookie) {
bru.setVar("rawCookie", setCookie.value);
}
// Collect all x-* custom headers from the response
const customHeaders = res.headerList.filter(h => h.key.startsWith("x-"));
console.log("Custom response headers:", customHeaders);
```
#### Iteration methods
| Method | Returns | Description |
| ----------------------------------------------- | ------- | ---------------------------------------------------- |
| `res.headerList.each(fn, context?)` | `void` | Calls `fn(header, index)` for every response header. |
| `res.headerList.map(fn, context?)` | `any[]` | Returns a new array of mapped values. |
| `res.headerList.reduce(fn, initial?, context?)` | `any` | Reduces all response headers to a single value. |
```javascript theme={null}
// Log every response header
res.headerList.each((h, i) => {
console.log(`[${i}] ${h.key}: ${h.value}`);
});
// Build a list of header names for assertion
const headerNames = res.headerList.map(h => h.key);
test("should include required security headers", () => {
expect(headerNames).to.include("content-security-policy");
expect(headerNames).to.include("x-content-type-options");
});
// Sum up all numeric rate-limit headers
const rateLimitTotal = res.headerList.reduce((acc, h) => {
if (h.key.startsWith("x-rate-limit")) acc[h.key] = Number(h.value);
return acc;
}, {});
console.log("Rate limit info:", rateLimitTotal);
```
#### Transform methods
| Method | Returns | Description |
| --------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------- |
| `res.headerList.toObject(excludeDisabled?, caseSensitive?, multiValue?, sanitizeKeys?)` | `object` | Plain `{ key: value }` map. |
| `res.headerList.toString()` | `string` | HTTP wire format `Key: Value\n...`. |
| `res.headerList.toJSON()` | `object[]` | Same as `all()` — suitable for `JSON.stringify()`. |
```javascript theme={null}
// Convert response headers to a plain object for assertions
const headersObj = res.headerList.toObject();
test("should return JSON content type", () => {
expect(headersObj["content-type"]).to.include("application/json");
});
// Serialize headers to HTTP wire format for logging
console.log("Response headers (wire format):\n" + res.headerList.toString());
// Output:
// Content-Type: application/json; charset=utf-8
// X-Request-Id: txn_abc123
// X-Rate-Limit-Remaining: 42
// Save the full header list for debugging a flaky test
bru.setVar("lastResponseHeaders", JSON.stringify(res.headerList.toJSON()));
```
### `res.getBody()`
Returns the response body. If the response has a JSON `Content-Type`, the body is automatically parsed into a JavaScript object. Otherwise, it returns the raw string.
```javascript theme={null}
const data = res.getBody();
console.log("Response body:", data);
// Output: { id: 42, name: "Jane Smith", role: "admin" }
// Extract and store a token from the response
const token = res.getBody().token;
bru.setEnvVar("access_token", token, { persist: true });
```
### `res.setBody(body)`
Replaces the response body with a new value. This modifies the body as seen by downstream test scripts — it does **not** alter what was actually returned by the server.
```javascript theme={null}
// Normalize the body shape before tests run
const rawBody = res.getBody();
res.setBody({
status: rawBody.status_code,
user: rawBody.data?.user ?? null,
});
```
### `res.getUrl()`
Get the response URL after redirects (same information as `res.url`).
```javascript theme={null}
const responseUrl = res.getUrl();
console.log("Response URL:", responseUrl);
test("should end up at correct URL after redirects", () => {
const url = res.getUrl();
expect(url).to.equal("https://www.apple.com");
});
```
### `res.getResponseTime()`
Returns the total time taken for the request in milliseconds. Equivalent to reading `res.responseTime`.
```javascript theme={null}
const responseTime = res.getResponseTime();
console.log("Response time:", responseTime, "ms");
// Output: "Response time: 312 ms"
test("API should respond within 2 seconds", () => {
expect(res.getResponseTime()).to.be.lessThan(2000);
});
```
### `res.getSize()`
Get the response size in bytes. Returns `{ body, headers, total }`.
```javascript theme={null}
const responseSize = res.getSize();
test("Response body size is less than 1KB", () => {
const bodySize = res.getSize().body;
expect(bodySize).to.be.lessThan(1024);
});
```
## Environments
The `bru` object provides methods for managing environments in Bruno. Environments allow you to maintain different configurations for various deployment stages (development, staging, production, etc.).
Environment variables are specific to the selected environment and can be accessed across all requests in your collection.
Here are all available environment-related methods:
| Method | Description |
| ------------------------------- | ------------------------------------------------------------- |
| bru.getEnvName() | Retrieves the environment name. |
| bru.hasEnvVar(key) | Checks if the environment variable exists. |
| bru.getEnvVar(key) | Retrieves the value of an environment variable. |
| bru.setEnvVar(key, value) | Sets a new environment variable. |
| bru.deleteEnvVar(key) | Deletes a specific environment variable. |
| bru.getAllEnvVars() | Retrieves all environment variables as an object. |
| bru.deleteAllEnvVars() | Deletes all environment variables in the current environment. |
| bru.getGlobalEnvVar(key) | Get the Bruno global environment variable. |
| bru.setGlobalEnvVar(key, value) | Set the Bruno global environment variable. |
| bru.hasGlobalEnvVar(key) | Checks if the global environment variable exists. |
| bru.getAllGlobalEnvVars() | Retrieves all global environment variables as an object. |
### `bru.getEnvName()`
Retrieves the current environment name.
```javascript theme={null}
const envName = bru.getEnvName();
console.log("Current environment:", envName);
```
### `bru.getEnvVar(key)`
Returns the value of an environment variable from the currently selected environment. Returns `undefined` if the variable does not exist or if no environment is selected.
```javascript theme={null}
const baseUrl = bru.getEnvVar("baseUrl");
console.log("Base URL:", baseUrl);
// Output: "https://api.staging.example.com"
const token = bru.getEnvVar("access_token");
req.setHeader("Authorization", "Bearer " + token);
```
### `bru.setEnvVar(key, value, options?)`
Set the Bruno environment variable. By default, variables are in-memory only; use `{ persist: true }` to save to disk.
**Parameters:** `key` (string), `value` (any), `options.persist` (boolean, optional).
```javascript theme={null}
bru.setEnvVar("sessionId", "temp");
bru.setEnvVar("apiToken", "12345", { persist: true });
function onResponse(res) {
let data = res.getBody();
bru.setEnvVar("access_token", data.token, { persist: true });
}
onResponse(res);
```
### `bru.hasEnvVar(key)`
Returns `true` if the variable exists in the currently selected environment, `false` otherwise. Useful for guarding reads when a variable may not be set in every environment.
```javascript theme={null}
if (bru.hasEnvVar("access_token")) {
req.setHeader("Authorization", "Bearer " + bru.getEnvVar("access_token"));
} else {
console.warn("No access token found — request may fail authentication.");
}
```
### `bru.deleteEnvVar(key)`
Removes a specific variable from the current environment. Changes are in-memory only for the duration of the run unless the environment is re-saved.
```javascript theme={null}
// Clean up a short-lived session token after it has been used
bru.deleteEnvVar("session_token");
```
### `bru.getAllEnvVars()`
Returns an object containing all variables in the currently selected environment as key-value pairs. Useful for debugging or iterating over environment config.
```javascript theme={null}
const allVars = bru.getAllEnvVars();
console.log("Environment variables:", allVars);
// Output: { baseUrl: "https://api.example.com", access_token: "eyJ...", timeout: "5000" }
```
### `bru.deleteAllEnvVars()`
Removes every variable from the currently selected environment. Use with caution — this affects all requests in the collection that rely on environment variables.
This action clears all variables from the active environment. It cannot be undone within the same run.
```javascript theme={null}
// Reset the environment state at the start of a clean test suite
bru.deleteAllEnvVars();
```
### `bru.getGlobalEnvVar(key)`
Returns the value of a global environment variable. Global variables are shared across all environments and collections in the same Bruno workspace.
```javascript theme={null}
const orgId = bru.getGlobalEnvVar("organizationId");
console.log("Org ID:", orgId);
// Output: "org_abc123"
```
### `bru.setGlobalEnvVar(key, value)`
Sets or updates a global environment variable. Useful for storing values that need to be accessible across different environments or collections.
```javascript theme={null}
// Store a workspace-level token retrieved from an auth request
const data = res.getBody();
bru.setGlobalEnvVar("global_token", data.token);
```
### `bru.hasGlobalEnvVar(key)`
Returns `true` if the global environment variable exists, `false` otherwise. Useful for guarding reads when a global variable may not have been set yet.
```javascript theme={null}
if (bru.hasGlobalEnvVar("global_token")) {
req.setHeader("Authorization", "Bearer " + bru.getGlobalEnvVar("global_token"));
} else {
console.warn("No global token found — request may fail authentication.");
}
```
### `bru.getAllGlobalEnvVars()`
Returns an object of all global environment variables as key-value pairs.
```javascript theme={null}
const globalVars = bru.getAllGlobalEnvVars();
console.log("Global variables:", globalVars);
// Output: { organizationId: "org_abc123", global_token: "eyJ..." }
```
## Variables
Bruno provides a comprehensive variable system that allows you to manage and access different types of variables throughout your scripts. Variables are resolved in a specific order of precedence, with runtime variables taking the highest priority.
Variable precedence (highest to lowest): Runtime Variables → Request Variables → Folder Variables → Collection Variables → Environment Variables
Here are all available variable-related methods:
| Method | Description |
| --------------------------------------- | ------------------------------------------------------------- |
| bru.getProcessEnv(key) | Fetches the process environment variable for a given key. |
| bru.getCollectionName() | Retrieves the current collection name. |
| bru.getCollectionVar(key) | Retrieves the collection-level variable for the key. |
| bru.hasCollectionVar(key) | Checks if a collection variable exists. |
| bru.getFolderVar(key) | Fetches a folder-specific variable by key. |
| bru.getRequestVar(key) | Retrieves the value of a request variable. |
| bru.hasVar(key) | Checks if a variable exists. |
| bru.getVar(key) | Retrieves the value of a variable. |
| bru.setVar(key, value) | Sets a new variable with a key-value pair. |
| bru.getAllVars() | Retrieves all runtime variables as an object. |
| bru.deleteVar(key) | Deletes a specific variable. |
| bru.deleteAllVars() | Deletes all runtime variables. |
| bru.getOauth2CredentialVar(key) | Retrieves an OAuth2 credential variable value. |
| bru.resetOauth2Credential(credentialId) | Resets an OAuth2 credential so it can be re-authorized. |
| bru.getSecretVar(key) | Retrieves a secret variable from a configured secret manager. |
### `bru.getProcessEnv(key)`
Returns the value of a Node.js `process.env` variable. This lets you read OS-level environment variables (e.g. from a `.env` file loaded in your shell) without storing secrets in your collection files.
```javascript theme={null}
// Read a secret from the host environment — never hardcode it in the collection
const secretToken = bru.getProcessEnv("SECRET_ACCESS_TOKEN");
req.setHeader("Authorization", "Bearer " + secretToken);
// Useful in CI pipelines where secrets are injected as environment variables
const dbPassword = bru.getProcessEnv("DB_PASSWORD");
```
### `bru.getCollectionVar(key)`
Returns the value of a collection-level variable. Collection variables are defined in the collection settings and are shared across all requests and folders in the collection.
```javascript theme={null}
const namespace = bru.getCollectionVar("namespace");
console.log("Namespace:", namespace);
// Output: "production"
// Use collection var to build the request URL
const baseUrl = bru.getCollectionVar("baseUrl");
req.setUrl(`${baseUrl}/api/v1/users`);
```
### `bru.hasCollectionVar(key)`
Returns `true` if a collection-level variable with the given key exists, `false` otherwise.
```javascript theme={null}
if (bru.hasCollectionVar("namespace")) {
console.log("Namespace:", bru.getCollectionVar("namespace"));
} else {
console.warn("Collection variable 'namespace' is not set.");
}
```
### `bru.getCollectionName()`
Returns the display name of the currently active collection. Useful for logging in shared scripts that run across multiple collections.
```javascript theme={null}
const collectionName = bru.getCollectionName();
console.log("Running in collection:", collectionName);
// Output: "Payment API"
```
### `bru.getFolderVar(key)`
Returns the value of a folder-level variable. Folder variables are defined in the folder settings and are only accessible to requests within that folder.
```javascript theme={null}
const company = bru.getFolderVar("company");
console.log("Company:", company);
// Output: "Acme Corp"
req.setHeader("x-company-id", bru.getFolderVar("companyId"));
```
### `bru.getRequestVar(key)`
Returns the value of a request-level variable. Request variables are defined within a specific request and are only accessible in that request's scripts.
```javascript theme={null}
const source = bru.getRequestVar("source");
const destination = bru.getRequestVar("destination");
console.log(`Routing from ${source} to ${destination}`);
// Output: "Routing from warehouse-a to warehouse-b"
```
### `bru.hasVar(key)`
Returns `true` if a runtime variable with the given key exists, `false` otherwise. Runtime variables are created with `bru.setVar()` and exist only for the duration of the collection run.
```javascript theme={null}
// Guard downstream requests that depend on a variable set in a previous request
if (bru.hasVar("userId")) {
console.log("User ID:", bru.getVar("userId"));
} else {
console.error("userId not set — did the login request run first?");
}
```
### `bru.getVar(key)`
Returns the value of a runtime variable. Runtime variables are in-memory only and are used to share data between requests within a single collection run.
```javascript theme={null}
// Retrieve an ID captured from a previous request
const userId = bru.getVar("userId");
console.log("Using user ID:", userId);
// Output: "usr_7a4f2d"
req.setUrl(`https://api.example.com/users/${userId}`);
```
### `bru.setVar(key, value)`
Creates or updates a runtime variable. Runtime variables are temporary and only exist during the current collection run — use them to pass data from one request to the next.
```javascript theme={null}
// Store the created resource's ID from the response to use in subsequent requests
const data = res.getBody();
bru.setVar("userId", data.id);
console.log("Stored user ID:", data.id);
// Also works with non-string values
bru.setVar("retryCount", 0);
bru.setVar("createdAt", new Date().toISOString());
```
### `bru.deleteVar(key)`
Removes a single runtime variable by key. Has no effect if the variable does not exist.
```javascript theme={null}
// Clean up a temporary flag after it has been used
bru.deleteVar("skipAuth");
bru.deleteVar("userId");
```
### `bru.deleteAllVars()`
Removes all runtime variables. Use at the start of a run to ensure a clean state.
```javascript theme={null}
// Reset all runtime state before beginning a new test scenario
bru.deleteAllVars();
```
### `bru.getAllVars()`
Returns an object containing all current runtime variables as key-value pairs. Useful for debugging mid-run state.
```javascript theme={null}
const allRuntimeVars = bru.getAllVars();
console.log("Current runtime variables:", allRuntimeVars);
// Output: { userId: "usr_7a4f2d", authToken: "eyJ...", retryCount: 0 }
```
### `bru.getOauth2CredentialVar(key)`
Returns the value of an OAuth2 credential variable (e.g. the current access token). This reads from the OAuth2 token store managed by Bruno's auth layer — not from environment or runtime variables.
```javascript theme={null}
const accessToken = bru.getOauth2CredentialVar("access_token");
console.log("OAuth2 token:", accessToken ? "present" : "missing");
// Manually attach it to a header if needed
req.setHeader("Authorization", "Bearer " + accessToken);
```
### `bru.resetOauth2Credential(credentialId)`
Clears the stored OAuth2 tokens for the given credential ID, forcing Bruno to re-run the OAuth2 authorization flow on the next request that uses it.
```javascript theme={null}
// Force re-authentication when a token has expired or been revoked
bru.resetOauth2Credential("my-oauth2-credential");
```
### `bru.getSecretVar(key)`
Retrieve a secret from a configured secret manager. Key pattern: `.`.
```javascript theme={null}
const apiKey = bru.getSecretVar("payment-service.api-key");
req.setHeader("x-api-key", apiKey);
const dbPassword = bru.getSecretVar("db-credentials.password");
console.log("Secret retrieved successfully");
```
Secrets must be configured in your collection's secret manager settings before they can be accessed via `bru.getSecretVar()`. See [Secret Managers](/secrets-management/secret-managers/overview) for setup details.
## Runner
The Runner API provides methods to control the execution flow of your collection runs. These methods are specifically designed for use within the collection runner context, allowing you to skip requests, change execution order, stop the run entirely, or read data from attached CSV/JSON files during data-driven runs.
Runner methods like `skipRequest()`, `stopExecution()`, and `iterationData` only work during collection runs and will have no effect when running individual requests. `iterationData` is only populated when the collection runner is iterating over an attached data file.
Here are all available runner-related methods:
| Method / Property | Description |
| -------------------------------------- | ------------------------------------------------------------------------ |
| bru.setNextRequest(requestName) | Sets the next request to execute. |
| bru.runner.setNextRequest(requestName) | Alter the order of requests by specifying the next request. |
| bru.runner.skipRequest() | Skip the execution of the current request. |
| bru.runner.stopExecution() | Terminate a collection run. |
| bru.runner.iterationData | Read and manipulate the current row from an attached CSV/JSON data file. |
| bru.runner.iterationIndex | Zero-based index of the current iteration. |
| bru.runner.totalIterations | Total number of iterations in the current run. |
### `bru.setNextRequest(requestName)`
Overrides the default sequential execution order — after the current request finishes, Bruno jumps to the named request instead of the next one in the list. Use in **post-request** or **test** scripts. Pass `null` to stop the run immediately after the current request.
Use the request's **display name** exactly as it appears in Bruno (not a folder path).
```javascript theme={null}
// Branch to a polling request after kicking off an async job
const status = res.getStatus();
if (status === 202) {
bru.setNextRequest("Poll Job Status");
} else if (status >= 400) {
bru.setNextRequest(null); // Stop the run on error
}
// Retry loop — re-run the same request up to 3 times
const retries = bru.getVar("retries") || 0;
if (res.getBody().status === "pending" && retries < 3) {
bru.setVar("retries", retries + 1);
bru.setNextRequest("Check Job Status"); // Loop back
}
```
### `bru.runner.setNextRequest(requestName)`
Identical to `bru.setNextRequest()` — an alternative access path on the `bru.runner` object. Specify the next request to execute by its display name.
```javascript theme={null}
// Jump to a status check after kicking off a background job
bru.runner.setNextRequest("Get Process Status");
```
### `bru.runner.skipRequest()`
Skips the current request entirely during a collection run — Bruno moves on to the next request without sending it. Call this in a **pre-request** script.
```javascript theme={null}
// Skip requests tagged "slow-test" in CI environments
if (req.getTags().includes("slow-test") && bru.getEnvVar("CI") === "true") {
bru.runner.skipRequest();
}
// Skip if a required variable is missing to avoid sending an incomplete request
if (!bru.hasVar("userId")) {
console.warn("Skipping — userId not set by a previous request");
bru.runner.skipRequest();
}
```
### `bru.runner.stopExecution()`
Immediately stops the entire collection run. No further requests are executed after this call. Can be used in pre-request, post-response, or test scripts.
```javascript theme={null}
// Abort the entire run if a critical auth request fails
if (res.getStatus() !== 200) {
console.error("Authentication failed — stopping run.");
bru.runner.stopExecution();
}
// Stop after a maximum number of retries
if (bru.getVar("retries") >= 5) {
console.error("Max retries reached — aborting.");
bru.runner.stopExecution();
}
```
### `bru.runner.iterationData`
Object for accessing and manipulating the **current row** from an attached CSV or JSON data file during a [data-driven collection run](/testing/automate-test/data-driven-testing). Each column (CSV) or object key (JSON) in the file becomes a field you can read in scripts.
Use in **pre-request**, **post-response**, or **test** scripts when the collection runner is iterating over a data file (UI runner or CLI with `--csv-file-path` / `--json-file-path`).
The data file does not automatically replace your request body. Use `{{columnName}}` in the request, or read values with `iterationData` and set the body in a script (for example with `req.setBody()`).
#### `bru.runner.iterationData.has(key)`
Returns `true` if the named field exists in the current iteration row.
```javascript theme={null}
if (bru.runner.iterationData.has("username")) {
console.log("Username exists in current iteration");
}
```
#### `bru.runner.iterationData.get(key?)`
Returns a single field value when `key` is provided. Call without arguments to get all fields for the current row as an object.
```javascript theme={null}
const username = bru.runner.iterationData.get("username");
console.log(`Current username: ${username}`);
const allData = bru.runner.iterationData.get();
console.log("All iteration data:", allData);
```
#### `bru.runner.iterationData.stringify()`
Returns the current iteration row as a JSON string.
```javascript theme={null}
const jsonData = bru.runner.iterationData.stringify();
console.log("JSON data:", jsonData);
```
#### `bru.runner.iterationData.unset(key)`
Removes a field from the current iteration only. Does not modify the source data file or affect other iterations.
```javascript theme={null}
bru.runner.iterationData.unset("password");
```
**Example:** build the request body from iteration data
```javascript theme={null}
const username = bru.runner.iterationData.get("username");
const password = bru.runner.iterationData.get("password");
req.setBody({ username, password });
```
**Example:** conditional logic per iteration
```javascript theme={null}
if (bru.runner.iterationIndex === 0) {
bru.runner.iterationData.unset("password");
}
if (bru.runner.iterationData.has("password")) {
console.log("Password available");
}
```
### `bru.runner.iterationIndex`
Zero-based index of the current iteration in a data-driven run.
```javascript theme={null}
const currentIteration = bru.runner.iterationIndex;
console.log(`Running iteration ${currentIteration}`);
```
### `bru.runner.totalIterations`
Total number of iterations in the current data-driven run.
```javascript theme={null}
const total = bru.runner.totalIterations;
console.log(`Total iterations: ${total}`);
```
## Utilities
The Utilities API provides a collection of helper functions for common tasks such as making HTTP requests, working with cookies, managing tests, and other utility operations.
Here are all available utility methods:
| Method | Description |
| ---------------------------------- | -------------------------------------------------------------------------------------------------- |
| bru.sendRequest(options, callback) | Sends a programmatic HTTP request within your script. |
| bru.sleep(milliseconds) | Pauses execution for the specified duration. |
| bru.interpolate(string) | Evaluates dynamic variables within a string. |
| bru.disableParsingResponseJson() | Disables JSON response parsing for the request. |
| bru.isSafeMode() | Detects whether the script is running in Safe Mode. |
| bru.cwd() | Returns the current working directory. |
| bru.runRequest(requestPathName) | Executes a request by its path name. |
| bru.getAssertionResults() | Retrieves the results of assertions. |
| bru.getTestResults() | Fetches the test results. |
| bru.cookies | Request-scoped helpers on `bru.cookies` (read / iterate / write) and optional `bru.cookies.jar()`. |
### `bru.sendRequest(options, callback?)`
Send a programmatic HTTP request from your script. Supports `method`, `url`, `headers`, `data`, `timeout`, `httpsAgent`, and optional `callback(err, res)`; also supports `await` without a callback.
Bruno applies your TLS (custom CA, SSL verification), proxy, and client certificate settings to `bru.sendRequest()` unless you override with `httpsAgent`.
You can see the executed request in the [Timeline](/debugging/timeline) tab with a `sendRequest` context badge.
```javascript theme={null}
await bru.sendRequest(
{
method: "POST",
url: "https://echo.usebruno.com",
headers: { "Content-Type": "application/json" },
data: { key: "value" },
},
function (err, res) {
if (err) {
console.error("Error:", err);
return;
}
console.log("Response:", res.data);
}
);
```
Custom HTTPS agent (e.g. self-signed):
```javascript theme={null}
const https = require("node:https");
const agent = new https.Agent({ rejectUnauthorized: false });
const res = await bru.sendRequest({
url: "https://self-signed.badssl.com",
method: "GET",
httpsAgent: agent,
});
console.log("Response:", res.data);
```
Custom CA (requires [Developer Mode](/get-started/configure/javascript-sandbox#developer-mode) for `fs`):
Custom HTTPS agents and loading certificates from disk require [Developer Mode](/get-started/configure/javascript-sandbox#developer-mode).
```javascript theme={null}
const https = require("node:https");
const fs = require("fs");
const path = require("path");
const ca = fs.readFileSync(path.join(bru.cwd(), "certs", "ca.pem"));
const agent = new https.Agent({ ca, rejectUnauthorized: true });
const res = await bru.sendRequest({
url: "https://api.example.com/data",
method: "GET",
httpsAgent: agent,
});
```
### `bru.sleep(milliseconds)`
Pauses script execution for the specified number of milliseconds. Use this to introduce a delay when polling a slow API or waiting for a background job to complete.
```javascript theme={null}
// Wait 3 seconds before polling for job status
await bru.sleep(3000);
console.log("Resuming after delay...");
// Progressive delay in a retry loop
const retries = bru.getVar("retries") || 0;
await bru.sleep(1000 * (retries + 1)); // 1s, 2s, 3s...
```
### `bru.interpolate(string)`
Resolves Bruno dynamic variables (e.g. `{{$randomFirstName}}`, `{{baseUrl}}`) inside a string and returns the result. Useful when you need a variable-interpolated value inside a script, not just inside a request field.
```javascript theme={null}
// Generate a random name for a test user
const firstName = bru.interpolate("{{$randomFirstName}}");
console.log("Random name:", firstName);
// Output: "James"
// Build a dynamic payload string with multiple variables
const userInfo = bru.interpolate(`
Name: {{$randomFullName}}
Job: {{$randomJobTitle}}
Email: {{$randomEmail}}
`);
console.log(userInfo);
// Output:
// Name: Clara Johnson
// Job: Senior Developer
// Email: clara.johnson@example.com
```
### `bru.disableParsingResponseJson()`
Prevents Bruno from automatically parsing the response body as JSON, even when the `Content-Type` is `application/json`. After calling this, `res.getBody()` returns the raw string. Call this in a **pre-request** script.
```javascript theme={null}
// Keep the raw JSON string to process with a custom parser
bru.disableParsingResponseJson();
// Then in the post-response script:
// const rawString = res.getBody(); // returns string, not object
```
### `bru.cwd()`
Returns the absolute path of the collection's root directory on disk. Useful when reading files (certificates, fixtures, config) relative to the collection in Developer Mode.
```javascript theme={null}
const collectionDir = bru.cwd();
console.log("Collection directory:", collectionDir);
// Output: "/Users/me/projects/my-api-collection"
// Load a local fixture file relative to the collection
const fs = require("fs");
const fixture = JSON.parse(fs.readFileSync(`${bru.cwd()}/fixtures/user.json`, "utf8"));
req.setBody(fixture);
```
### `bru.isSafeMode()`
Returns `true` when the script is running in **Safe Mode** (the default sandbox), or `false` when running in **Developer Mode** which enables `require()` and Node.js built-ins. Use this to branch logic based on the active sandbox.
```javascript theme={null}
if (bru.isSafeMode()) {
console.log("Running in Safe Mode — require() is not available.");
} else {
// Developer Mode: full Node.js access
const fs = require("fs");
const config = JSON.parse(fs.readFileSync(`${bru.cwd()}/config.json`, "utf8"));
bru.setVar("config", config);
}
```
See [JavaScript Sandbox](/get-started/configure/javascript-sandbox#detecting-sandbox-mode-in-scripts).
### `bru.runRequest(requestPathName)`
Executes another request in the collection by its name and returns its response. Useful for chaining dependent requests inside a single script without relying on collection runner order.
Do not call `bru.runRequest()` from a collection-level pre-request script — it can cause an infinite recursive loop. Always use double quotes for the request name.
You can see the executed request in the [Timeline](/debugging/timeline) tab with a `runRequest` context badge.
```javascript theme={null}
// Run an auth request and use the token it returns
const authResponse = await bru.runRequest("Login");
const token = authResponse.data.token;
req.setHeader("Authorization", "Bearer " + token);
// Run a cleanup step after the main request
const cleanupResponse = await bru.runRequest("Delete Test User");
console.log("Cleanup status:", cleanupResponse.status);
```
### `bru.getTestResults()`
Returns the test results for the current request as an array of result objects. Each object contains the test name and its pass/fail status. Use in **test** scripts to inspect or log outcomes programmatically.
```javascript theme={null}
const testResults = await bru.getTestResults();
console.log("Test results:", testResults);
// Output: [{ name: "status is 200", status: "pass" }, { name: "has token", status: "fail" }]
// Log only failing tests
testResults.filter(r => r.status === "fail").forEach(r => {
console.error("FAILED:", r.name);
});
```
### `bru.getAssertionResults()`
Returns the assertion results for the current request as an array of result objects, each describing the expression, expected value, actual value, and pass/fail status. Use in **test** scripts.
```javascript theme={null}
const assertionResults = await bru.getAssertionResults();
console.log("Assertion results:", assertionResults);
// Output: [{ lhs: "res.status", operator: "eq", rhs: "200", status: "pass" }]
// Count how many assertions failed
const failures = assertionResults.filter(a => a.status === "fail");
console.log(`${failures.length} assertion(s) failed`);
```
## Cookies
Cookie helpers on **`bru.cookies`** are **scoped to the current request URL** automatically. You can **read** and **iterate** cookies synchronously, and **write** changes with `async` methods that persist to Bruno’s underlying cookie storage—without constructing a jar or repeating the URL on every call.
For advanced flows (explicit URL, multiple sites in one script), you can still use **`bru.cookies.jar()`** below; the jar API is unchanged.
### Why use request-scoped helpers?
**Jar-based (still valid):** you create a jar, pass the site URL into each call.
```javascript theme={null}
const jar = bru.cookies.jar();
const session = await jar.getCookie("https://example.com", "session");
const hasAuth = await jar.hasCookie("https://example.com", "auth");
await jar.setCookie("https://example.com", "token", "abc123");
```
**Request-scoped (simpler):** Bruno uses the active request URL for you.
```javascript theme={null}
const session = bru.cookies.get("session");
const hasAuth = bru.cookies.has("auth");
await bru.cookies.add({ key: "token", value: "abc123" });
bru.cookies.each((c) => console.log(c.key, c.value));
const authCookies = bru.cookies.filter((c) => c.key.startsWith("auth_"));
const asObject = bru.cookies.toObject();
```
Use whichever style fits your script; both operate on the same stored cookies for the request.
### Read methods (sync)
| Method | Description |
| ------------------------------ | ---------------------------------------------------------------------------------- |
| `bru.cookies.get(name)` | Cookie **value** for `name` at the current request URL (`undefined` when missing). |
| `bru.cookies.has(name)` | `true` if a cookie with that name exists. |
| `bru.cookies.has(name, value)` | `true` if a cookie exists with that **name** and **value**. |
| `bru.cookies.one(id)` | Look up a cookie entry by **id** when entries expose ids. |
| `bru.cookies.all()` | All cookie entries for the scoped URL. |
| `bru.cookies.count()` | Number of cookies. |
| `bru.cookies.idx(n)` | Cookie at numeric index `n`. |
| `bru.cookies.indexOf(item)` | Index of a given cookie entry. |
| `bru.cookies.toObject()` | Plain object: cookie **name** → **value** (handy for logging or assertions). |
| `bru.cookies.toString()` | String representation of the cookie set. |
### Iteration methods (sync)
| Method | Description |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bru.cookies.each(fn)` | Synchronously iterates over all cookies and calls the provided function `fn(cookie)` for each one. Useful for logging or processing each cookie in turn. |
| `bru.cookies.find(fn)` | Returns the first cookie that matches the predicate function `fn(cookie)`. If no cookie matches, returns `undefined`. |
| `bru.cookies.filter(fn)` | Returns an array of all cookies for which the predicate function `fn(cookie)` returns `true`. |
| `bru.cookies.map(fn)` | Maps each cookie to a new value by applying the function `fn(cookie)`, returning an array of the results. |
| `bru.cookies.reduce(fn, initial)` | Reduces the list of cookies to a single value using the reducer function `fn(acc, cookie)`, starting with `initial` as the accumulator. |
### Write methods (async)
These update the persisted jar for the scoped URL—use **`await`** in async scripts.
| Method | Description |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `await bru.cookies.add(obj)` | Add a cookie; `obj` includes at least **`key`** and **`value`** (plus optional `domain`, `path`, `expires`, `maxAge`, `secure`, `httpOnly`, `sameSite`, etc., when supported). |
| `await bru.cookies.upsert(obj)` | Add or replace a cookie with the same identity rules as your Bruno version. |
| `await bru.cookies.remove(name)` | Remove by cookie name. |
| `await bru.cookies.delete(name)` | Same intent as `remove` (alias for ergonomics). |
| `await bru.cookies.clear()` | Remove all cookies for the scoped context. |
```javascript theme={null}
await bru.cookies.add({ key: "session", value: res.body.sessionId, path: "/", maxAge: 3600 });
```
### Where it runs
Request-scoped cookie helpers are available in **pre-request**, **post-response**, and **test** scripts in both the **Node VM** runtime and the **QuickJS** sandbox, matching the environments where your other `bru.*` script APIs run.
***
### Cookie jar (`bru.cookies.jar()`)
Low-level jar APIs work in pre-request, post-request, and test scripts. Create a jar when you need an explicit URL or jar instance not tied to a single request context.
### `bru.cookies.jar()`
Create a cookie jar instance.
```javascript theme={null}
const jar = bru.cookies.jar();
```
### `jar.setCookie(url, name, value)` / `jar.setCookie(url, cookieObject)`
Set one cookie: either `(url, name, value)` or `(url, { key, value, domain, path, expires, maxAge, secure, httpOnly, sameSite })`.
```javascript theme={null}
const jar = bru.cookies.jar();
jar.setCookie("https://example.com", "sessionId", "abc123");
jar.setCookie("https://example.com", {
key: "userToken",
value: "xyz789",
domain: "example.com",
path: "/api",
secure: true,
httpOnly: true,
maxAge: 3600,
});
```
### `jar.setCookies(url, cookies)`
Set multiple cookies from an array of cookie objects.
```javascript theme={null}
const jar = bru.cookies.jar();
jar.setCookies("https://example.com", [
{ key: "sessionId", value: "abc123", secure: true, httpOnly: true },
{ key: "userPreference", value: "dark-mode", path: "/", maxAge: 86400 },
]);
```
### `jar.getCookie(url, name)`
Get a cookie by name; returns the cookie object or `null`.
```javascript theme={null}
const jar = bru.cookies.jar();
const sessionCookie = await jar.getCookie("https://example.com", "sessionId");
```
### `jar.hasCookie(url, name, callback?)`
Returns a Promise of `true`/`false`, or invokes `callback(error, exists)` if provided.
```javascript theme={null}
const jar = bru.cookies.jar();
const exists = await jar.hasCookie("https://example.com", "sessionId");
jar.hasCookie("https://testbench-sanity.usebruno.com", "existing_cookie", function (error, exists) {
test("should work with callback pattern", function () {
expect(error).to.be.null;
expect(exists).to.be.true;
});
});
```
### `jar.getCookies(url)`
Get all cookies for a URL (array).
```javascript theme={null}
const jar = bru.cookies.jar();
const allCookies = await jar.getCookies("https://example.com");
```
### `jar.deleteCookie(url, name)`
Delete one cookie. Domain/path must match how the cookie was set.
Deletion only succeeds when domain and path match the stored cookie.
```javascript theme={null}
const jar = bru.cookies.jar();
jar.deleteCookie("https://example.com", "sessionId");
```
### `jar.deleteCookies(url)`
Remove all cookies for a URL.
```javascript theme={null}
const jar = bru.cookies.jar();
jar.deleteCookies("https://example.com");
```
### `jar.clear()`
Clear every cookie in the jar.
```javascript theme={null}
const jar = bru.cookies.jar();
jar.clear();
```
# Creating a JS File in Bruno
Source: https://docs.usebruno.com/testing/script/js-file
Bruno provides the ability to create JavaScript tests where you can modularize common code and reuse the tests across different requests or collections.
This can be especially helpful in complex scripting scenarios where the same test will be run across multiple requests, or you're looking to leverage data that lays outside of Bruno.
## Create a New Script
You can generate a JS File by:
1. Right clicking on any collection or folder
2. Select `New Script` from dropdown.
3. Enter name and select **Create**
## Import the Function(s)
Once your JS File has been created, you can now import the functions into any `Script` tab across your resources.
When the requests are run, you will see the script being invoked.
## Sharing Scripts Across Collections
The `additionalContextRoots` feature in Bruno allows you to share scripts across multiple collections, eliminating code duplication and promoting better code organization. This is particularly useful when you have common utility functions or shared logic that you want to use across different collections.
Make sure you're in Developer Mode to use `additionalContextRoots` feature. When using Bruno CLI (v3.0.0+), pass the `--sandbox=developer` flag.
### How to Use
#### 1. Create a Shared Scripts Directory
First, create a directory to store your shared scripts. This can be anywhere in your project structure, but it's common to place it at the root level of your project.
```bash theme={null}
project/
├── collection-a/
│ └── bruno.json
├── collection-b/
│ └── bruno.json
└── shared-scripts/
└── utils.js
```
#### 2. Configure Shared Scripts Directory
##### 2.1 Configure bruno.json
In each BRU collection's `bruno.json` file, add the `additionalContextRoots` configuration under the `scripts` object:
```json showLineNumbers theme={null}
{
"scripts": {
"additionalContextRoots": ["./path/to/shared/scripts"]
}
}
```
##### 2.2 Configure opencollection.yml
In each YML collection's `opencollection.yml` file, add the `additionalContextRoots` configuration under the `extensions.bruno.scripts` object:
```yml showLineNumbers theme={null}
extensions:
bruno:
scripts:
additionalContextRoots:
- "./path/to/shared/scripts"
```
#### 3. Create Shared Scripts
Create your shared scripts in the specified directory. For example: `shared-scripts/utils.js`:
```javascript showLineNumbers theme={null}
// shared-scripts/utils.js
const formatDate = (date) => {
return new Date(date).toISOString();
};
const generateAuthToken = (apiKey) => {
return `Bearer ${apiKey}`;
};
module.exports = {
formatDate,
generateAuthToken
};
```
#### 4. Use Shared Scripts in Collections
You can now use these shared scripts in your collection's pre-request or post-response scripts:
```javascript showLineNumbers theme={null}
// In your collection's pre-request script
const { formatDate, generateAuthToken } = require('../shared-scripts/utils.js');
// Use the shared functions
const today = formatDate(new Date());
const token = generateAuthToken('your-api-key');
// Set environment variables or request headers
bru.setEnvVar('auth_token', token);
bru.setEnvVar('current_date', today);
```
# Request Chaining
Source: https://docs.usebruno.com/testing/script/request-chaining
Request chaining in Bruno allows you to execute multiple requests in sequence, pass data between them, and control the flow of your API testing workflow. This powerful feature enables you to build complex test scenarios where the output of one request becomes the input for the next.
## Overview
Bruno provides several methods for request chaining:
* **`bru.runRequest()`** - Execute requests within your collection
* **`bru.sendRequest()`** - Send programmatic HTTP requests
* **`bru.runner.setNextRequest()`** - Control request execution order in collection runs
## `bru.runRequest()`
Execute any request in your collection and retrieve the response directly within your script.
Avoid using `bru.runRequest()` in **collection-level scripts**. Since collection scripts run for all requests, calling `bru.runRequest()` from a collection script will trigger the target request, which will also execute the collection script again, creating an infinite loop.
### Syntax
```javascript showLineNumbers theme={null}
const requestResponse = await bru.runRequest(
"absolute/path/to/a/request/from/collection/root"
);
```
### Parameters
* **requestPath**: The absolute path to the request from the collection root (string)
### Returns
Returns a response object containing:
* `status`: HTTP status code
* `statusText`: HTTP status text
* `headers`: Response headers object
* `body`: Response body (automatically parsed if JSON)
* `responseTime`: Response time in milliseconds
### Examples
#### Basic Request Execution
```javascript showLineNumbers {2,5-6} theme={null}
// Execute a request and get the response
const response = await bru.runRequest("auth/login");
// Check if login was successful
if (response.status === 200) {
const data = response.body;
bru.setVar("authToken", data.token);
console.log("Login successful, token saved");
} else {
console.error("Login failed:", response.statusText);
}
```
## `bru.sendRequest()`
Send a programmatic HTTP request within your script. This allows you to make additional API calls during script execution.
### Syntax
```javascript showLineNumbers theme={null}
await bru.sendRequest({
method: string,
url: string,
headers?: object,
data?: string | object,
timeout?: number
}, callback)
```
### Parameters
* **method**: HTTP method (GET, POST, PUT, DELETE, etc.)
* **url**: The URL to send the request to
* **headers**: (Optional) Request headers object
* **data**: (Optional) Request data. Can be a string or object
* **timeout**: (Optional) Request timeout in milliseconds
* **callback**: Function to handle the response with signature `(err, res)`
### Examples
#### Basic HTTP Request
```javascript showLineNumbers {1,8-9} theme={null}
await bru.sendRequest({
method: 'GET',
url: 'https://api.example.com/users',
headers: {
'Authorization': 'Bearer ' + bru.getVar('authToken'),
'Content-Type': 'application/json'
}
}, function (err, res) {
if (err) {
console.error('Error:', err);
return;
}
console.log('Users:', res.data);
bru.setVar('users', res.data);
});
```
#### POST Request with Data
```javascript showLineNumbers {1-4,13} theme={null}
const userData = {
name: 'John Doe',
email: 'john@example.com'
};
await bru.sendRequest({
method: 'POST',
url: 'https://api.example.com/users',
headers: {
'Authorization': 'Bearer ' + bru.getVar('authToken'),
'Content-Type': 'application/json'
},
data: userData,
timeout: 10000
}, function (err, res) {
if (err) {
console.error('Error creating user:', err);
return;
}
if (res.status === 201) {
console.log('User created:', res.data);
bru.setVar('newUserId', res.data.id);
}
});
```
## `bru.runner.setNextRequest()`
Control the order in which requests are executed during collection runs. This allows you to skip requests or change the execution flow based on conditions.
`bru.runner.setNextRequest()` works only in **post-request scripts** or **test scripts**.
### Syntax
```javascript showLineNumbers theme={null}
bru.runner.setNextRequest("request-name");
```
### Parameters
* **requestName**: The name of the next request to execute (string) or `null` to stop execution
### How it works
* `setNextRequest` works with **single requests** - it executes the current request first, then jumps to the specified request
* It **skips** all requests between the current one and the target request
* The target request must exist in the collection and be specified by its exact name
* **For requests inside folders**: Use just the request name (e.g., "request-name"), not the full path
### Examples
#### Conditional Request Execution
```javascript showLineNumbers theme={null}
// In post-request script
const response = res.getBody();
if (response.status === 'active') {
// Continue to next request
bru.runner.setNextRequest("process-active-user");
} else if (response.status === 'inactive') {
// Skip to different request
bru.runner.setNextRequest("handle-inactive-user");
} else {
// Stop execution
bru.runner.setNextRequest(null);
}
```
* **Use `bru.runRequest()`** for requests within your collection (faster, uses existing configuration)
* **Use `bru.sendRequest()`** for external APIs or when you need custom request configuration
* **Avoid nested loops** that could cause infinite request chains
# Request Object
Source: https://docs.usebruno.com/testing/script/request/request-object
The `req` object represents the HTTP request made to the server. It contains various properties that define the request details.
## `req` Sub-Object
The `req` sub-object contains detailed information about the request.
* `assertions` : An array containing any assertions associated with the request. *See [assertions](../../tests/assertions)*.
* `auth` : An object containing authentication credentials, such as username and password.
* `headers` : A sub-object representing the HTTP headers associated with the request.
* `method` : The HTTP method used for the request (e.g., "GET", "POST").
* `mode` : The mode of the request (e.g., "none", "cors").
* `responseType` : The expected response type for the request (e.g., "text", "json").
* `script` : An object containing script-related information for the request.
* `signal` : A signal object used to abort the request.
* `url` : The URL of the request.
* `vars` : An object containing any variables associated with the request. *See [variables](../../script/vars)*.
## Headers
The `headers` sub-object of the `req` object contains key-value pairs representing the HTTP headers associated with the request.
```javascript theme={null}
// Example usage
console.log(req.headers);
/* Output: {
authorization: 'Bearer ',
'content-type': 'application/json',
accept: 'application/json',
// Add more headers as needed...
} */
```
## Method
The `method` property of the `req` object specifies the HTTP method used for the request. Common HTTP methods include "GET", "POST", "PUT", "DELETE", etc. The method indicates the type of action the request wishes to perform on the resource. The value of the `method` property should be a string representing the desired HTTP method for the request.
```javascript theme={null}
// Example usage
console.log(req.method); // Output: "GET"
```
## URL
The `url` property of the `req` object represents the Uniform Resource Locator (URL) of the request. It specifies the address of the resource being requested by the client. Variables enclosed within double curly braces (`{{...}}`) in the URL string are placeholders that may be replaced with actual values at runtime. These variables are not directly visible within the URL string and are typically encapsulated during request processing.
```javascript theme={null}
// Example usage
console.log(req.url); // Output: "{{base.url}}/users/2?queryTest=queryResult"
```
## Example Usage
```javascript theme={null}
// Example request object
const req = {
assertions: [],
auth: { username: 'myUsername', password: 'mySuperPassword' },
headers: {
authorization: 'Bearer ',
'content-type': 'application/json',
accept: 'application/json',
// Add more headers as needed...
},
method: 'GET',
mode: 'none',
responseType: 'arraybuffer',
script: {
req: "// Create an array of objects\nconst data = [\n { i…q);\nconst myVariable = bru.getEnvVar('password');"
},
signal: {},
url: '{{base.url}}/users/2?queryTest=queryResult',
vars: {}
};
// Accessing request properties
console.log(req.method); // Output: "GET"
console.log(req.url); // Output: "{{base.url}}/users/2?queryTest=queryResult"
console.log(req.headers.authorization); // Output: "Bearer "
console.log(req.auth.username); // Output: "myUsername"
```
# Sync requests
Source: https://docs.usebruno.com/testing/script/request/sync-requests
You can make synchronous requests in your pre/post scripts. By synchronous, we mean that you can await a request in your scripting code.
Below is an inbuilt example of using `axios` library to
```js theme={null}
const axios = require("axios");
const response = await axios.get("https://api.github.com/users/usebruno");
bru.setVar("avatarUrl", response.data.avatar_url);
```
**Example:**
# Response Object
Source: https://docs.usebruno.com/testing/script/response/response-object
The `res` object that is available inside the [vars](../vars), [assertions](../../tests/assertions), [scripting](../getting-started) and [testing](../../tests/introduction)
contexts can be used to extract values from the response body, headers and status.
*Note that the `res` object is only available in the context of a request.*
You can also access it with [response queries](./response-query).
## Object Structure
The `res` object has the following properties:
* `body`: Represents the response body containing data returned to the client.
* `headers`: Contains key-value pairs representing HTTP headers associated with the response.
* `status`: Represents the HTTP status code indicating the outcome of the request.
## Property Descriptions
### `body`
The `body` property of the `res` object contains the response data sent to the client. It can be a string, an object, or a stream, depending on the application's needs.
### `headers`
The `headers` property contains HTTP headers associated with the response. These headers provide metadata about the response, such as content type, encoding, and caching directives.
### `status`
The `status` property represents the HTTP status code of the response. It indicates the outcome of the request, such as success, redirection, client error, or server error.
## Example Usage
```javascript theme={null}
// Example response object
const res = {
body: '{"message": "Hello, world!"}',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
status: 200,
};
// Accessing response properties
console.log(res.body); // Output: '{"message": "Hello, world!"}'
console.log(res.headers['Content-Type']); // Output: 'application/json'
console.log(res.status); // Output: 200
```
# Response Query
Source: https://docs.usebruno.com/testing/script/response/response-query
The `res` object that is available inside the [vars](../vars), [assertions](../../tests/assertions), [scripting](../getting-started) and [testing](../../tests/introduction) contexts can be queried for data by invoking it like below.
Think of it as `lodash.get()` on steroids
```javascript theme={null}
res('order.total')
```
## Examples
```javascript theme={null}
const data = {
customer: {
address: {
city: "bangalore"
},
orders: [
{
id: "order-1",
items: [
{ id: 1, amount: 10 },
{ id: 2, amount: 20 }
]
},
{
id: "order-2",
items: [
{ id: 3, amount: 30 },
{ id: 4, amount: 40 }
]
}
]
},
};
```
| Query | Output | |
| --------------------------------------- | ----------------------------------- | - |
| res("customer.address.city") | bangalore | |
| res("customer.orders.items.amount") | \[10, 20, 30, 40] | |
| res("customer.orders.items.amount\[0]") | 10 | |
| res("..items.amount") | \[10, 20, 30, 40] | |
| res("..amount") | \[10, 20, 30, 40] | |
| res("..items.amount\[0]") | 10 | |
| res("..items\[0].amount") | 10 | |
| res("..items\[5].amount") | undefined | |
| res("..id") | \["order-1", 1, 2, "order-2", 3, 4] | |
| res("customer.orders.foo") | undefined | |
| res("..customer.foo") | undefined | |
| res("..address") | \[\{ city: "bangalore" }] | |
| res("..address\[0]") | \{ city: "bangalore" } | |
## API
### Standard dot notation
**Example:**
```javascript theme={null}
res('customer.orders.items.amount')
```
### Deep navigation .. double dots
**Example:**
```javascript theme={null}
res('..items.amount')
```
### Array indexing
**Example:**
```javascript theme={null}
res('..items[0].amount')
```
### Array filtering \[?] with corresponding filter function
**Example:**
```javascript theme={null}
res('..items[?].amount', i => i.amount > 20)
```
### Array mapping \[?] with corresponding mapper function
**Example:**
```javascript theme={null}
res('..items..amount[?]', amt => amt + 10)
```
# Script Flow in Bruno
Source: https://docs.usebruno.com/testing/script/script-flow
In Bruno, we provide two types of script flows: **Sandwich** and **Sequential (Natural)**. These flows control the order in which the scripts at various levels of your API test collections are executed, allowing for flexibility and control over your automation.
## Flow Types
1. Sandwich Flow
2. Sequential (Natural) Flow
### 1. Sandwich Flow
In the **Sandwich Flow**, scripts are executed in the following order:
```bash theme={null}
sandwich-flow/
├── Collection Pre Script
│ └── Folder Pre Script
│ └── Request Pre Script
└── Request Post Script
└── Folder Post Script
└── Collection Post Script
```
This order allows you to execute actions at multiple levels (collection, folder, and request) before and after each test. It is useful when you need to configure different environments or states before and after running tests at various levels.
### 2. Sequential (Natural) Flow
In the **Sequential Flow**, scripts are executed in this order:
```bash theme={null}
sequential-flow/
├── Collection Pre Script
│ └── Folder Pre Script
│ └── Request Pre Script
└── Collection Post Script
└── Folder Post Script
└── Request Post Script
```
This flow executes scripts in a more natural, linear order. After the **Request Pre Script**, it runs the **Collection Post Script**, followed by **Folder Post Script**, and finally, the **Request Post Script**. This flow may be better suited when you want a clean execution from top to bottom.
If the `flow` property is not specified, Sandwich is used by default.
You can set the flow type (either `sandwich` or `sequential`) in your collection configuration file:
```yaml filename="opencollection.yml" theme={null}
extensions:
bruno:
scripts:
flow: sequential # Or "sandwich"
```
```json filename="bruno.json" theme={null}
{
"scripts": {
"moduleWhitelist": ["crypto", "buffer", "form-data"],
"filesystemAccess": {
"allow": true
},
"flow": "sequential"
}
}
```
# Vars tab
Source: https://docs.usebruno.com/testing/script/vars
The **Vars** tab in Bruno allows you to manage variables at different stages of your request lifecycle. You can set and use variables:
* Before the request (Pre Request)
* After receiving the response (Post Response)
Variables are scoped within the request and cannot be accessed outside of it. This ensures clean separation between different requests.
## Pre Request Vars
In the **Vars** tab > **Pre Request** variables section, you can write any strings, numbers or any valid JavaScript literal.
Example:
## Post Response Vars
In the **Vars** tab > **Post Response** variables section, you can write any valid JavaScript expression. The `res` object is available, allowing you to declaratively parse the [response object](/testing/script/response/response-object) and set variables, instead of writing scripts to do the same.
Example:
For parsing the response, you can checkout the [response query](/testing/script/response/response-query) that allows you to easily query your response.
# Assertions
Source: https://docs.usebruno.com/testing/tests/assertions
Assertions allow you to declaratively write tests without writing any code.
## Getting Started
1. Open a request and click on the **Assert** tab
2. Add assertions by specifying:
* **Expression**: The value to test (e.g., `res.status`, `res.body.id`)
* **Operator**: The comparison operator
* **Value**: The expected value
## Common Examples
### Basic Response Testing
| Expression | Operator | Value |
| ------------------ | ------------ | --------- |
| `res.status` | `equals` | `200` |
| `res.body.status` | `equals` | `success` |
| `res.body.message` | `contains` | `created` |
| `res.body.id` | `isNotEmpty` | |
### Nested Objects
```json theme={null}
{
"user": {
"profile": {
"name": "John",
"email": "john@example.com"
}
}
}
```
| Expression | Operator | Value |
| ----------------------------- | ---------- | -------------- |
| `res.body.user.profile.name` | `equals` | `John` |
| `res.body.user.profile.email` | `contains` | `@example.com` |
### Arrays
```json theme={null}
{
"users": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
}
```
| Expression | Operator | Value |
| ------------------------ | ------------ | ------- |
| `res.body.users` | `isNotEmpty` | |
| `res.body.users[0].name` | `equals` | `Alice` |
| `res.body.users[1].id` | `equals` | `2` |
### Using res() Query
For complex nested data, use the `res()` function:
```json theme={null}
{
"order": {
"items": [
{ "id": 1, "price": 29.99 },
{ "id": 2, "price": 69.99 }
]
}
}
```
| Expression | Operator | Value |
| ----------------------------- | ------------ | ------- |
| `res('order.items[0].price')` | `equals` | `29.99` |
| `res('..price')` | `isNotEmpty` | |
Learn more about `res()` in [Response Query](../script/response/response-query) docs.
### Response Headers
| Expression | Operator | Value |
| ----------------------------- | ---------- | ------------------ |
| `res.headers['content-type']` | `contains` | `application/json` |
### Response Time
| Expression | Operator | Value |
| ------------------ | -------- | ------ |
| `res.responseTime` | `lt` | `1000` |
## Available Operators
* **Comparison**: `equals`, `notEquals`, `gt`, `gte`, `lt`, `lte`
* **String**: `contains`, `notContains`, `startsWith`, `endsWith`, `matches`, `notMatches`
* **Type Checks**: `isNull`, `isNotEmpty`, `isEmpty`, `isDefined`, `isUndefined`
* **Value Checks**: `isTruthy`, `isFalsy`, `isNumber`, `isString`, `isBoolean`, `isArray`, `isJson`
* **Other**: `in`, `notIn`, `between`, `length`
## Advanced Testing
For complex test scenarios with custom logic, use [Test Scripts](./introduction) with JavaScript and Chai assertions.
# Testing
Source: https://docs.usebruno.com/testing/tests/introduction
Bruno uses the [Chai library ↗](https://www.chaijs.com/) for assertions. All Chai expect syntax works in Bruno tests.
Write JavaScript test scripts to validate API responses, handle complex logic, and automate testing workflows.
## Basic Test Structure
```javascript theme={null}
test("test name", function () {
expect(res.getStatus()).to.equal(200);
});
```
## Common Test Examples
### Testing Status Codes
```javascript theme={null}
test("should return success", function () {
expect(res.getStatus()).to.equal(200);
});
test("should not return server error", function () {
expect(res.getStatus()).to.not.equal(500);
});
```
### Testing Response Body
```javascript theme={null}
test("should return user data", function () {
const body = res.getBody();
expect(body).to.have.property("id");
expect(body.name).to.equal("John Doe");
expect(body.email).to.contain("@example.com");
});
test("should return array of users", function () {
const users = res.getBody();
expect(users).to.be.an("array");
expect(users).to.have.lengthOf(3);
expect(users[0]).to.have.property("id");
});
```
### Validating the JSON Body with `jsonBody`
Bruno provides a custom Chai assertion `jsonBody` to make response-body checks more readable. It's the native Bruno equivalent of Postman's `pm.response.to.have.jsonBody(...)`, and supports four call shapes:
```javascript theme={null}
test("body is JSON", function () {
expect(res.getBody()).to.have.jsonBody();
});
test("body deep-equals an object", function () {
expect(res.getBody()).to.have.jsonBody({ id: 1, name: "Alice" });
});
test("a nested path exists", function () {
expect(res.getBody()).to.have.jsonBody("user.id");
});
test("a nested path equals a value", function () {
expect(res.getBody()).to.have.jsonBody("user.id", 123);
});
```
Negation is supported across all variants:
```javascript theme={null}
test("body is not the legacy shape", function () {
expect(res.getBody()).to.not.have.jsonBody({ legacy: true });
});
test("debug field is not exposed", function () {
expect(res.getBody()).to.not.have.jsonBody("internal.debugInfo");
});
```
**Migrating from Postman?** When you import a Postman collection, Bruno automatically translates `pm.response.to.have.jsonBody(...)` (and `pm.response.to.not.have.jsonBody(...)`) into `expect(res.getBody()).to.have.jsonBody(...)`. No manual rewriting needed.
### Validating against a JSON Schema with `jsonSchema`
For contract testing, use the `jsonSchema` assertion to validate a response against a [JSON Schema](https://json-schema.org/) document. Bruno uses [Ajv](https://ajv.js.org/) under the hood, so all modern JSON Schema drafts are supported (**04, 06, 07, 2019-09, 2020-12**) along with the full [ajv-formats](https://ajv.js.org/packages/ajv-formats.html) library.
```javascript theme={null}
test("response matches the user schema", function () {
const userSchema = {
type: "object",
required: ["id", "email"],
properties: {
id: { type: "integer" },
email: { type: "string", format: "email" },
createdAt: { type: "string", format: "date-time" },
website: { type: "string", format: "uri" }
}
};
expect(res.getBody()).to.have.jsonSchema(userSchema);
});
```
#### Built-in formats
Common `ajv-formats` validators are available out of the box, including `email`, `date`, `date-time`, `time`, `uri`, `uri-reference`, `uuid`, `ipv4`, `ipv6`, `hostname`, and `regex`.
```javascript theme={null}
test("user has a valid email and signup date", function () {
expect(res.getBody()).to.have.jsonSchema({
type: "object",
properties: {
email: { type: "string", format: "email" },
signupDate: { type: "string", format: "date-time" }
}
});
});
```
#### Passing Ajv options
You can pass any [Ajv options](https://ajv.js.org/options.html) as a second argument — useful, for example, to collect every validation error rather than failing on the first one:
```javascript theme={null}
test("collect every schema violation", function () {
expect(res.getBody()).to.have.jsonSchema(userSchema, { allErrors: true });
});
```
#### Negation
```javascript theme={null}
test("response should not match the legacy v1 schema", function () {
expect(res.getBody()).to.not.have.jsonSchema(legacyV1Schema);
});
```
Like `jsonBody`, the `jsonSchema` assertion runs in both the **Safe Mode** and the **Developer Mode**, and Postman's `pm.response.to.have.jsonSchema(schema)` is automatically translated to `expect(res.getBody()).to.have.jsonSchema(schema)` on import.
Earlier versions of Bruno used `tv4` for schema validation, which only supports JSON Schema **draft-04**. The new Ajv-based `jsonSchema` assertion is the recommended way to validate schemas going forward and supports drafts **04, 06, 07, 2019-09, and 2020-12**.
### Testing Nested Objects
```javascript theme={null}
test("should validate nested user profile", function () {
const body = res.getBody();
expect(body.user.profile.name).to.equal("Alice");
expect(body.user.settings.theme).to.equal("dark");
expect(body.user.settings.notifications).to.be.true;
});
```
### Testing with Conditional Logic
```javascript theme={null}
test("should validate response based on status", function () {
const status = res.getStatus();
const body = res.getBody();
if (status === 200) {
expect(body).to.have.property("data");
expect(body.data).to.not.be.empty;
} else if (status === 404) {
expect(body).to.have.property("error");
expect(body.error.message).to.contain("not found");
} else {
throw new Error(`Unexpected status code: ${status}`);
}
});
test("should validate user role permissions", function () {
const body = res.getBody();
if (body.user.role === "admin") {
expect(body.user.permissions).to.include("write");
expect(body.user.permissions).to.include("delete");
} else if (body.user.role === "user") {
expect(body.user.permissions).to.include("read");
expect(body.user.permissions).to.not.include("delete");
}
});
```
### Testing Arrays and Loops
```javascript theme={null}
test("should validate all users have required fields", function () {
const users = res.getBody();
users.forEach((user) => {
expect(user).to.have.property("id");
expect(user).to.have.property("email");
expect(user.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
});
test("should find user by id", function () {
const users = res.getBody();
const targetUser = users.find((u) => u.id === 123);
expect(targetUser).to.exist;
expect(targetUser.name).to.equal("John");
});
```
### Testing Response Headers
```javascript theme={null}
test("should have correct content type", function () {
expect(res.getHeader("content-type")).to.contain("application/json");
});
test("should include authentication headers", function () {
expect(res.getHeader("x-api-key")).to.exist;
expect(res.getHeader("authorization")).to.not.be.empty;
});
```
### Testing Response Time
```javascript theme={null}
test("should respond quickly", function () {
expect(res.getResponseTime()).to.be.lessThan(1000);
});
```
### Advanced: Saving Values for Next Request
```javascript theme={null}
test("should save token for next request", function () {
const body = res.getBody();
expect(body).to.have.property("token");
expect(body.token).to.not.be.empty;
// Save token to environment variable
bru.setVar("authToken", body.token);
});
test("should extract and save user ID", function () {
const body = res.getBody();
if (body.users && body.users.length > 0) {
const firstUserId = body.users[0].id;
bru.setVar("userId", firstUserId);
}
});
```
### Error Handling
```javascript theme={null}
test("should handle missing fields gracefully", function () {
const body = res.getBody();
if (!body.data) {
expect(body).to.have.property("error");
expect(body.error.code).to.be.oneOf([400, 404, 422]);
}
});
test("should validate error response structure", function () {
const status = res.getStatus();
if (status >= 400) {
const body = res.getBody();
expect(body).to.have.property("error");
expect(body.error).to.have.property("message");
expect(body.error.message).to.be.a("string");
}
});
```
## Common Chai Assertions
```javascript theme={null}
// Equality
expect(value).to.equal(expected);
expect(value).to.not.equal(expected);
expect(value).to.eql(expected); // deep equality
// Type checking
expect(value).to.be.a("string");
expect(value).to.be.an("array");
expect(value).to.be.true;
expect(value).to.be.null;
// Property checks
expect(obj).to.have.property("key");
expect(obj).to.have.all.keys("name", "email");
// String checks
expect(str).to.contain("substring");
expect(str).to.match(/regex/);
// Number comparisons
expect(num).to.be.above(10);
expect(num).to.be.below(100);
expect(num).to.be.within(10, 100);
// Array checks
expect(arr).to.be.an("array");
expect(arr).to.have.lengthOf(3);
expect(arr).to.include("item");
expect(arr).to.be.empty;
// Bruno-specific: JSON body and schema validation
expect(res.getBody()).to.have.jsonBody(); // body is JSON
expect(res.getBody()).to.have.jsonBody({ id: 1 }); // deep-equals
expect(res.getBody()).to.have.jsonBody("user.id"); // path exists
expect(res.getBody()).to.have.jsonBody("user.id", 123); // path equals value
expect(res.getBody()).to.have.jsonSchema(schema); // validate via Ajv
expect(res.getBody()).to.have.jsonSchema(schema, { allErrors: true });
```
## Next Steps
For more advanced scripting capabilities, see:
* [Scripting API](../script/javascript-reference) - Full API reference
* [Response Query](../script/response/response-query) - Query complex response data
* [Variables](../script/vars) - Work with variables across requests
# Collection Variables
Source: https://docs.usebruno.com/variables/collection-variables
Collection variables are variables that are scoped to a specific collection. They are useful for storing variables that are used across multiple requests in a collection.
## Creating a Collection Variable
* Go to Collection Settings
* Go to the Variables tab
* Add your variable name, value in the Pre Request Vars section
* Save your changes
## Using a Collection Variable
You can use the `{{varName}}` syntax to use a collection variable in a request.
#### Access Collection Variable inside scripts
You can also use the `bru.getCollectionVar()` function to get the value of a runtime variable and use it inside your pre request scripts.
```javascript theme={null}
let namespace = bru.getCollectionVar("namespace");
```
## Import/Export Collection Variables
For detailed instructions on importing and exporting collection environment variables, see the [Import/Export Environments](/import-export-data/import-export-environments) guide.
# Environment Variables
Source: https://docs.usebruno.com/variables/environment-variables
Environment variables are variables that are scoped to an environment, such as local development or production. They are useful for storing variables that can be re-used across the collection that are different depending on the environment. An example would be the host url of the server you want to test.
## Creating an Environment Variable
1. Go to environments (top right) and press **No environment**.
2. If there are no environments, you will be prompted to create one.
3. Add your variable name and value for the specific environment.
4. Save your changes.
## Using an Environment Variable
Just like other variables you can use the `{{varName}}` syntax to use an environment variable in a request. On the top-right, you can see which environment you are currently using and select a different one.
## Searching environment variables
When working with a large number of environment variables, you can use the search feature to quickly find specific variables.
1. Open your environment settings.
2. Click the search icon in the environment details panel.
3. Type your query to filter variables by name or value.
The search filters the variable list in real time. If you click on a variable row while searching, that row stays visible even if it no longer matches the query, so you can edit it without losing your place. The row unpins automatically after a short delay.
The search feature is also available for [global environment variables](/variables/global-environment-variables).
## Using the ./environment directory
Environment variables are synced with the `/environments` directory inside your collection. You can also create and manage environments there.
Each environment is saved in a `.bru` file, which looks like this:
```bash filename="local.bru" theme={null}
vars {
host: http://localhost:8787
}
```
For information on importing and exporting environment variables, see the [Import/Export Environments](/import-export-data/import-export-environments) guide.
# Folder Variables
Source: https://docs.usebruno.com/variables/folder-variables
Folder variables are variables that are scoped to a specific folder. They are useful for storing variables that are used across multiple requests in a folder.
## Creating a Folder Variable
* Go to Folder Settings (can be accessed from dropdown menu of the folder in the sidebar)
* Go to Vars Tab
* Add your variable name, value in the Pre Request Vars section
* Save your changes
## Using a Folder Variable
You can use the `{{varName}}` syntax to use a folder variable in a request.
#### Access Folder Variable inside scripts
You can also use the `bru.getFolderVar()` function to get the value of a folder variable and use it inside your scripts.
```javascript theme={null}
let bearerToken = bru.getFolderVar("bearerToken");
```
# Global Environment Variables
Source: https://docs.usebruno.com/variables/global-environment-variables
Global environment variables can be used across all collections and requests within a workspace. Think of them as a global store accessible throughout the app, eliminating the repetitive task of defining the same variables in every collection.
**Bruno 3.0.0+**: Global environments are now stored within each [workspace](/bruno-basics/create-a-workspace) in the `environments/` folder, making them version-controllable and workspace-scoped.
**Bruno 2.x and earlier**: Global environments are stored in the application's data folder. You can access them by visiting the About section from Preferences and navigating to the AppData path location.
## Storage Location
### Bruno 3.0.0+ (Workspace-based)
In Bruno 3.0.0 and later, global environments are stored as `.yml` files in the `environments/` directory within your workspace:
```
my-workspace/
├── workspace.yml
├── collections/
└── environments/
├── development.yml
├── staging.yml
└── production.yml
```
Each environment file contains the environment name and variables:
```yaml filename="development.yml" theme={null}
name: development
variables:
- name: api_url
value: http://localhost:3000
enabled: true
secret: false
type: text
```
This structure provides several benefits:
* **Version Control** - Global environments can be committed to Git alongside your workspace
* **Team Collaboration** - Share environments with team members through your repository
* **Workspace Isolation** - Each workspace maintains its own set of global environments
### Bruno 2.x and Earlier (Application-based)
In earlier versions, global environments are stored in Bruno's application data folder and are shared across all collections in the application.
## Create a Global Environment Variable
1. Go to the **Environments** section (top right corner).
2. Navigate to the **Global** tab and click on **Create**.
3. (Optional) If you need to import an existing environment, you can use the **Import Environment** option.
4. Enter a name for your environment and click **Create**.
5. Add a variable by specifying the **Name** and **Value**.
6. Click **Save**.
Your global environment variable is now created and can be accessed across your workspace.
You can click the secret checkbox to hide the values of variables while hovering over them. Secret values are stored securely and are not saved to the environment file.
## Using Global Environment Variables
To use a global environment variable, reference it using the format `{{var_name}}` in any request or collection. These variables can be combined with other variables and used throughout your workspace, making them ideal for consistent value sharing.
## Selecting a Global Environment
To activate a global environment:
1. Click on the **Environments** dropdown in the top right corner
2. Navigate to the **Global** tab
3. Select the environment you want to activate
The active global environment selection is stored per-workspace in the application's internal settings. This means your environment selection persists across app restarts and is maintained independently for each workspace.
## Upgrading from Bruno 2.x to 3.0.0
When you upgrade to Bruno 3.0.0, your existing global environments from the application data folder can be migrated to the Default Workspace automatically. Custom workspaces will start with no global environments.
If you have existing global environments in Bruno 2.x:
1. **Default Workspace** - Bruno will migrate your existing global environments to the Default Workspace's `environments/` folder
2. **Custom Workspaces** - You will need to manually recreate or import global environments for any new custom workspaces you create
3. **Export/Import** - You can export environments from one workspace and import them into another
## Import/Export Global Environment Variables
For detailed instructions on importing and exporting global environment variables, see the [Import/Export Environments](/import-export-data/import-export-environments) guide.
# Variable Interpolation
Source: https://docs.usebruno.com/variables/interpolation
Variable interpolation in Bruno allows you to use variables in your requests using the `{{variableName}}` syntax. Let's explore how interpolation works with different data types.
## Basic Interpolation
### Strings
```javascript theme={null}
// Setting a string variable
bru.setVar("greeting", "Hello World");
```
```http theme={null}
// Using in request
GET http://api.example.com/{{greeting}}
// Interpolates to: http://api.example.com/Hello World
```
### Numbers
```javascript theme={null}
// Setting a number variable
bru.setVar("userId", 123);
```
```http theme={null}
// Using in request
GET http://api.example.com/users/{{userId}}
// Interpolates to: http://api.example.com/users/123
```
### Booleans
```javascript theme={null}
// Setting a boolean variable
bru.setVar("isActive", true);
// Using in request body
{
"active": {{isActive}}
}
// Interpolates to: { "active": true }
```
## Object Interpolation
Object interpolation is available from Bruno v2.2.0 onwards.
You can access object properties using dot notation. Here's how to work with objects containing different data types:
```javascript theme={null}
// Define your object with multiple data types
const userProfile = {
username: "john_doe", // string
accountId: 12345, // number
isVerified: true, // boolean
preferences: { // nested object
theme: "dark",
notifications: true
}
};
// Set the object as a variable
bru.setVar("user", userProfile);
// Using in request body
{
"username": "{{user.username}}",
"verified": {{user.isVerified}},
"preferences": {{user.preferences}},
}
// Interpolates to:
// {
// "username": "john_doe",
// "verified": true,
// "preferences": {
// "theme": "dark",
// "notifications": true
// }
// }
```
## Array Interpolation
Array interpolation is available from Bruno v2.2.0 onwards.
```javascript theme={null}
// Define your arrays
const technologies = ["REST", "GraphQL", "gRPC"];
const settings = [
{ port: 3000, env: "dev" },
{ port: 8080, env: "prod" }
];
// Set arrays as variables
bru.setVar("apiTypes", technologies);
bru.setVar("configs", settings);
// Using array elements in request
{
// Simple array access
"primaryAPI": "{{apiTypes[0]}}", // "REST"
"alternativeAPI": {{apiTypes}},
// Accessing object properties in arrays
"devPort": {{configs[0].port}}, // 3000
"prodEnv": {{configs}},
// Using multiple array elements
"supported": {
"apis": ["{{apiTypes[0]}}", "{{apiTypes[1]}}"],
}
}
// Interpolates to:
// {
// "primaryAPI": "REST",
// "alternativeAPI": [
// "REST",
// "GraphQL",
// "gRPC"
// ]
// "devPort": 3000,
// "prodEnv": [
// {
// "port": 3000,
// "env": "dev"
// },
// {
// "port": 8080,
// "env": "prod"
// }
// ]
// "supported": {
// "apis": ["REST", "GraphQL"],
// }
// }
```
## Date Interpolation
```javascript theme={null}
// Bruno v2.2.0+
bru.setVar("timestamp", new Date());
// Using in request
{
"createdAt": "{{timestamp}}"
}
// Interpolates to:
// {
// "createdAt": "2025-04-23T13:57:56.341Z"
// }
```
## Practical Examples
### API Authentication
```javascript theme={null}
bru.setVar("authConfig", {
apiKey: "your-api-key",
secret: "your-secret"
});
// In request headers
headers {
"X-API-Key": "{{authConfig.apiKey}}",
"X-Secret": "{{authConfig.secret}}"
}
```
### Dynamic Query Parameters
```javascript theme={null}
bru.setVar("searchParams", {
limit: 10,
offset: 0,
filter: "active"
});
```
```http theme={null}
// In URL
GET http://api.example.com/users?limit={{searchParams.limit}}&offset={{searchParams.offset}}&filter={{searchParams.filter}}
```
### Request Body with Mixed Types
```javascript theme={null}
bru.setVar("product", {
name: "Bruno Pro",
price: 99.99,
features: ["Git Integration", "Offline First"],
metadata: {
version: "2.2.0",
released: new Date()
}
});
// In request body
{
"productName": "{{product.name}}",
"price": {{product.price}},
"firstFeature": "{{product.features[0]}}",
"releaseDate": "{{product.metadata.released}}"
}
```
Remember that variables set using `bru.setVar()` are available throughout your collection's scope. Use them to make your requests more dynamic and maintainable.
# Variables
Source: https://docs.usebruno.com/variables/overview
### Overview
Variables in the Bruno allow you to store dynamic values that can be reused across multiple API requests, environments, and workflows. This feature enhances flexibility, maintainability, and efficiency by enabling you to manage frequently changing data points such as tokens, environment-specific URLs, or user-defined values in one place.
### Types
There are 7 types of variables you can create:
* [Global Environments Variables](./global-environment-variables)
* [Environment Variables](./environment-variables)
* [Collection Variables](./collection-variables)
* [Folder Variables](./folder-variables)
* [Request Variables](./request-variables)
* [Runtime Variables](./runtime-variables)
* [Prompt Variables](./prompt-variables)
Additionally, Process Environment Variables can be defined in an external environment configuration file:
* [Process Environment Variables](./process-env)
### Variable Precedence and Scope
When a variable is accessed, the following precedence is used to determine which value is used:
↑ Higher Precedence
↓ Broader Scope
Runtime Variables
Request Variables
Folder Variables
Environment Variables
Collection Variables
Global Variables
* Runtime variables get the highest precedence.
* Prompt Variables are defined using the `{{?Prompt String}}` syntax and hence don't compete with the above.
* Process Environment Variables are accessed using the `{{process.env.VAR_NAME}}` syntax and hence don't compete with the above.
### Variable Storage
Each variable has its own storage location either within your collection file or within the app's memory. **All storage is local.**
| Variable Type | Storage Location |
| ------------------- | ----------------------- |
| Collection | `.bru` |
| Folder | `.bru` |
| Request | `.bru` |
| Environment | `.bru` |
| Runtime | Local storage |
| Global | Local storage |
| Prompt | Never stored |
| Process Environment | Separate `.env` file |
### Environment Color Coding
Environment color coding is now available (v3.1.0+) for Environment Variables and Global Environment Variables, making it easier to visually identify and distinguish between different environments.
Bruno allows you to customize the visual appearance of your environments using color coding. This feature helps you quickly identify which environment you're currently working with, reducing the risk of accidentally running requests against the wrong environment.
#### Color Customization
You can assign a custom color to each environment:
1. Navigate to environment settings (top right corner)
2. Click the brush icon next to the environment and select a color from the color picker component
3. If no color is selected, a default color will be assigned
#### Color-Coded Visual Indicators
Once colors are assigned, they appear in multiple places throughout the interface:
**Environment Selector Dropdown**: A small color swatch is displayed next to each environment name, making it easy to distinguish between environments at a glance.
**Active Environment Indicator**: The currently selected environment's color is prominently displayed in the environment pill in the tabs UI, providing constant visual feedback about which environment you're working with.
This visual system helps prevent mistakes when switching between development, staging, and production environments, especially in fast-paced workflows.
### Variable Data Type
All variables are stored as strings. Bruno does not infer or change the data type of the variable based on the value you set.
### Debugging Variables in Console
You can access and debug variables in the console using the following pattern:
The pattern follows `bru.get[Type]Var(key)` where:
* `[Type]` is the variable type (Runtime, Request, Folder, etc.)
* `key` is the variable name you want to access
#### Example:
```javascript theme={null}
// Basic syntax: console.log(bru.get[Type]Var(key))
console.log(bru.getVar('myVar')) // Runtime variables
```
### Scripting API
Please see the [Scripting API](/scripting/javascript-reference#collection-variables) for more information on how to access variables in your scripts.
Prompt variables are not accessible via the Scripting API.
# Process Environment Variables
Source: https://docs.usebruno.com/variables/process-env
## Overview
Process environment variables are used to store sensitive information, such as API keys, passwords, and other secret values. These values are typically stored in a .env file. For more details on secret management, refer to the [secret management](/secrets-management/dotenv-file) section.
### Creating a Process Environment Variables
To create process environment variables, you can add a .env file to the root of your Bruno collection. This file will store your secrets, which can then be accessed throughout the collection.
#### Example
In your Bruno collection, the structure might look like this:
In the `.env` file, you would store a key-value pair, like this:
```bash showLineNumbers filename=".env" theme={null}
authkey = anc123
```
### Using the Process Environment Variables
To access the values stored in the `.env` file, you can use the `process.env` global object. For example, if you want to retrieve the key value from your `.env` file, you would use:
You can use `process.env.` throughout your Bruno collection to securely manage and access your environment variables.
Starting from Bruno **v3.1.0**, you can create and manage `.env` files directly inside Bruno at workspace level. Check the [Secret Management - Dotenv File](/secrets-management/dotenv-file) section for more details.
# Prompt Variables
Source: https://docs.usebruno.com/variables/prompt-variables
## Overview
Prompt variables are **ephemeral variables** in Bruno that allow you to provide values interactively at the time a request is executed. Unlike other variable types, prompt variables do not have predefined values and are not persisted. They exist only for the duration of a single request execution.
Prompt variables are ideal for:
* Sensitive data you don't want to save in your collection
* Ad-hoc values that change frequently
* Scenarios where you want to manually provide input before sending a request
## Syntax
Prompt variables use the following syntax:
`{{?Prompt Message}}`
* The `?` indicates this is a prompt variable
* The text after `?` is the message shown to you in the prompt dialog
Prompt variables are highlighted in blue in the Bruno UI.
## Using Prompt Variables
You can use prompt variables anywhere markup variables are supported in Bruno, including:
* URLs and query parameters
* Headers
* Request bodies
* Authentication fields
### How It Works
When you execute a request containing prompt variables, Bruno displays a modal dialog listing all prompt variables with their prompt messages. You provide values for each variable before the request is sent.
**Example:**
If your request body contains a prompt variable like `{{?Prompt Var}}`:
```json theme={null}
{
"title": "{{?Prompt Var}}"
}
```
Bruno will display a prompt dialog asking for "Prompt Var" before sending the request.
### Deduplication
If you use the same prompt variable (like `{{?Username}}`) multiple times in a request, Bruno will only prompt you once and substitute the value everywhere it appears.
**Example:**
```json theme={null}
{
"user": "{{?Username}}",
"profile": {
"name": "{{?Username}}"
}
}
```
You will only be prompted for "Username" once, and the value will be used in both locations.
### Edge Cases
**Empty Values:**\
If you leave a prompt variable blank (without entering any value), it will be replaced with an empty string in the request.
**Canceling the Prompt:**\
If you cancel the prompt dialog, the request will not be sent.
## Behavior in Automated Environments
When running collections via **Bruno CLI** or the **Collection Runner**, any requests containing prompt variables will be **skipped** and marked as `skipped` in the output. This is because these automated environments do not support interactive prompting.
## Viewing Prompt Variables
Prompt variables are ephemeral and only exist during request execution. They are not displayed in the variables viewer and are not accessible in scripts (pre-request or post-response scripts).
# Request Variables
Source: https://docs.usebruno.com/variables/request-variables
Request variables are variables that are scoped to a specific request. They are useful for storing variables that is used in a specific request.
## Creating a Request Variable
* Go to the Request
* Go to Vars Tab
* Add your variable name, value in the Pre Request Vars section
* Save your changes
## Using a Request Variable
You can use the `{{varName}}` syntax to use a request variable in a request.
#### Access Request Variable inside scripts
You can also use the `bru.getRequestVar()` function to get the value of a request variable and use it inside your scripts.
```javascript theme={null}
let source = bru.getRequestVar("source");
let destination = bru.getRequestVar("destination");
```
# Runtime Variables
Source: https://docs.usebruno.com/variables/runtime-variables
## Overview
Runtime variables are **ephemeral variables** that exist only during the runtime of the API client. These variables are scoped within a collection, meaning they are accessible to all requests within that collection. However, once the API client is closed or restarted, the values of these variables are lost. They are ideal for managing temporary data such as session tokens, dynamic parameters, or other values that are generated or updated during the execution of requests.
### Creating a Runtime Variable
To create a runtime variable, you can use the `bru.setVar(name, value)` function. This function allows you to store any value (e.g., tokens, response data, temporary user inputs) as a runtime variable.
#### Example:
Suppose you receive an authentication token as part of a response body and want to reuse it in subsequent requests. You can capture it using the `bru.setVar()` function in your post response script.
```javascript theme={null}
bru.setVar("token", res.body.token);
```
### Using a Runtime Variable
Once a runtime variable is created, you can use it in subsequent API requests by referencing it with the `{{variableName}}`syntax. The variable's value will be dynamically injected into the request at runtime.
#### Example:
If you’ve stored a token variable using `bru.setVar()`, you can use it in headers, query parameters, body, or URL of subsequent requests like this:
```plaintext theme={null}
Authorization: Bearer {{token}}
```
You can also use the `bru.getVar()` function to get the value of a runtime variable and use it inside your pre request scripts.
```javascript theme={null}
const token = bru.getVar("token");
req.setHeader("Authorization", `Bearer ${token}`);
```
### Viewing Runtime Variables
You can view all runtime variables by clicking on the **Eye** icon located in the top-right corner of the navigation bar
The request must be executed before you can view the runtime variables.
Example:
Consider the following script inside Bruno, which creates a runtime variable with a `key` and value `pair`. In this case, the key is *hey* and the value is *Bruno*.
```js theme={null}
bru.setVar("hey", "Bruno");
```
Once the request is executed, the runtime variable will be available for viewing. You should see something like this:
# Response Visualization
Source: https://docs.usebruno.com/advanced-guides/visualize
Bruno provides a powerful visualization feature that allows you to display API response data in a more readable and interactive format using the `bru.visualize` function. This feature supports multiple providers and formats to help you analyze and present your API data effectively.
## Try it out
Explore the [response-visualizer](https://github.com/bruno-collections/response-visualizer) sample collection to see tables, charts, and HTML views in action:
## Overview
The `bru.visualize` function allows you to display API response data in a more readable and interactive format using the `bru.visualize` function. This feature supports multiple providers and formats to help you analyze and present your API data effectively.
```javascript theme={null}
bru.visualize(type, config)
```
1. **type**(string): The type of visualization to render (e.g., 'table', 'html').
2. **config**(object): Depends on `type`:
* **`table`**: **name**, **provider** (`ag-grid`, `react-table`), **props**.
* **`html` (raw HTML)**: **name**, **content**
* **`html` (Handlebars)**: **name**, **template** (Handlebars string), **data** (object passed into the template), optional **options**.
### Parameters
| Name | Type | Description |
| -------- | -------- | -------------------------------------------------------------------- |
| `type` | `string` | Visualization kind: `'table'` or `'html'`. |
| `config` | `object` | See **Config properties** below. Table vs HTML use different fields. |
### Config properties
| Property | Type | Used with | Description |
| ---------- | -------- | ----------------------------- | -------------------------------------------------------------------------------- |
| `name` | `string` | `table`, `html` | Instance label in the Visualize tab. |
| `provider` | `string` | `table` only | `ag-grid` or `react-table`. |
| `props` | `object` | `table` only | Provider-specific row/column config. |
| `content` | `string` | `html` (raw) | Full HTML string (safe mode OK). |
| `template` | `string` | `html` (Handlebars) | Handlebars source (not in QuickJS safe mode). |
| `data` | `object` | `html` (Handlebars) | Template context object — values are available as `{{key}}` inside the template. |
| `options` | `object` | `html` (Handlebars, optional) | `Handlebars.compile()` options. Accepts a safe subset of options (see below). |
Handlebars is compiled server-side; the Visualize tab receives the rendered HTML.
## Supported Visualization
### Table Visualization ('table')
You can render tables using different providers like `ag-grid` and `react-table`.
**Syntax**:
```js theme={null}
bru.visualize('table', { // type
// config
name: '', // name of the visualization
provider: '', // provider type (ag-grid, react-table)
props: { , } // provider-specific row/column config
});
```
#### Using **ag-grid**
*Example:*
```javascript copy theme={null}
const rowData = [
{ name: 'John Doe', age: 28, email: 'john@example.com', city: 'New York' },
{ name: 'Jane Smith', age: 32, email: 'jane@example.com', city: 'London' }
];
const columnDefinitions = [
{ field: "name", filter: true, floatingFilter: true },
{ field: "age", filter: true, floatingFilter: true },
{ field: "email", filter: true, floatingFilter: true },
{ field: "city", filter: true, floatingFilter: true }
];
bru.visualize('table', {
name: 'table1',
provider: 'ag-grid',
props: { rowData, columnDefinitions }
});
```
This will render a table using the ag-grid provider with filters enabled on all columns.
#### Using **react-table**
*Example:*
```javascript copy theme={null}
const rowData = [
{ name: 'John Doe', age: 28, email: 'john@example.com', city: 'New York' },
{ name: 'Jane Smith', age: 32, email: 'jane@example.com', city: 'London' },
];
const columnDefinitions = [
{ id: 'name', header: 'name', cell: (info) => info.getValue(), meta: { filterVariant: 'text' } },
{ id: 'age', header: 'age', cell: (info) => info.getValue(), meta: { filterVariant: 'range' } },
{ id: 'email', header: 'email', cell: (info) => info.getValue(), meta: { filterVariant: 'text' } },
{ id: 'city', header: 'city', cell: (info) => info.getValue(), meta: { filterVariant: 'text' } },
];
bru.visualize('table', {
name: 'table2',
provider: 'react-table',
props: { rowData, columnDefinitions }
});
```
The `header` property only accepts **string values**. Use strings like `header: "Column Name"`.
This renders a table with text and range filters. `filterVariant: 'text'` adds a search box; `filterVariant: 'range'` adds Min/Max inputs.
### HTML visualization (`'html'`)
You can render either **raw HTML** or a **Handlebars** template with structured **data**.
**Synatx**
```js theme={null}
bru.visualize('html', { // type
name: '', // name of the visualization
content: `` // or , , (handlebars)
});
```
#### Raw HTML (`content`)
You can pass a full HTML string to the `content` property to render a raw HTML.
*Example:*
```javascript copy theme={null}
const htmlString = `
Name
Age
Email
City
John Doe
28
john@example.com
New York
Jane Smith
32
jane@example.com
London
`;
bru.visualize('html', {
name: 'htmlReport',
content: htmlString
});
```
This example will render an HTML table with predefined data using the html type.
#### Handlebars template (`template`, `data`, `options`)
Use Handlebars when you want a small template plus JSON data instead of building HTML strings in script:
```javascript copy theme={null}
bru.visualize('html', {
name: 'userCard',
template: '
{{title}}
{{message}}
',
data: { title: "Bruno", message: res.body.msg },
options: {} // optional; see Handlebars compile options below
});
```
Make sure to use `Post-script` while using `res.body` for visualization data.
Above example requires a `msg` property in the response body.
Bruno compiles **template** with **data** server-side and renders the result in the **Visualize** tab.
#### Handlebars compile options
The `options` field maps directly to [`Handlebars.compile()` options](https://handlebarsjs.com/api-reference/compilation.html). Bruno allows a safe subset:
| Option | Description |
| ------------------------ | -------------------------------------------------------------- |
| `noEscape` | Skip HTML escaping of output values. |
| `strict` | Throw on missing fields instead of rendering empty. |
| `assumeObjects` | Skip object existence checks on paths. |
| `preventIndent` | Disable auto-indent for partials. |
| `ignoreStandalone` | Disable standalone tag removal. |
| `explicitPartialContext` | Disable implicit context for partials. |
| `knownHelpersOnly` | Only allow pre-registered helpers (compile-time optimization). |
The following options are **excluded** regardless: `data`, `compat`, `knownHelpers`, `allowProtoPropertiesByDefault`, and `allowProtoMethodsByDefault` — these can alter template resolution in unexpected ways or weaken prototype-traversal protections.
**`dangerouslyAllowAllOptions`**
To bypass the allowlist entirely and pass any `Handlebars.compile()` option, set `dangerouslyAllowAllOptions: true` inside `options`:
```javascript copy theme={null}
bru.visualize('html', {
name: 'userCard',
template: '
{{title}}
{{body}}
',
data: { title: 'Bruno', body: res.body.msg },
options: {
strict: true,
dangerouslyAllowAllOptions: true
}
});
```
Use `dangerouslyAllowAllOptions` only when you fully control the template and data. Enabling excluded options like `allowProtoPropertiesByDefault` can expose prototype-traversal vulnerabilities.
#### `bru.clearVisualizations()`
Clears every visualization registered for the **current request** and resets the internal list (same idea as Postman’s `pm.visualizer.clear()`).
```javascript copy theme={null}
bru.clearVisualizations();
```
#### Postman `pm.visualizer` mapping
When you **import** a Postman collection, script calls are translated automatically:
| Postman | Bruno |
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
| `pm.visualizer.set(template, data)` | `bru.visualize('html', { template, data })` (plus `name` when you author in Bruno) |
| `pm.visualizer.set(template, data, options)` | `bru.visualize('html', { template, data, options })` |
| `pm.visualizer.clear()` | `bru.clearVisualizations()` |
Read more from **[Postman migration](/get-started/import-export-data/postman-migration)** and **[Converters overview](/converters/overview)**.
## Examples
One of the most powerful features of `bru.visualize` is the ability to transform API responses into visual tables. Here are practical examples of working with real API data:
In Bruno, the parsed API response is stored in **`res.body`**
### Custom Dashboard with Statistics
Create rich dashboards with API data:
1. Create or use existing request.
2. Go to request -> **Script** (post-response) tab and add the following code:
3. Click on **Send** button to execute the request.
```javascript copy theme={null}
const apiData = res.body
const stats = {
totalRequests: apiData.length || 0,
successRate: 98.5,
avgResponseTime: 145
};
const htmlString = `
API Analytics Dashboard
Last updated: ${new Date().toLocaleString()}
Total Requests
${stats.totalRequests || 0}
↑ 12% from last week
Success Rate
${stats.successRate || 0}%
↑ 3% improvement
Avg Response Time
${stats.avgResponseTime || 0}ms
↓ 15ms faster
`;
bru.visualize('html', {
name: 'dashboard',
content: htmlString
});
```
### Using API Response Data with Table
Render a live API response directly as an interactive table with sorting and filtering using `ag-grid`.
```javascript copy theme={null}
// API URL: https://jsonplaceholder.typicode.com/users
// Bruno stores the parsed response in res.body
const users = res.body;
const rowData = users.map(user => ({
id: user.id,
name: user.name,
email: user.email,
phone: user.phone,
city: user.address.city,
company: user.company.name,
}));
const columnDefinitions = [
{ field: 'id', headerName: 'ID', width: 70, filter: false },
{ field: 'name', headerName: 'Name', filter: true, floatingFilter: true },
{ field: 'email', headerName: 'Email', filter: true, floatingFilter: true },
{ field: 'phone', headerName: 'Phone', filter: true },
{ field: 'city', headerName: 'City', filter: true, floatingFilter: true },
{ field: 'company', headerName: 'Company', filter: true, floatingFilter: true },
];
bru.visualize('table', {
name: 'usersTable',
provider: 'ag-grid',
props: { rowData, columnDefinitions }
});
```
### Bar Chart
Render a bar chart using [Chart.js](https://www.chartjs.org/) loaded via CDN. Uses static data by default — swap in `res.body` values for live API data.
```javascript copy theme={null}
// Static data — replace with values from your API response
const labels = ['January', 'February', 'March', 'April', 'May', 'June'];
const values = [120, 95, 160, 140, 180, 210];
// To use live API data instead, replace the two lines above with:
// const labels = res.body.map(item => item.month);
// const values = res.body.map(item => item.count);
const html = `
Monthly API Requests
`;
bru.visualize('html', { name: 'barChart', content: html });
```
### Pie Chart
Render a pie chart using [Chart.js](https://www.chartjs.org/) via CDN. Uses static data by default — swap in `res.body` values for live API data.
```javascript copy theme={null}
// Static data — replace with values from your API response
const labels = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
const values = [540, 320, 180, 95, 65];
// To use live API data instead, replace the two lines above with:
// const labels = res.body.map(item => item.method);
// const values = res.body.map(item => item.count);
const html = `
API Requests by Method
`;
bru.visualize('html', { name: 'pieChart', content: html });
```
# Claude
Source: https://docs.usebruno.com/agents/claude
Claude is a powerful AI agent that can help you write code faster and more efficiently. It empowers developers, testers, and non-technical users to write code and automate their workflows.
## Configure Claude with Bruno
1. Download and install Claude from [here](https://claude.ai/download).
2. After installing Claude, open your project and create a `.claude/CLAUDE.md` file at the root of your project.
Your project folder structure should look like this:
You can write your own instructions for Claude to follow while working on the collection or you can use the [default instructions provided by Bruno](https://github.com/bruno-collections/ai-assistant-prompts/blob/main/prompts/vscode/.vscode/ai-instructions.md).
For example workflows and prompts, see [Use cases](./use-cases).
# Codex
Source: https://docs.usebruno.com/agents/codex
[Codex](https://openai.com/codex) is OpenAI's AI-powered coding agent. It reads custom instructions from `AGENTS.md` before doing any work, so you can give it project-specific guidance. With Bruno's local-first approach, you can add an `AGENTS.md` at your project root so Codex understands how to generate and work with Bruno collections.
Codex discovers instructions in this order: global `~/.codex/AGENTS.md`, See [Custom instructions with AGENTS.md](https://developers.openai.com/codex/guides/agents-md/) for full details.
## Configure Codex with Bruno
1. Install and set up Codex from the [Codex documentation](https://developers.openai.com/codex/).
2. At your project root (typically the Git root), create an `AGENTS.md` file with instructions for working with Bruno collections.
Your project folder structure should look like this:
You can write your own instructions in `AGENTS.md` for Codex to follow when working on Bruno collections, or use the [default instructions provided by Bruno](https://github.com/bruno-collections/ai-assistant-prompts) if available for Codex.
For example workflows and prompts, see [Use cases](./use-cases).
# Cursor
Source: https://docs.usebruno.com/agents/cursor
Cursor is a widely adopted AI code editor by developers. A powerful tool to automate your development workflow as well as remove the manual effort of writing code. With Bruno's local-first approach you can configure Cursor to automate your API development workflow.
## Configure Cursor with Bruno
1. Download and install Cursor from [here](https://cursor.com/download).
2. After installing Cursor, open your project and create a `.cursor/rules/instructions.md` file.
Your project folder structure should look like this:
You can write your own instructions for Cursor to follow while working on the collection or you can use the [default instructions provided by Bruno](https://github.com/bruno-collections/ai-assistant-prompts/tree/main/prompts/cursor).
After creating the instructions file, you can start using Cursor to automate your API development workflow. See [Use cases](./use-cases) for example prompts and workflows.
# Overview
Source: https://docs.usebruno.com/agents/overview
Bruno's local-first approach allows you to configure it with any AI agent and editors like Cursor, VS Code, Claude, and more. Meanwhile keeping all your data version controlled and stored on your local filesystem.
You can configure Bruno to use any AI agent and editor of your choice.
## Use Cases
See [Use cases](./use-cases) for common workflows (generating collections, writing tests, CI/CD) and example prompts you can use with any supported agent.
## Supported Agents
* [Cursor](https://cursor.com) - AI code editor with powerful automation for API development workflows
* [VS Code](https://code.visualstudio.com) with GitHub Copilot - Popular editor with built-in AI code completion
* [Codex](https://developers.openai.com/codex/) - OpenAI's coding agent; uses `AGENTS.md` for custom instructions
* [Claude](https://claude.ai) - AI agent for writing code and automating workflows
# Use Cases
Source: https://docs.usebruno.com/agents/use-cases
Once you've [configured an AI agent](./overview) with Bruno, you can use it to automate common API development tasks. The following use cases apply to any supported agent (Cursor, VS Code with Copilot, Codex, Claude); specific examples use Cursor-style prompts where relevant.
## Common Use Cases
* **Generate Bruno collections** – Create API collections from your backend source code with requests, tests, and documentation.
* **Write scripts and tests** – Use the agent to write pre-request scripts, test scripts, and assertions. Reference Bruno's [JavaScript API Reference](/testing/script/javascript-reference) and [Bru Lang](/bru-lang/overview) in your instructions so the agent produces valid Bruno files.
* **Automate workflows** – Get help with CI/CD pipelines, environment setup, and collection organization.
## Example: Create a complete Bruno collection from your backend API
If you want to create a new Bruno collection for your backend API endpoints from source code, you can use a prompt like:
```md theme={null}
Create Bruno collection for my backend API with relevant test cases and write docs for each request at request level.
```
The agent will analyze your source code and generate a Bruno collection. Your project may look like this:
## Example: Create a CI/CD pipeline for API tests
You can ask the agent to generate a CI/CD pipeline for your API tests using Bruno CLI. For example, with Cursor you can use:
```md theme={null}
Create fully functional CI/CD pipeline for my API tests using Bruno CLI to generate test reports.
```
The agent can generate a workflow file (e.g. GitHub Actions) in your project:
## Example: Write automated test cases
You can ask your AI agent to write test cases for each API in your Bruno collection, including different edge cases.
### Prompt
Use a prompt like:
```md theme={null}
Write test cases for each API in my Bruno collection. Include happy-path checks and different edge cases (error responses, missing fields, invalid data, response time, headers).
```
The agent generates test scripts. Bruno uses the [Chai library](https://www.chaijs.com/) for assertions—all Chai `expect` syntax works in Bruno tests. The agent can produce tests such as:
**Status codes and response body (happy path):**
```javascript theme={null}
test("should return success", function () {
expect(res.getStatus()).to.equal(200);
});
test("should return user data", function () {
const body = res.getBody();
expect(body).to.have.property("id");
expect(body.name).to.equal("John Doe");
expect(body.email).to.contain("@example.com");
});
```
**Edge cases (error responses, conditional logic):**
```javascript theme={null}
test("should validate response based on status", function () {
const status = res.getStatus();
const body = res.getBody();
if (status === 200) {
expect(body).to.have.property("data");
expect(body.data).to.not.be.empty;
} else if (status === 404) {
expect(body).to.have.property("error");
expect(body.error.message).to.contain("not found");
} else {
throw new Error(`Unexpected status code: ${status}`);
}
});
test("should validate error response structure", function () {
const status = res.getStatus();
if (status >= 400) {
const body = res.getBody();
expect(body).to.have.property("error");
expect(body.error).to.have.property("message");
expect(body.error.message).to.be.a("string");
}
});
```
For more test patterns (headers, response time, arrays and loops, saving variables for the next request), see the [Testing introduction](/testing/tests/introduction).
# GitHub Copilot
Source: https://docs.usebruno.com/agents/vs-code
VS Code is known as the most popular code editor among developers. It's open source and free to use. VS Code has built-in GitHub Copilot support for AI code completion and you can configure it to use Bruno as your AI agent.
GitHub Copilot is a powerful AI agent that can help you write code faster and more efficiently.
## Configure GitHub Copilot with Bruno
1. Download and install VS Code from [here](https://code.visualstudio.com/download).
2. After installing VS Code, install the [GitHub Copilot extension](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot).
3. Create a `copilot-instructions.md` file inside the `.github` folder at the root of your project.
Your project folder structure should look like this:
You can write your own instructions for Copilot to follow while working on the collection or you can use the [default instructions provided by Bruno](https://github.com/bruno-collections/ai-assistant-prompts/tree/main/prompts/copilot/.github/copilot-instructions.md).
For example workflows and prompts, see [Use cases](./use-cases).
# Auto Generate Documentation
Source: https://docs.usebruno.com/api-docs/auto-generate-docs
Bruno allows you to automatically generate a standalone HTML documentation file from your collection. This creates a shareable, interactive API documentation page that you can deploy anywhere or share with your team.
## Generate Documentation
1. Navigate to the Collection Settings and click on the **Generate Docs** under Documentation section
2. Click the **Generate** button to create the HTML documentation
3. Bruno will generate a HTML documentation file for your collection documentation
## Key Features
### Auto-Generated Docs from Bruno Collections
* **Support for .bru files** and Bruno collection structure
* **Render nested folders** with requests, environment variables, and metadata
* **Sample requests/responses** and script previews included
* **Environment support** for different configurations
### Code Samples
The documentation automatically generates code snippets for each request in multiple languages:
* cURL
* Python (requests)
* JavaScript
### Tests and Scripts Display
* Show **pre-request scripts** inline for each request
* Display **post-response scripts** and test logic
* View **test assertions** directly in the documentation
### Try It Out Playground
Interactive playground that allows users to:
* Edit request parameters, headers, and body
* Modify and test scripts in real-time
* Execute requests live directly from the documentation
* View responses immediately
## Deployment Options
Once generated, you can:
1. **Open locally**: View the HTML file in any browser
2. **Static hosting**: Deploy to GitHub Pages, Netlify, Vercel, or any static host
3. **Share directly**: Send the HTML file to team members or API consumers
4. **Documentation portal**: Add to your developer portal or docs site
# Document a Collection
Source: https://docs.usebruno.com/api-docs/collection-docs
Bruno allows you to add documentation directly at the collection level, which can be extremely useful for providing a high-level overview of your entire API collection.
By configuring documentation at the collection level, you ensure that users and developers have a centralized place to understand the key concepts, actions, and flow of your API before diving into individual requests or folders.
## Create Collection Docs
1. Go to the **Collection** settings.
2. Click on the **Edit** icon on the right-hand side.
3. Click the **Save** button to view the formatted output.
4. Formatted Preview for Collection Documentation.
# Document a Folder
Source: https://docs.usebruno.com/api-docs/folder-docs
To add Folder-level documentation in Bruno, you need to first create a folder within your collection. Once the folder is created, you can add specific details for the folder-level API documentation.
## Create Folder Docs
1. Navigate to the folder level settings.
2. Go to the Docs tab and click the **Edit** button.
3. Click the **Preview** button to view the formatted output.
# APIs Documentation
Source: https://docs.usebruno.com/api-docs/overview
Bruno allows you to create API documentation at four distinct levels: **Workspace, Collection, Folder, and Request**.
With full Markdown support you can write clear, concise, and well-structured documentation at each level supplemented with any relevant screenshots, gifs, or other elements to ensure your users have a seamless onboarding experience.
Bruno sanitizes all HTML in documentation blocks before rendering. This prevents cross-site scripting (XSS) attacks when previewing markdown that contains embedded HTML. Elements like `script` tags and inline event handlers are automatically removed.
Although Collections are the machine-readable instructions for interacting with an API, offering human-readable instructions to your users can significantly reduce the time to their first interaction.
## Create Documentation
1. [Document a workspace](./workspace-docs)
2. [Document a collection](./collection-docs)
3. [Document a folder](./folder-docs)
4. [Document a request](./request-docs)
## Auto Generate Documentation
Bruno can automatically generate a standalone HTML documentation file for your entire collection. This allows you to quickly create and deploy professional-looking API documentation.
[Learn how to auto-generate docs](/api-docs/auto-generate-docs)
# Document a Request
Source: https://docs.usebruno.com/api-docs/request-docs
In Bruno, you can create Request-level documentation to provide specific details about your API and its functions. Bruno offers a **Docs** section where you can easily write and manage your request-level documentation.
## Create Request Docs
1. Go to the **Docs** tab within your request.
2. Click the **Edit** button.
3. Enter your API details in the provided textarea.
4. Click the **Preview** button to see the formatted output.
# Document a Workspace
Source: https://docs.usebruno.com/api-docs/workspace-docs
Bruno allows you to add documentation at the workspace level, providing a centralized place for high-level information about your entire workspace. Workspace documentation is ideal for onboarding new team members, explaining the workspace structure, and documenting organizational standards.
## Create Workspace Docs
1. Navigate to your workspace settings by clicking on the home icon.
2. Click on **Add Documentation** button.
3. Add your documentation in the textarea section in markdown format.
4. Click save button to save your documentation.
# Adding and managing certificates
Source: https://docs.usebruno.com/auth/add-and-manage-certs
Bruno supports adding custom Client Authorization (CA) and client certificates to your requests if required by the API you are working with.
### Adding a custom CA certificate
Navigate to the `Preferences` menu in Bruno and select `Use Custom CA Certificate`, then upload the selected file.
### Adding client certificates to Collections
You can add and manage client certificate(s) on any Collection by going to the `Collection Settings` menu and selecting `Client Certificates`.
Simply enter the domain, designate the type of certificate, and then upload the relevant file(s).
# Authenticate using AWS Signature
Source: https://docs.usebruno.com/auth/aws-signature
AWS Signature is the authorization workflow for Amazon Web Services requests. AWS uses a custom HTTP scheme based on a keyed-HMAC (Hash Message Authentication Code) for authentication.
The official AWS Signature documentation provides more detail:
* [Signing and Authenticating Requests](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html)
* [How to call an API](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html)
To use AWS Signature, do the following:
1. In the `Auth` tab for a request, select `AWS Sig V4` from dropdown list.
2. Enter your *AccessKeyID* and *SecretKey* values.
For extra security, integrate Bruno with your secret manager to store these values in a single location. Learn more [here](./../secrets-management/secret-managers/hashicorp-vault/overview).
The AWS Signature parameters are as follows:
* Session Token - Required only when using temporary security credentials.
* Service Name - The service receiving the request.
* AWS Region - The region receiving the request.
* Profile Name - name of your AWS signing profile
# Using Basic authentication
Source: https://docs.usebruno.com/auth/basic
Basic authentication involves sending a verified username and password with your request. In the request `Auth` tab, select `Basic Auth` from the Auth Type dropdown list.
Enter your API username and password in the Username and Password fields. For extra security, store these in variables.
In the request Headers, the Authorization header passes the API a Base64 encoded string representing your username and password values, appended to the text `Basic`.
# Authenticate with a Bearer token
Source: https://docs.usebruno.com/auth/bearer
Bearer tokens enable requests to authenticate using an access key, such as a JSON Web Token (JWT). The token is a text string, included in the request header.
In the request `Auth` tab, select `Bearer Token` from the Auth Type dropdown list. In the Token field, enter your API key value. For added security, store it in a variable and reference the variable by name.
Bruno appends the token value to the text `Bearer` in the required format to the request Authorization header.
# Authenticate with Digest Access
Source: https://docs.usebruno.com/auth/digest
With Digest auth, the client sends a first request to the API, and the server responds back with details.
Response details include a number that can be used only once (a nonce), a realm value, and a 401 Unauthorized response.
You then send back an encrypted array of data, including a username and password combined with the data received from the server in the first request.
The server uses the passed data to generate an encrypted string and compares it against what you sent to authenticate your request.
In the Authorization tab for a request, select Digest Auth from the Auth Type dropdown list.
# Authenticate with NTLM
Source: https://docs.usebruno.com/auth/ntlm
NTLM (NT LAN Manager) is a Microsoft authentication protocol used for Windows-based systems. Its challenge-response authentication mechanism is primarily used for:
* SharePoint
* Exchange
* Internal corporate APIs
Unlike basic authentication, which has a less secure protection layer, NTLM has multiple handshake steps and offers more robust security.
## NTLM in Bruno
Follow these steps to use NTLM authentication:
1. Open your request
2. Navigate to the **Auth** tab
3. Select **NTLM** from the dropdown list
4. Fill in the required fields:
* **Username**: Your Windows username
* **Password**: Your Windows password
* **Domain**: Your Windows domain name
# OAuth 1.0 authentication
Source: https://docs.usebruno.com/auth/oauth1
OAuth 1.0 is a protocol that allows applications to make authenticated HTTP requests on behalf of a user without exposing their credentials. Bruno supports the full OAuth 1.0 specification ([RFC 5849](https://datatracker.ietf.org/doc/html/rfc5849)), including multiple signature methods and flexible parameter placement.
## Setting up OAuth 1.0
1. Open your request.
2. Navigate to the **Auth** tab.
3. Select **OAuth 1.0** from the dropdown list and configure the required fields.
OAuth 1.0 can be configured at the **request**, **folder**, or **collection** level. When set at the collection or folder level, all child requests that use **Inherit** as their auth mode will use the shared OAuth 1.0 configuration.
## Configuration fields
### Core credentials
| Field | Description |
| ------------------- | --------------------------------------------------------------------------- |
| **Consumer Key** | The application's consumer key issued by the OAuth provider. |
| **Consumer Secret** | The application's consumer secret. Hidden when using RSA signature methods. |
| **Token** | The access token for the authenticated user. |
| **Token Secret** | The access token secret for the authenticated user. |
### Signature
| Field | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Signature Method** | The algorithm used to sign requests. See [Supported signature methods](#supported-signature-methods). |
| **Private Key** | The PEM private key used for RSA signature methods. Only shown when an RSA method is selected. Can be provided inline or as a file path. |
| **Add Params To** | Where OAuth parameters are placed — `Header`, `Query Params`, or `Body`. Defaults to `Header`. |
| **Include Body Hash** | When enabled, computes and includes an `oauth_body_hash` parameter for non-form-encoded request bodies. |
### Advanced
These fields are optional and are hidden in a collapsible section.
| Field | Description |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| **Callback URL** | The `oauth_callback` URL, used during the temporary credentials step of the OAuth 1.0 flow. |
| **Verifier** | The `oauth_verifier` value, used during the token credentials step. |
| **Timestamp** | Override the auto-generated `oauth_timestamp`. Leave empty to let Bruno generate it automatically. |
| **Nonce** | Override the auto-generated `oauth_nonce`. Leave empty to let Bruno generate it automatically. |
| **Version** | The `oauth_version` value. Defaults to `1.0`. |
| **Realm** | The `realm` parameter included in the Authorization header. |
All OAuth 1.0 fields support [Bruno variables](/variables/overview). For example, you can store your consumer key in an environment variable and reference it as `{{consumerKey}}`.
## Supported signature methods
Bruno supports seven signature methods:
| Method | Type | Description |
| --------------- | ---- | -------------------------------------------------------------------- |
| **HMAC-SHA1** | HMAC | Signs using HMAC with SHA-1. This is the most common method. |
| **HMAC-SHA256** | HMAC | Signs using HMAC with SHA-256. |
| **HMAC-SHA512** | HMAC | Signs using HMAC with SHA-512. |
| **RSA-SHA1** | RSA | Signs using an RSA private key with SHA-1. |
| **RSA-SHA256** | RSA | Signs using an RSA private key with SHA-256. |
| **RSA-SHA512** | RSA | Signs using an RSA private key with SHA-512. |
| **PLAINTEXT** | None | Sends the signing key directly without hashing. Only use over HTTPS. |
When using an RSA signature method, you must provide a PEM-formatted private key. The **Consumer Secret** field is not used with RSA methods.
## Parameter placement
You can choose where OAuth parameters are included in your requests:
* **Header** (default) — OAuth parameters are sent in the `Authorization` header, formatted per RFC 5849.
* **Query Params** — OAuth parameters are appended to the request URL as query parameters.
* **Body** — OAuth parameters are merged into the request body as `application/x-www-form-urlencoded` parameters.
Body placement requires a form-urlencoded body. If your request uses a different content type, the body will be replaced with OAuth parameters.
## CLI support
OAuth 1.0 is fully supported in the [Bruno CLI](/bru-cli/overview). When you run collections or requests via `bru run`, OAuth 1.0 authentication is applied automatically to requests that have OAuth 1.0 configured.
# How to Use OAuth2 for the Authorization Code Grant Type
Source: https://docs.usebruno.com/auth/oauth2-2.0/authorization-code
OAuth2 can be configured in multiple ways, making it adaptable to your needs. You can configure OAuth2 auth at collection, folder, and request level.
## OAuth2 Configuration
1. Go to **Collection Settings** and navigate to the **Auth** tab.
2. Select `OAuth 2.0` as the auth method and set the grant type to `Authorization Code`.
3. Fill in the following fields:
* **Callback URL**: Also known as **Redirect URI**, this URL must be configured with the IdP\* (Identity Provider)
* **Authorization URL**: The IdP's authorization endpoint, often named **/auth** or **/authorization**
* **Access Token URL**: The IdP's token endpoint, often named **/token**
* **Client ID**: Also known as **App ID**, the IdP-supplied client identifier
* **Client Secret**: Also known as **App Secret**, the IdP-supplied client secret
* **Scope**: The requested scope(s) for the access
* **State**: Can be a random value or transfer data
* **Add Credentials to**: Choose how to add the access token to requests (in the Request Body or a Basic Auth Header)
For Bruno's purposes, this can be any URL because the redirect will be intercepted before getting there (see [Understanding Callback URLs](#understanding-callback-urls-redirect-uris)). However, it still must match a **Callback URL** (or **Redirect URI**) configured with the IdP.
*The IdP (Identity Provider) is the OAuth2 authorization provider (like Azure, Google, Facebook, etc.) managing access to the API.*
### Use PKCE
Enable "Use PKCE" if your OAuth provider supports it as a recommended best security practice.
## Understanding Callback URLs (Redirect URIs)
The **Callback URL** (also known as **Redirect URI**) is a critical component of the OAuth2 authorization code flow. Here's what you need to know:
### How Bruno Handles Callback URLs
Bruno does not actually redirect to the Callback URL. Instead, Bruno effectively serves as the redirect endpoint by intercepting the redirect attempt from the OAuth2 IdP (Identity Provider). That's how Bruno is able to handle the authorization code exchange and receive the access token.
This means:
* **Bruno Support all formats**: It can handle any callback URL format that your OAuth2 provider allows
* **Callback URL isn't actually hit**: Bruno never actually navigates to the Callback URL
* **Security maintained**: The auth process is handled securely
## Browser Support
Bruno supports oauth2 authentication using system browser and inbuilt browser\[default]. You can enable system browser by selecting the **System Browser** option in the **Callback URL** section.
Learn more about [System Browser Support](/auth/oauth2-2.0/system-browser)
## Token Configuration and Usage
Before getting the access token, configure how you want to use it in your requests:
1. **Token Usage Configuration**:
* **Add token to**: Specify how the token should be included in requests:
* **Headers**: Adds the token to request headers with a configurable prefix (default: `"Bearer"`).
* **URL**: Adds the token as a URL query parameter with a configurable parameter name (default: `"access_token"`).
2. **Token Management Settings**:
* **Auto-fetch**: When enabled, Bruno will automatically fetch a new token when you try to access a resource and don't have a valid token.
* **Auto-refresh**: When enabled, Bruno will automatically refresh your token using the refresh URL when it expires.
These settings streamline your workflow by reducing manual token management. Note that token refresh operations require a valid refresh token to be available.
You may optionally assign a Token ID to your token. If you do, you can reference this token in requests using the format `{{$oauth2..access_token}}` (replace `` with your Token ID).
If you use the same Token ID in multiple OAuth requests, Bruno will overwrite the existing token data. Make sure to use unique Token IDs for different OAuth configurations.
2. Click **Get Access Token** to obtain and store the token.
You can click the 'Clear Cache' button to remove the stored token and request a new one.
* Read more about [using tokens](/auth/oauth2-2.0/collection-level-configuration#using-oauth2-tokens) in scripts and variables.
# How to Use OAuth2 for the Client Credentials Grant Type
Source: https://docs.usebruno.com/auth/oauth2-2.0/client-credentials
OAuth2 authentication can be implemented in various ways, depending on the collection structure and specific requirements. You can configure OAuth2 at either the [collection level](/auth/oauth2-2.0/collection-level-configuration) or [request level](/auth/oauth2-2.0/collection-level-configuration).
## OAuth2 Configuration
Select `OAuth 2.0` as the authentication method and set the grant type to `Client Credentials`. Fill in the following required fields:
* **Access Token URL**: The endpoint to obtain the access token
* **Client ID**: Your application's client identifier
* **Client Secret**: Your application's client secret
* **Scope**: (Optional) The requested scope
* **Add Credentials to**: Choose how to send credentials (Request Body or Basic Auth Header)
When using client certificates with OAuth2 client credentials, ensure your domain uses wildcard patterns (e.g., `*.domain.com`) instead of specific subdomains. This ensures proper certificate matching and prevents "Bad credentials" errors.
## Token Configuration and Usage
Before getting the access token, configure how you want to use it in your requests:
1. **Token Usage Configuration**:
* **Add token to**: Specify how the token should be included in requests:
* **Headers**: Adds the token to request headers with a configurable prefix (default: `"Bearer"`).
* **URL**: Adds the token as a URL query parameter with a configurable parameter name (default: `"access_token"`).
2. **Token Management Settings**:
* **Auto-fetch**: When enabled, Bruno will automatically fetch a new token when you try to access a resource and don't have a valid token.
* **Auto-refresh**: When enabled, Bruno will automatically refresh your token using the refresh URL when it expires.
These settings streamline your workflow by reducing manual token management. Auto-refresh is particularly useful for long-running operations or testing sessions.
You may optionally assign a Token ID to your token. If you do, you can reference this token in requests using the format `{{$oauth2..access_token}}` (replace `` with your Token ID).
If you use the same Token ID in multiple OAuth requests, Bruno will overwrite the existing token data. Make sure to use unique Token IDs for different OAuth configurations.
2. Click **Get Access Token** to obtain and store the token.
You can click the 'Clear Cache' button to remove the stored token and request a new one.
# OAuth 2.0 Configuration
Source: https://docs.usebruno.com/auth/oauth2-2.0/collection-level-configuration
## Overview
Bruno allows you to configure OAuth2 authentication at multiple levels: collection, folder, and request. This guide explains how to set up and use OAuth2 authentication effectively across your API testing workflow.
The configuration process and all settings are identical across all levels. The only difference is where you access the settings and the scope of the configuration.
## Setting Up OAuth2
1. **Access Settings**
* **Collection Level**: Open your collection and navigate to Collection Settings > Auth tab
* **Folder Level**: Right-click on the folder and select Folder Settings > Auth tab
* **Request Level**: Open your request and navigate to Auth tab
2. **Configure OAuth2**
* Choose "OAuth 2.0" from the authentication dropdown
* Select your preferred grant type
* Fill in the required OAuth2 parameters
## Supported Grant Types
Bruno supports three OAuth2 grant types:
* [Authorization Code](/auth/oauth2-2.0/authorization-code)
* [Client Credentials](/auth/oauth2-2.0/client-credentials)
* [Password Credentials](/auth/oauth2-2.0/password-credentials)
## Managing Access Tokens
### Generating Tokens
1. Configure OAuth2 parameters as per grant type.
2. You can choose where to add this token in the request by selecting the "Add token to" dropdown:
* Headers
* Adds the token to the request headers
* Configure the header prefix (defaults to "Bearer")
* URL
* Adds the token to the request URL
* Configure the query parameter name (defaults to "access\_token")
3. Click "Get Access Token" to obtain and store the token.
### Auto-fetch and Auto-refresh Settings
Bruno provides two important token management features:
* **Auto-fetch**: When enabled, Bruno will automatically fetch a new token when you try to access a resource and don't have a valid token. This eliminates the need to manually retrieve a token before making API calls.
* **Auto-refresh**: When enabled, Bruno will automatically refresh your token using the refresh URL when it expires. This ensures continuous access to protected resources without manual intervention.
These settings can be found in the OAuth2 configuration panel and provide significant convenience for API testing workflows.
You may optionally assign a Token ID to your token. If you do, you can reference this token in requests using the format `{{$oauth2..access_token}}` (replace `` with your Token ID).
If you use the same Token ID in multiple OAuth requests, Bruno will overwrite the existing token data. Make sure to use unique Token IDs for different OAuth configurations.
## Authentication Inheritance
Once you configure OAuth2 at the collection or folder level, you can inherit the authentication at the request level without duplicating the configuration.
### How to Inherit Authentication
1. Open your request and navigate to **Auth** tab
2. Select **Inherit** from the authentication dropdown
Bruno will automatically determine the source level:
* **Inherit from Collection**: Uses the collection-level OAuth2 configuration
* **Inherit from Folder**: Uses the folder-level OAuth2 configuration (if available)
The token will be automatically added to your request according to the inherited configuration.
### Inheritance Hierarchy
Bruno follows a specific hierarchy for authentication inheritance:
1. **Request-level configuration** (highest priority)
2. **Folder-level configuration** (if no request-level config)
3. **Collection-level configuration** (if no folder-level config)
4. **No authentication** (if no configuration at any level)
Folder-level OAuth2 configuration overrides collection-level settings for requests within that folder. If no folder-level auth is configured, requests will inherit from the collection level.
## Using OAuth2 Tokens
### Method 1: Collection/Folder Inheritance
When a collection or folder has OAuth2 configured, individual requests can inherit the authentication:
1. Open and configure OAuth2 in collection level settings
2. Enter all required credentials depend upon type of oauth grant type (Authorization Code, Client Credentials, Password Credentials)
3. Once you configure the OAuth2, you can click on the "Get Access Token" button to get the token
4. Navigate to the request auth tab and select inherit as type
5. The token will be automatically added to the request according to your configuration (Headers or URL)
### Method 2: Direct Token Reference
You can manually reference OAuth2 tokens in your requests using the variable format `{{$oauth2..access_token}}` in headers or query parameters.
#### Example
1. Navigate to the collection level oauth2 settings and generate the token by clicking on the "Get Access Token" button
2. Navigate to the request and use the token id in the request headers or query parameters
3. You can use the following format: `{{$oauth2.credentials.access_token}}`
You can click the 'Clear Cache' button to remove the stored token and request a new one at any time.
## Configuration Comparison
| Feature | Collection Level | Folder Level | Request Level |
| ----------------- | ------------------------------ | ------------------------------ | ------------------------------- |
| **Scope** | All requests in collection | Requests in specific folder | Individual request |
| **Inheritance** | Inherit from collection | Inherit from folder | Override collection/folder |
| **Configuration** | Collection settings | Folder settings | Request settings |
| **Use Case** | Collection-wide authentication | Folder-specific authentication | Request-specific authentication |
## Additional Parameters
Bruno supports adding custom headers, query parameters, and body parameters to OAuth2 requests, providing flexibility similar to Bruno v1.x. This feature allows you to customize OAuth2 requests to meet specific provider requirements.
# OAuth 2.0 Authentication in Bruno
Source: https://docs.usebruno.com/auth/oauth2-2.0/overview
This document describes the OAuth 2 implementation available in Bruno. This approach simplifies authentication handling with automatic token management and flexible configuration options.
Want to learn or brush up on OAuth 2? Read more about it here: [https://blog.usebruno.com/what-is-oauth-2.0-oauth-2-explained-for-developers](https://blog.usebruno.com/what-is-oauth-2.0-oauth-2-explained-for-developers)
## Try it out
Explore the [oauth-keycloak](https://github.com/bruno-collections/oauth-keyclock) sample collections for real-world OAuth2 flows to walk through each grant type step by step:
## Overview
Bruno supports OAuth 2 authentication at collection, folder and request levels:
* **Collection Level**: Configure once and share across all requests in the collection
* **Folder Level**: Configure for a specific folder and share across all requests within that folder
* **Request Level**: Configure for individual requests, providing maximum flexibility
## Features & Improvements
1. **Simplified Configuration**: You no longer need to separately configure authorization and resource requests
2. **Automatic Token Management**: No scripting is required to set headers for the resource URL
3. **Automatic Token Injection**: Bruno automatically adds the token to request headers or parameters based on your configuration
4. **Token Data Access**: Access token data using variables: `{{$oauth2..access_token}}` within the collection
5. **Auto-fetch and Auto-refresh**: Bruno can automatically fetch new tokens when needed and refresh tokens when they expire, providing a seamless authentication experience
### Auto-fetch and Auto-refresh
Bruno includes two powerful token management features:
* **Auto-fetch**: Automatically fetches a new token when you try to access a protected resource and don't have a valid token
* **Auto-refresh**: Automatically refreshes your token using the refresh URL when it expires
These settings can be configured separately for each OAuth2 implementation and help streamline your API testing workflow by reducing manual token management tasks.
### System Browser Support
Bruno now supports OAuth 2.0 authentication using your system browser. This provides:
* **Familiar UX**: Use your default browser with saved passwords and extensions
* **Better Compatibility**: Enhanced support for OAuth providers that block embedded browsers
* **Enhanced Security**: Leverage your browser's security features
Learn more about [System Browser Support](/auth/oauth2-2.0/system-browser)
## Accessing OAuth2 Tokens in Scripts
You can access OAuth2 tokens in your scripts using the `bru.getOauth2CredentialVar()` function.
### Syntax
```javascript theme={null}
bru.getOauth2CredentialVar('$oauth2..access_token')
```
### Resetting OAuth2 Credentials
Use `bru.resetOauth2Credential(credentialId)` to programmatically clear existing OAuth2 credentials and trigger a fresh authentication cycle.
```javascript theme={null}
bru.resetOauth2Credential("my-credential-id");
```
Multiple requests can share the same `credentialId`. Resetting a credential ID will clear the credentials for **all** requests that use it.
**Example — refresh on 401 Unauthorized:**
```javascript theme={null}
if (res.getStatus() === 401) {
bru.resetOauth2Credential("my-credential-id");
console.log("OAuth2 credentials reset, fresh token will be fetched on next request");
}
```
For the full list of OAuth2 scripting APIs, see the [JavaScript API Reference](/testing/script/javascript-reference#oauth2-credentials).
# How to Use OAuth2 for the Password Credentials Grant Type
Source: https://docs.usebruno.com/auth/oauth2-2.0/password-credentials
OAuth2 authentication can be implemented in various ways, depending on the collection structure and specific requirements. You can configure OAuth2 at either the [collection level](/auth/oauth2-2.0/collection-level-configuration) or [request level](/auth/oauth2-2.0/collection-level-configuration).
## OAuth2 Configuration
Select `OAuth 2.0` as the authentication method and set the grant type to `Password Credentials`. Fill in the following required fields:
* **Access Token URL**: The endpoint to obtain the access token
* **Username**: The resource owner's username
* **Password**: The resource owner's password
* **Client ID**: Your application's client identifier
* **Client Secret**: Your application's client secret
* **Scope**: (Optional) The requested scope
* **Add Credentials to**: Choose how to send credentials (Request Body or Basic Auth Header)
## Token Configuration and Usage
Before getting the access token, configure how you want to use it in your requests:
1. **Token Usage Configuration**:
* **Add token to**: Specify how the token should be included in requests:
* **Headers**: Adds the token to request headers with a configurable prefix (default: `"Bearer"`).
* **URL**: Adds the token as a URL query parameter with a configurable parameter name (default: `"access_token"`).
2. **Token Management Settings**:
* **Auto-fetch**: When enabled, Bruno will automatically fetch a new token when you try to access a resource and don't have a valid token.
* **Auto-refresh**: When enabled, Bruno will automatically refresh your token using the refresh URL when it expires.
These settings streamline your workflow by reducing manual token management. When using password credentials, auto-fetch is especially useful as it eliminates the need to manually authenticate before each testing session.
You may optionally assign a Token ID to your token. If you do, you can reference this token in requests using the format `{{$oauth2..access_token}}` (replace `` with your Token ID).
If you use the same Token ID in multiple OAuth requests, Bruno will overwrite the existing token data. Make sure to use unique Token IDs for different OAuth configurations.
2. Click **Get Access Token** to obtain and store the token.
You can click the 'Clear Cache' button to remove the stored token and request a new one.
# System Browser Support for OAuth 2.0
Source: https://docs.usebruno.com/auth/oauth2-2.0/system-browser
Bruno provides a **System Browser** option for OAuth 2.0 Authorization Code authentication, allowing you to use your default system browser instead of the embedded Electron browser window.
## Enabling System Browser
### Global Configuration (Recommended)
If you want to use Bruno System Browser OAuth2 for all your collections in workspace, you can enable it from **Preferences**. This will make System Browser available for the entire collection by default.
1. Navigate to **Preferences** (located at the bottom of the left sidebar)
2. Go to the **General** section
3. Enable **Use System Browser for OAuth2**
4. This setting will apply to all collections by default
### General Configuration
1. Navigate to the Auth tab at (request, folder or collection) level and selecet OAuth 2.0 as authentication method
2. Under **Callback URL**, select the **Use System Browser for OAuth** option
The **Inbuilt browser** option is selected by default, maintaining existing behavior for users who prefer the embedded browser.
## How It Works
When you select the System Browser option, the OAuth flow proceeds as follows:
1. **Authorization Request**: Bruno initiates the OAuth flow and opens your system browser
2. **User Authentication**: You authenticate in your system browser with your OAuth provider
3. **Callback Redirect**: The authorization server redirects to the configured callback URL:
```
https://oauth.usebruno.com/callback
```
4. **Deep Link Trigger**: The callback page redirects to Bruno's custom URL scheme:
```
bruno://app/oauth2/callback
```
5. **Token Exchange**: Bruno receives the authorization code and:
* Validates the `state` parameter
* Performs the authorization code + PKCE token exchange locally
* Stores the access token securely
All OAuth data remains on your machine. Tokens are never processed or stored by Bruno servers.
## Custom Callback Server
You may use your own hosted callback server or self-host one. Bruno will treat any configured callback URL as the OAuth redirect endpoint.
### Option 1: Hosted Callback Server
You can host your own callback server and configure that in Bruno. Bruno will use this custom callback endpoint to complete the OAuth flow securely within your network.
**Configuration Steps:**
1. Host your own callback server at your domain
2. Configure Bruno with your hosted callback URL:
```
https://your-domain.com/oauth/callback
```
3. Add the same URL to your OAuth provider's allowed redirect URLs
### Option 2: Run a Local Callback Server
If you have Node.js installed, you can quickly start a local callback server using:
```bash theme={null}
npx @usebruno/oauth2-callback-server
```
This command starts a local server that automatically forwards OAuth redirects to Bruno via the deep link mechanism.
**Configuration Steps:**
1. Start the callback server using the command above
2. Configure Bruno to use your local callback URL:
```
http://localhost:3000/oauth/callback
```
3. Add the same URL to your OAuth provider's allowed redirect URLs
Ensure your OAuth provider allows `localhost` redirect URLs.
# Authentication in Bruno
Source: https://docs.usebruno.com/auth/overview
Bruno allows you to send authentication details with your API requests.
Authentication methods can be set on the request level or collection level, if you want all of your requests to use the same method.
## Try it out
Explore the [oauth-keycloak](https://github.com/bruno-collections/oauth-keyclock) sample collections for real-world OAuth2 flows to walk through each grant type step by step:
Some APIs will require a digital certificate to establish a client's identity.
You can add your certificate authority (CA) or client certificates to Bruno.
See [Add and Manage Certificates](./add-and-manage-certs) for more
information.
You can pass auth details along with any request in Bruno in the header, body, or as a parameter.
If you enter your auth details to the `Auth` tab of your requests, we will automatically populate the relevant parts of the request for your chosen auth method.
### Bruno currently supports the following authentication protocols:
* [OAuth 1.0](/auth/oauth1)
* [OAuth 2.0](/auth/oauth2-2.0/overview)
* [AWS Sig v4](/auth/aws-signature)
* [Basic Auth](/auth/basic)
* [Bearer Auth](/auth/bearer)
* [Digest Auth](/auth/digest)
* [NTLM](/auth/ntlm)
# Generating Reports
Source: https://docs.usebruno.com/bru-cli/builtInReporters
Bruno CLI provides built-in support for generating reports in three formats: **JSON**, **JUnit**, and **HTML**. These reports help with analyzing test results and integrating with various CI/CD tools.
You can generate any combination of these reports and even run them simultaneously.
### JSON Report
To generate a report in JSON format, use the `--reporter-json` option:
```bash copy theme={null}
bru run --reporter-json results.json
```
This will output the test results in a results.json file, which can be useful for further processing or programmatic analysis.
### JUnit Report
To generate a report in JUnit format, use the `--reporter-junit` option:
```bash copy theme={null}
bru run --reporter-junit results.xml
```
The results.xml file will be in a format compatible with JUnit, making it ideal for integration with CI/CD pipelines that rely on JUnit reporting.
### HTML Report
To generate a human-readable HTML report, use the `--reporter-html` option:
```bash copy theme={null}
bru run --reporter-html results.html
```
This will create an results.html file that provides a visual representation of the test outcomes, ideal for quick reviews.
### Running Multiple Reporters Simultaneously
You can generate multiple reports at once by specifying more than one reporter option. For example, to generate JSON, JUnit, and HTML reports simultaneously, run:
```bash copy theme={null}
bru run --reporter-json results.json --reporter-junit results.xml --reporter-html results.html
```
This command will create three files: `results.json, results.xml, and results.html`, allowing you to analyze the results in different formats as needed.
**Secret Masking in Reports:** When running CLI and generating reports, sensitive data like secrets can be automatically masked to prevent exposure. Learn more about [Secret Masking](/secrets-management/secret-masking).
## Skipping Specific Headers in the Report
If you want to exclude certain headers from the report, use the `--reporter-skip-headers` option. You can list multiple headers to skip, separated by spaces.
```bash theme={null}
bru run --reporter-html results.html --reporter-skip-headers "Authorization" "X-Auth-Token"
```
## Skip All Headers in the Report
To exclude all headers from the report, use the `--reporter-skip-all-headers` option. This will remove all headers from the output report, ensuring a cleaner result.
```bash copy theme={null}
bru run --reporter-html results.html --reporter-skip-all-headers
```
## Skipping Request and Response Bodies in the Report
Reports can include large request and response payloads. Use these flags to omit bodies and keep report files smaller:
| Option | Description |
| ------------------------------- | -------------------------------------------------------------- |
| `--reporter-skip-request-body` | Omits request bodies from reports |
| `--reporter-skip-response-body` | Omits response bodies from reports |
| `--reporter-skip-body` | Shorthand: omits both request and response bodies from reports |
**Behavior:**
| Flags used | Result |
| ----------------------------------------------------------------------- | ------------------------------------------------ |
| Both `--reporter-skip-request-body` and `--reporter-skip-response-body` | Omits both request and response bodies |
| Neither (default) | Reports include both request and response bodies |
**Examples:**
```bash copy theme={null}
# Omit only request bodies
bru run --reporter-html results.html --reporter-skip-request-body
# Omit only response bodies
bru run --reporter-html results.html --reporter-skip-response-body
# Omit both (shorthand)
bru run --reporter-html results.html --reporter-skip-body
```
# Command Options
Source: https://docs.usebruno.com/bru-cli/commandOptions
Bruno CLI provides a variety of command options to help you customize your API testing and execution process. These options allow you to specify environments, configure reports, handle security, and much more. Below is a comprehensive list of available options:
## Options
To use options, add them to the bru run command after you specify the collection file or folder:
```bash theme={null}
bru run [options]
```
To view a list of available options for Bruno CLI, run the following command:
```bash theme={null}
bru run -h
```
### Basic options
| Option | Details |
| -------------- | ------------------------- |
| `-h`, `--help` | Output usage information |
| `--version` | Output the version number |
### Setup options
| Option | Details |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--env [string]` | Specify environment to run with |
| `--global-env [string]` | Specify global/workspace-level environment to run with |
| `--workspace-path [string]` | Specify workspace path when collection is not located at the root |
| `--env-var [string]` | Overwrite a single environment variable, multiple usages possible |
| `--env-file [string]` | Path to the environment file (.bru or .json) to use for the collection run |
| `--sandbox [string]` | Specifies the JavaScript execution sandbox mode: `safe` (default) or `developer`.
**Note:** Starting from v3.0.0, the default is "safe" mode. Use `--sandbox=developer` to enable Developer Mode features. |
| `--csv-file-path` | CSV file to run the collection with |
| `--json-file-path` | Path to the JSON data file |
| `--iteration-count [number]` | Number of times to run the collection |
| `-r` | Indicates a recursive run \[boolean] \[default: false] |
### Request options
| Option | Details |
| ------------------------- | ----------------------------------------------------------------------- |
| `--delay [number]` | Delay between each requests (in milliseconds) |
| `--tests-only` | Only run requests that have tests or active assertions |
| `--bail` | Stop execution after a failure of a request, test, or assertion |
| `--tags [string]` | Only run requests that have ALL of the specified tags (comma-separated) |
| `--exclude-tags [string]` | Skip requests that have ANY of the specified tags (comma-separated) |
| `--parallel` | Run requests in parallel order |
### SSL & Security options
| Option | Details |
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `--cacert [string]` | CA certificate to verify peer against |
| `--ignore-truststore` | Use custom CA certificate exclusively and ignore default truststore \[boolean] \[default: false] |
| `--client-cert-config` | Path to the Client certificate config file used for securing the connection |
| `--insecure` | Allow insecure server connections |
| `--disable-cookies` | Automatically save and send cookies with requests \[boolean] \[default: false] |
| `--noproxy` | Disable all proxy settings (both collection-defined and system proxies) \[boolean] \[default: false] |
### Output & Reporting options
| Option | Details |
| ------------------------------- | ------------------------------------------------------------------------------------------- |
| `-o`, `--output [string]` | **\[DEPRECATED]** Path to write file results to. Use reporter options instead |
| `-f`, `--format [string]` | **\[DEPRECATED]** Format of the file results. Use reporter options instead |
| `--reporter-json [string]` | Path to generate a JSON report |
| `--reporter-junit [string]` | Path to generate a JUnit report |
| `--reporter-html [string]` | Path to generate an HTML report |
| `--reporter-skip-all-headers` | Skip all headers in the report \[boolean] \[default: false] |
| `--reporter-skip-headers` | Skip specific headers in the report \[array] \[default: \[]] |
| `--reporter-skip-request-body` | Omits request bodies from reports \[boolean] \[default: false] |
| `--reporter-skip-response-body` | Omits response bodies from reports \[boolean] \[default: false] |
| `--reporter-skip-body` | Shorthand: omits both request and response bodies from reports \[boolean] \[default: false] |
### Import options
Used with `bru import openapi` (and related import commands). Run `bru import openapi -h` for the full list.
| Option | Details |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-s`, `--source [string]` | Path or URL to the OpenAPI specification (YAML or JSON) |
| `-o`, `--output [string]` | Output **directory** for a collection on disk (folder import) |
| `-f`, `--output-file [string]` | Output **file** path for a single Bruno collection JSON export |
| `-n`, `--collection-name [string]` | Name for the imported collection |
| `--collection-format [string]` | **OpenAPI folder import only:** `bru` (classic `.bru` files) or `opencollection` ([OpenCollection YAML](/opencollection-yaml/overview)). Default: `opencollection`. |
| `--insecure` | Skip TLS verification when `--source` is an `https://` URL |
| `-g`, `--group-by [string]` | OpenAPI only: group requests by `tags` or `path` (default: `tags`) |
## Examples
This will run all the requests in your collection.
```bash copy theme={null}
bru run
```
You can also run specific files or folders:
```bash copy theme={null}
# Run a single file
bru run request.bru
# Run multiple files
bru run request1.bru request2.bru
# Run a folder
bru run
# Run multiple folders
bru run
# Mix of files and folders
bru run request1.bru request2.bru
```
## Next Steps
Now that you're familiar with the command options, explore these guides to get the most out of Bruno CLI:
1. [Command Examples](/bru-cli/runCollection) - Learn how to execute your collections with the above command options
2. [Import Data](/bru-cli/import) - Import OpenAPI and WSDL specifications into Bruno collections
3. [Generate Reports](/bru-cli/builtInReporters) - Create detailed test reports in multiple formats
***
## Support
If you encounter any issues or have any feedback or suggestions, please raise them on our [GitHub repository ↗](https://github.com/usebruno/bruno)
# Docker Integration
Source: https://docs.usebruno.com/bru-cli/docker
Run Bruno CLI in Docker containers — no Node.js installation required.
Bruno CLI is available as an official Docker image. Pull it, mount your collection, and run — no Node.js or npm needed on the host.
## Where to pull from
| Registry | Image | Pull command |
| ------------------------- | ---------------------- | ---------------------------------- |
| Docker Hub | `usebruno/cli` | `docker pull usebruno/cli` |
| GitHub Container Registry | `ghcr.io/usebruno/cli` | `docker pull ghcr.io/usebruno/cli` |
Both registries receive identical images on every release.
## Image variants
| Variant | Base image | Size | When to use |
| -------------------- | ---------------- | -------- | --------------------------------------------------- |
| **Alpine** (default) | `node:22-alpine` | \~141 MB | Best for most cases — smallest footprint. |
| **Debian** | `node:22-slim` | \~162 MB | When Alpine hits SSL or glibc compatibility issues. |
Both variants support `linux/amd64` and `linux/arm64`.
## Tags
| Style | Example | Behavior |
| ------ | --------------------- | ------------------------------------------ |
| Exact | `usebruno/cli:3.4.2` | Immutable — recommended for production CI. |
| Minor | `usebruno/cli:3.4` | Receives patch updates (3.4.x). |
| Major | `usebruno/cli:3` | Receives any 3.x.x update. |
| Latest | `usebruno/cli:latest` | Moves only when explicitly tagged. |
Unsuffixed tags (`:latest`, `:3.4.2`, `:3.4`, `:3`) always resolve to the **Alpine** variant. Append `-debian` for the Debian variant (e.g. `usebruno/cli:3.4.2-debian`).
## Quick start
Pull the image and verify it works:
```bash theme={null}
docker pull usebruno/cli:latest
docker run usebruno/cli --version
```
`docker pull` downloads the image to your machine. `docker run` starts a container from that image — anything after the image name (`usebruno/cli`) is passed as arguments to `bru`, so `--version` prints the CLI version.
Adding `--rm` to `docker run` automatically removes the container after it exits. It's not required, but keeps your system tidy by avoiding a buildup of stopped containers.
## Run a collection
The Docker container needs access to your collection files to run them. Use the `-v` (volume mount) flag to map a directory on your host to `/bruno` inside the container. The path should always be mapped to `/bruno` — that's the working directory the CLI expects.
Since the image's entrypoint is already set to `bru`, running `usebruno/cli run` is the same as running `bru run` on your local machine. Any arguments you pass after the image name go straight to the CLI.
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli run
```
```powershell theme={null}
docker run --rm -v ${PWD}:/bruno usebruno/cli run
```
```cmd theme={null}
docker run --rm -v %cd%:/bruno usebruno/cli run
```
### Run a subfolder or single request
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli run ./api-tests
docker run --rm -v $(pwd):/bruno usebruno/cli run ./api-tests/login.bru
```
### Choose an environment
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli run --env staging
```
### Pass environment variables
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli run --env local --env-var API_KEY=secret123
```
### Generate a report
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli run --reporter-junit results.xml
```
For all `bru run` options — environments, variables, reporters, tags, parallel execution, and more — see [Run Command Options](/bru-cli/runCollection).
### Collection at a different path
If your collection is not in the current directory, point Docker at its path:
```bash theme={null}
docker run --rm -v /path/to/your/collection:/bruno usebruno/cli run
```
## Two usage patterns
### Mount at runtime (no image build needed)
Best for local development and ad-hoc runs.
```bash theme={null}
cd ~/my-api-tests
docker run --rm -v $(pwd):/bruno usebruno/cli run
```
### Bake collection into a custom image
Best for CI pipelines and distributable test suites. Create a `Dockerfile` next to your `bruno.json`:
```dockerfile theme={null}
FROM usebruno/cli:3.4.2-alpine
COPY --chown=node:node . /bruno
CMD ["run", ".", "--env", "ci"]
```
Build and run:
```bash theme={null}
docker build -t my-team/api-tests:v1 .
docker run --rm my-team/api-tests:v1
```
The base image runs as `USER node` (UID 1000) for security. Use `--chown=node:node` in `COPY` so the runtime user can write reports back to the mount.
## CI/CD examples
### GitHub Actions
```yaml theme={null}
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Bruno collection
run: |
docker run --rm \
-v ${{ github.workspace }}:/bruno \
usebruno/cli:latest run \
--reporter-junit results.xml
- name: Publish test report
uses: dorny/test-reporter@v3
if: success() || failure()
with:
name: Bruno Test Results
path: results.xml
reporter: java-junit
```
### GitLab CI
```yaml theme={null}
api-tests:
image: usebruno/cli:latest
script:
- bru run --reporter-junit results.xml
artifacts:
reports:
junit: results.xml
```
### Docker Compose
```yaml theme={null}
services:
bruno-cli:
image: usebruno/cli:latest
volumes:
- ./collection:/bruno
- ./reports:/reports
command:
run .
-r
--env ci
--reporter-json /reports/results.json
--reporter-junit /reports/results.xml
--reporter-html /reports/results.html
```
```bash theme={null}
docker compose run bruno-cli
```
## Image details
| Property | Value |
| ----------------- | ---------------------------- |
| Entrypoint | `bru` |
| Working directory | `/bruno` |
| Runtime user | `node` (UID 1000, non-root) |
| Architectures | `linux/amd64`, `linux/arm64` |
## Troubleshooting
Files written by the container may appear owned by a foreign UID. Fix with:
```bash theme={null}
docker run --rm \
-v $(pwd):/bruno \
--user "$(id -u):$(id -g)" \
usebruno/cli run --env ci
```
macOS Docker Desktop handles UID translation automatically.
The musl libc in Alpine can occasionally cause issues with native Node modules or SSL. Switch to the Debian variant:
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli:3.4.2-debian run --env ci
```
The `-v` flag uses a colon separator that collides with Windows drive letters. Use `--mount` instead:
```bash theme={null}
docker run --rm \
--mount type=bind,source=C:\repo\collection,target=/bruno \
usebruno/cli run --env ci
```
Or run inside WSL where POSIX paths work directly.
Bruno CLI's `run` is non-recursive by default. If your collection has nested subfolders, add `-r`:
```bash theme={null}
docker run --rm -v $(pwd):/bruno usebruno/cli run my-folder -r --env staging
```
## Resources
* [Docker Hub — usebruno/cli](https://hub.docker.com/r/usebruno/cli)
* [GHCR — ghcr.io/usebruno/cli](https://github.com/usebruno/bruno/pkgs/container/cli)
* [Bruno CLI docs](/bru-cli/overview)
* [bruno-collections/bruno-cli-docker](https://github.com/bruno-collections/bruno-cli-docker) — ready-to-run demo
# Pairing with Downstream Actions
Source: https://docs.usebruno.com/bru-cli/github-actions/downstream-actions
Chain the Bruno CLI GitHub Action with EnricoMi, dorny, Slack, and other ecosystem actions for PR comments, artifacts, and notifications.
The [Bruno CLI GitHub Action](/bru-cli/github-actions/overview) emits clean JUnit XML. Downstream actions render it for PR comments, Checks tab UI, Step Summary, or artifact upload. The [action README](https://github.com/usebruno/bruno-cli-action#pairing-with-downstream-actions) documents all 13 canonical recipes. Common patterns:
## Upload reports as artifacts
Pass reporter flags in `command`, then chain `actions/upload-artifact`:
```yaml theme={null}
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml --reporter-html report.html'
- uses: actions/upload-artifact@v6
if: always()
with:
name: bruno-reports-${{ github.run_id }}
path: |
tests/payments/results.xml
tests/payments/report.html
```
To redact sensitive headers before upload, pass `--reporter-skip-headers` in `command`. See [Generating Reports](/bru-cli/builtInReporters).
**What this does:** Saves test report files (JUnit and HTML) as downloadable artifacts on the workflow run page. Anyone with repo access can download them for up to 90 days.
## Slack notification on failure
Use action outputs with `continue-on-error: true` so the notification step still runs:
```yaml theme={null}
- id: bruno
uses: usebruno/bruno-cli-action@v1
continue-on-error: true
with:
working-directory: tests/payments
command: 'run --env prod'
- if: steps.bruno.outputs.failed != '0'
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Bruno tests failed: ${{ steps.bruno.outputs.failed }}/${{ steps.bruno.outputs.total }} on ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```
**What this does:** Runs Bruno tests, but if any fail, sends a Slack message with the failure count. `continue-on-error: true` lets the Slack step run even when tests fail. `id: bruno` names the step so later steps can read its outputs (`steps.bruno.outputs.failed`).
## PR comment on every run
`EnricoMi/publish-unit-test-result-action` posts a sticky PR comment updated on re-runs, plus a check run with structured results.
```yaml theme={null}
permissions:
pull-requests: write
checks: write
contents: read
jobs:
bruno:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml'
- uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: tests/payments/results.xml
```
Use `if: always()` so EnricoMi runs even when the Bruno step fails. Otherwise users do not see the comment on failure.
**What this does:** Runs your Bruno tests, writes a JUnit report to `results.xml`, then posts a sticky comment on the PR with pass/fail details. The comment updates in place on re-runs instead of spamming new comments.
## PR comment only on failures
```yaml theme={null}
- uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: tests/payments/results.xml
comment_mode: failures
```
**What this does:** Same as above, but the PR comment only appears when something failed. Successful runs stay quiet.
## Checks tab UI (polyglot test stacks)
Use `dorny/test-reporter` when you want Bruno results alongside Jest, Pytest, or other JUnit-emitting suites:
```yaml theme={null}
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml'
- uses: dorny/test-reporter@v1
if: always()
with:
name: Bruno API tests
path: tests/payments/results.xml
reporter: java-junit
```
**What this does:** Shows Bruno test results as a separate check run in the PR Checks tab, the same way Jest or Pytest results appear. Good when your repo runs multiple test tools and you want a consistent UI.
## Matrix across environments
Use distinct JUnit paths and artifact names per matrix leg:
```yaml theme={null}
strategy:
matrix:
env: [staging, prod]
steps:
- uses: actions/checkout@v6
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env ${{ matrix.env }} --reporter-junit results-${{ matrix.env }}.xml'
- uses: actions/upload-artifact@v6
if: always()
with:
name: bruno-report-${{ matrix.env }}
path: tests/payments/results-${{ matrix.env }}.xml
```
Include the matrix key in both `--reporter-junit` and the artifact `name` to avoid duplicate-name errors across legs.
**What this does:** Runs the same collection twice in parallel, once per environment (`staging` and `prod`). Each run gets its own report file and artifact so results do not overwrite each other.
## Soft-fail
Record results without failing the build, then act on outputs:
```yaml theme={null}
- id: bruno
uses: usebruno/bruno-cli-action@v1
continue-on-error: true
with:
working-directory: tests/payments
command: 'run --env prod'
- if: steps.bruno.outputs.failed != '0'
run: ./notify-slack.sh ${{ steps.bruno.outputs.failed }}
```
The Bruno step still appears red on failure (honest signal), but downstream steps proceed.
**What this does:** Runs tests and records results, but does not stop the workflow when tests fail. Use this when you want to notify or log failures without blocking the entire pipeline.
## Enterprise mTLS
Write cert files from secrets, then pass paths in `command`:
```yaml theme={null}
- run: |
mkdir -p /tmp/certs && chmod 700 /tmp/certs
echo "$CA_CERT" > /tmp/certs/ca.pem
echo "$CLIENT_CERT_CONFIG" > /tmp/certs/client.json
env:
CA_CERT: ${{ secrets.API_CA_CERT }}
CLIENT_CERT_CONFIG: ${{ secrets.API_CLIENT_CERT_CONFIG }}
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --cacert /tmp/certs/ca.pem --client-cert-config /tmp/certs/client.json'
```
**What this does:** Writes TLS certificates from GitHub Secrets to temp files, then passes those file paths to Bruno so it can call APIs protected by mTLS (mutual TLS). Secrets never appear in logs.
**Forked PR caveat:** GitHub restricts `GITHUB_TOKEN` to read-only for `pull_request` events from forked repositories. PR comment actions need write permission, which may require `pull_request_target` with care. See [EnricoMi's fork PR docs](https://github.com/EnricoMi/publish-unit-test-result-action#support-fork-repositories-and-dependabot-branches).
# Manual CLI Setup
Source: https://docs.usebruno.com/bru-cli/github-actions/manual-setup
Run Bruno collections in GitHub Actions without the official action by installing the CLI yourself.
If you prefer not to use the [official GitHub Action](/bru-cli/github-actions/overview), install Bruno CLI yourself. This is the same pattern every team copied before the official action existed: checkout, setup Node, install `@usebruno/cli`, run `bru`.
## Prerequisites
* [Git](https://git-scm.com/downloads) installed
* A GitHub repository containing a Bruno workspace
* [Node.js](https://nodejs.org/) (for installing the Bruno CLI)
## Demo workspace
To follow along with the video, clone the [Bruno Automation Demo Workspace](https://github.com/bruno-collections/bruno-automation-demo-workspace) and open it in Bruno.
## Create the workflow
1. Create the workflow directory:
```bash theme={null}
mkdir -p .github/workflows
```
2. Create `.github/workflows/bruno-api-tests.yml`:
```yaml theme={null}
name: Bruno API Tests
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
jobs:
bruno-tests:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Install Bruno CLI
run: npm install -g @usebruno/cli
- name: Prepare report directory
run: mkdir -p reports
- name: Run Bruno demo collection
working-directory: collections/bruno-automation-demo
run: |
bru run \
--global-env ci \
--workspace-path ../.. \
--tags smoke,workflow,release-gate \
--env-var platform_name="GitHub Actions" \
--env-var build_id="${{ github.run_id }}" \
--env-var commit_sha="${{ github.sha }}" \
--reporter-html ../../reports/github-actions-report.html
- name: Upload Bruno report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v6
with:
name: bruno-report
path: reports/github-actions-report.html
if-no-files-found: error
```
### What this workflow does
| Step | Purpose |
| ----------------------------- | --------------------------------------------------------------------------- |
| **Check out repository** | Clones your repo so the runner has access to the workspace and collections. |
| **Set up Node.js** | Installs Node.js, which is required to run the Bruno CLI. |
| **Install Bruno CLI** | Installs `@usebruno/cli` globally via npm. |
| **Prepare report directory** | Creates a `reports/` folder for the HTML test report. |
| **Run Bruno demo collection** | Runs `bru run` from inside the collection directory. |
| **Upload Bruno report** | Saves the HTML report as a downloadable GitHub Actions artifact. |
### Key `bru run` flags
* **`--global-env ci`** activates the `ci` [global environment](/variables/global-environment-variables) defined in `environments/ci.yml` at the workspace root.
* **`--workspace-path ../..`** tells Bruno where the workspace root is relative to the collection directory. Required when running from inside a collection folder.
* **`--tags smoke,workflow,release-gate`** runs only requests tagged with these values.
* **`--env-var`** overrides environment variables at runtime, useful for injecting CI-specific values like the GitHub run ID and commit SHA.
* **`--reporter-html`** generates an HTML report of the test results.
For a full list of CLI options, see [Command Options](/bru-cli/commandOptions).
## Run the workflow
1. **Commit and push** your workflow file:
```bash theme={null}
git add .github/workflows/bruno-api-tests.yml
git commit -m "Add Bruno API test workflow"
git push origin main
```
2. **Monitor the workflow**:
* Go to your GitHub repository and click the **Actions** tab.
* The workflow runs automatically on pushes and pull requests to `main`, or trigger it manually with **workflow\_dispatch**.
3. **View the report**:
* Once the run completes, click into the workflow run.
* Download the **bruno-report** artifact from the Artifacts section.
* Open `github-actions-report.html` in your browser for a visual summary of all test results.
## Troubleshooting
Bruno CLI v3 changed the default sandbox to Safe Mode. If your tests rely on Node built-ins (`require`, `Buffer`, etc.), add `--sandbox developer` to your `bru run` command:
```yaml theme={null}
run: bru run --env ci --sandbox developer
```
The `bru` process crashed before writing JUnit, or wrote an empty report. Treat as a runtime error and check the step log for stderr. When using the official action, expand the `::group::` block in the action log to see the full `bru` invocation, including auto-injected flags.
If the run log shows a red **Install Bruno CLI** step, the failure is in `npm install -g @usebruno/cli`, not in your collection.
| stderr substring | Likely cause | Fix |
| ------------------------- | --------------------------------------------- | --------------------------------------- |
| `E404` / `404 Not Found` | `bru-version` does not exist on npm | Pick a real version or use `latest` |
| `ENOTFOUND` / `ETIMEDOUT` | Runner cannot reach `registry.npmjs.org` | Check corporate proxy or network policy |
| `EACCES` | Self-hosted runner missing install permission | Adjust npm prefix or permissions |
| `ENOSPC` | Self-hosted runner disk full | Clear disk space |
| `EINTEGRITY` | Corrupted npm cache | Run `npm cache clean --force` and retry |
| Code | Meaning |
| ----- | ------------------------------------------------------------------------------------------ |
| `0` | All requests, tests, and assertions passed |
| `1` | One or more requests, tests, or assertions failed |
| `4` | `bru` was invoked outside a collection root. Set `working-directory` to the collection dir |
| `6` | Environment file not found. Check `environments/` exists |
| `12` | `--global-env` used without `--workspace-path`. Add `--workspace-path` |
| `137` | Process killed (usually OOM). Use a bigger runner or split the collection |
See the [action README](https://github.com/usebruno/bruno-cli-action#exit-code-reference) for the full exit-code table.
Bruno honours `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. Set them via `env:` on the step or runner configuration.
# GitHub Actions
Source: https://docs.usebruno.com/bru-cli/github-actions/overview
Run Bruno collections in GitHub Actions workflows with the official Bruno CLI action.
[GitHub Actions](https://docs.github.com/en/actions) lets you automate API testing directly from your repository. Bruno ships an [official GitHub Action](https://github.com/marketplace/actions/bruno-cli) that installs `@usebruno/cli`, runs your `bru` command, and exposes machine-readable test counts. PR comments, annotations, artifact upload, and soft-fail behavior are handled by standard GitHub Actions ecosystem tools documented in [Pairing with downstream actions](/bru-cli/github-actions/downstream-actions).
## Official GitHub Action
| Property | Value |
| ----------- | ------------------------------------------------------------------------- |
| Marketplace | [Bruno CLI](https://github.com/marketplace/actions/bruno-cli) |
| Repository | [usebruno/bruno-cli-action](https://github.com/usebruno/bruno-cli-action) |
| Reference | `usebruno/bruno-cli-action@v1` |
The action is a composite action. It prepends `bru` to your `command` input, auto-injects `--reporter-junit` when absent, parses the JUnit summary, and exposes `exit-code`, `passed`, `failed`, `total`, and `duration-ms` as step outputs. The workflow step fails naturally when `bru` exits non-zero.
Write `run --env prod`, not `bru run --env prod`. The action prepends `bru` for you.
## Quick start
```yaml theme={null}
name: API Tests
on: [pull_request, push]
jobs:
bruno:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod'
```
This minimal workflow installs the CLI, runs the collection, and populates count outputs. The step turns red on assertion failure and green on success. No PR comments, annotations, Step Summary, or uploaded artifacts are created by default. See [Pairing with downstream actions](/bru-cli/github-actions/downstream-actions) for those patterns.
## How this workflow works
A GitHub Actions workflow is a YAML file in `.github/workflows/`. Each line tells GitHub what to run and when. Here is what the quick start example means:
| Line | What it does |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name: API Tests` | Display name shown in the GitHub Actions tab. You can pick any name. |
| `on: [pull_request, push]` | When to run. This workflow triggers on every push and pull request. |
| `jobs:` | A workflow is made of one or more jobs. |
| `bruno:` | Job name. This is an identifier you choose (like `bruno` or `api-tests`). |
| `runs-on: ubuntu-latest` | The virtual machine GitHub spins up to run your steps. `ubuntu-latest` is a standard Linux runner. |
| `steps:` | Ordered list of tasks inside the job. They run top to bottom. |
| `uses: actions/checkout@v6` | A reusable GitHub Action that clones your repository onto the runner so Bruno can read your collection files. |
| `uses: usebruno/bruno-cli-action@v1` | Bruno's official action. Format is `owner/repo@version`: `usebruno` is the org, `bruno-cli-action` is the action repo, `@v1` pins the major version (gets compatible updates automatically). |
| `with:` | Inputs you pass into the action, like function arguments. |
| `working-directory: tests/payments` | The folder on the runner where Bruno runs. Point this at your collection root (the folder that contains `opencollection.yml` or `bruno.json`). Think of it as `cd tests/payments` before running `bru`. |
| `command: 'run --env prod'` | The CLI command without the `bru` prefix. The action prepends `bru`, so this becomes `bru run --env prod`. `--env prod` selects the `prod` environment from your collection. |
In plain terms: GitHub checks out your code, moves into your collection folder, installs Bruno CLI, runs your collection against the `prod` environment, and reports pass or fail.
## Inputs
| Input | Required | Default | Description |
| ------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `command` | yes | — | The `bru` subcommand and flags (e.g. `run --env prod`). Reporter flags are optional; `--reporter-junit` is auto-injected if absent. |
| `bru-version` | no | `latest` | Version of `@usebruno/cli` to install. |
| `working-directory` | no | `.` | Shell working directory. Typically the Bruno collection root. |
CLI flags such as `--env`, `--env-var`, `--tags`, `--bail`, `--sandbox`, and `--reporter-*` go in `command`, not as separate action inputs. Every CLI flag works the day it ships in the CLI.
## Outputs
Available as `${{ steps..outputs. }}` in subsequent steps:
| Output | Description |
| ------------- | ---------------------------------------------------------------- |
| `exit-code` | Exit code from the `bru` process |
| `passed` | Number of passed requests |
| `failed` | Number of failed requests (assertion failures or runtime errors) |
| `total` | Total requests run |
| `duration-ms` | Total run duration in milliseconds |
Report file paths are intentionally not outputs. Pass an explicit `--reporter-junit ` in `command` and reference that path in downstream steps.
## Behavior
**JUnit auto-injection (always-on).** If `command` does not contain `--reporter-junit`, the action appends `--reporter-junit "$RUNNER_TEMP/bruno-junit.xml"` before invoking `bru`. A minimal `command: 'run'` still produces count outputs. If you pass `--reporter-junit some/path.xml`, the action uses your path.
**Exit code propagation.** The step succeeds on exit `0` and fails on non-zero. To continue the workflow past failures, use GitHub's built-in `continue-on-error: true` on the step.
## Versioning
| Tag | Behavior |
| --------- | ---------------------------------------------------------------- |
| `@v1` | Floating major tag. Receives every backwards-compatible release. |
| `@v1.2.3` | Immutable. Pinned to a specific release. |
The `v` tag is retagged automatically on every published release.
### Workspace structure
The demo workspace is organized as a Bruno workspace with an OpenCollection layout:
```
bruno-automation-demo-workspace/
├── .github/
│ └── workflows/
│ └── bruno-api-tests.yml
├── collections/
│ └── bruno-automation-demo/
│ ├── 01-smoke-checks/
│ ├── 02-ci-workflow/
│ ├── 03-release-gates/
│ ├── environments/
│ │ ├── ci.bru
│ │ ├── local.bru
│ │ └── staging.bru
│ └── opencollection.yml
├── environments/
│ ├── ci.yml
│ ├── local.yml
│ └── staging.yml
├── workspace.yml
└── reports/
```
Key items:
* **`workspace.yml`** defines the workspace and points to the collection at `collections/bruno-automation-demo`.
* **`environments/ci.yml`** is a [global environment](/variables/global-environment-variables) with variables like `bruno_echo_url`, `platform_name`, and `build_id`.
* **`collections/bruno-automation-demo/`** contains folders of requests with tests and assertions.
### Run the demo with the action
```yaml theme={null}
name: Bruno API Tests
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
jobs:
bruno-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: mkdir -p reports
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: collections/bruno-automation-demo
command: >
run
--global-env ci
--workspace-path ../..
--tags smoke,workflow,release-gate
--env-var platform_name="GitHub Actions"
--env-var build_id="${{ github.run_id }}"
--env-var commit_sha="${{ github.sha }}"
--reporter-html ../../reports/github-actions-report.html
- uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: bruno-report
path: reports/github-actions-report.html
if-no-files-found: error
```
**What this does:** A fuller real-world example using the demo workspace above. It runs tagged requests with a global environment, injects CI variables (`build_id`, `commit_sha`), generates an HTML report, and uploads it as an artifact.
## Other CI platforms
Bruno CLI also works on Jenkins, Azure DevOps, GitLab CI, and Bitbucket Pipelines via direct CLI invocation or the [Bruno CLI Docker image](/bru-cli/docker). See [Jenkins Integration](/bru-cli/jenkins) for Jenkins-specific examples.
## Resources
* [Bruno CLI on GitHub Marketplace](https://github.com/marketplace/actions/bruno-cli)
* [usebruno/bruno-cli-action](https://github.com/usebruno/bruno-cli-action) (source and full recipe list)
* [GitHub discussion #2635](https://github.com/usebruno/bruno/discussions/2635)
* [Pairing with downstream actions](/bru-cli/github-actions/downstream-actions)
* [Manual CLI setup](/bru-cli/github-actions/manual-setup)
* [Bruno CLI Overview](/bru-cli/overview)
* [Command Options](/bru-cli/commandOptions)
* [Generating Reports](/bru-cli/builtInReporters)
* [Global Environment Variables](/variables/global-environment-variables)
* [GitHub Actions Documentation](https://docs.github.com/en/actions)
# Import Data
Source: https://docs.usebruno.com/bru-cli/import
Bruno CLI allows you to import API specifications directly into Bruno collections from the command line, which can be integrated into CI/CD pipelines whenever API changes are committed.
## Importing OpenAPI specifications
### Collection format (`--collection-format`)
When you import OpenAPI into a **directory** (`--output`), you choose how the collection is laid out on disk:
| Value | Result |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `opencollection` | [OpenCollection YAML](/opencollection-yaml/overview) layout (for example `opencollection.yml` and `.yml` requests). **This is the default** if you omit the flag. |
| `bru` | Classic Bruno **`.bru`** files and folder structure. |
Use one of:
```bash theme={null}
--collection-format=bru
--collection-format=opencollection
```
### Option 1: Import to a Bruno collection folder
This imports the OpenAPI specification (YAML or JSON) and writes a collection under the output directory.
**OpenCollection layout (default):**
```bash copy theme={null}
bru import openapi \
--source \
--output \
--collection-name "Petstore API"
```
**Explicit OpenCollection layout:**
```bash copy theme={null}
bru import openapi \
--source \
--output \
--collection-name "Petstore API" \
--collection-format=opencollection
```
**Classic `.bru` layout:**
```bash copy theme={null}
bru import openapi \
--source \
--output \
--collection-name "Petstore API" \
--collection-format=bru
```
Where:
* ``: Path or URL to your OpenAPI spec (YAML or JSON)
* ``: Directory for the generated collection
* `--collection-name`: Display name for the collection
* `--collection-format`: Optional; `opencollection` (default) or `bru`
You can also pass `-s` / `-o` / `-n` as shorthand for `--source`, `--output`, and `--collection-name`.
### Option 2: Import to a single JSON file
This imports the OpenAPI specification and writes one Bruno collection JSON file at the given path.
```bash copy theme={null}
bru import openapi \
--source \
--output-file .json \
--collection-name "Petstore API"
```
Where:
* ``: Path or URL to your OpenAPI spec (YAML or JSON)
* `.json`: Path for the exported collection file
* `--collection-name`: Custom name for the collection
## Importing WSDL (SOAP APIs)
This imports a WSDL file and generates a Bruno collection in the output directory.
```bash copy theme={null}
bru import wsdl \
--source \
--output \
--collection-name "SOAP Service"
```
Where:
* ``: Path or URL to your WSDL file
* ``: Directory where the collection should be created
* `--collection-name`: Optional name for the collection
# Installation
Source: https://docs.usebruno.com/bru-cli/installation
Make sure you have Node.js installed on your local system. It is recommended to use the latest LTS version (Node 18 or higher).
To install the Bruno CLI, use the node package manager of your choice:
### Using pnpm
```bash theme={null}
pnpm install -g @usebruno/cli
```
### Using npm
```bash theme={null}
npm install -g @usebruno/cli
```
### Using yarn
```bash theme={null}
yarn global add @usebruno/cli
```
For more details, visit the official [NPM Page for Bruno CLI ↗](https://www.npmjs.com/package/@usebruno/cli)
## Docker
If you prefer not to install Node.js on the host, official Docker images are published for the Bruno CLI:
```bash theme={null}
docker pull usebruno/cli:latest
```
See [Docker](/bru-cli/docker) for variants, tags, and usage examples.
# Jenkins CI Integration
Source: https://docs.usebruno.com/bru-cli/jenkins
[Jenkins](https://www.jenkins.io/) is a powerful open-source automation server that enables continuous integration and continuous delivery (CI/CD) for software development workflows. It provides a robust platform for automating build, test, and deployment processes across multiple environments and platforms.
Explore our Jenkins setup collection to see practical examples and test Jenkins CI/CD integration:
## Prerequisites
* A running Jenkins instance (local or remote).
* The [NodeJS Plugin](https://plugins.jenkins.io/nodejs/) installed in your Jenkins environment.
* A Git repository containing your Bruno collections and a `Jenkinsfile`.
## Automate API Testing with Jenkins
### Step 1: Organize Your Bruno Collections
Ensure your Bruno collections and environment files are properly organized within your Git repository. A typical structure looks like this:
```
your-api-project/
├── Jenkinsfile
├── collections/
│ ├── authentication/
│ │ ├── login.bru
│ │ └── logout.bru
├── environments/
│ ├── development.bru
│ ├── ci.bru
│ └── production.bru
└── bruno.json
```
### Step 2: Configure Node.js in Jenkins
Before running Bruno CLI, you need to ensure Jenkins can access a Node.js runtime environment.
1. **Install the NodeJS Plugin:**
* Navigate to `Manage Jenkins` > `Plugins`.
* Select the `Available plugins` tab, search for "NodeJS", and install it.
2. **Configure a Node.js Installation:**
* Go to `Manage Jenkins` > `Tools` (under `System Configuration`).
* Scroll down to the `NodeJS installations` section.
* Click `Add NodeJS`.
* Provide a descriptive **Name** (e.g., `Node.js 18`). This exact name will be used in your Jenkinsfile.
* Check `Install automatically` and select your desired `Node.js version` (e.g., `NodeJS 18.20.8`).
* **Crucially, ensure the checkbox "Provide Node & npm folder to PATH" is checked.** This ensures `node` and `npm` commands are available to your pipeline.
* Click `Save`.
### Step 3: Create Your Jenkinsfile
Create a file named `Jenkinsfile` in the root of your Git repository. This file defines the steps of your CI pipeline.
```groovy theme={null}
// Sample Jenkinsfile
pipeline {
agent any
environment {
// Capture the path to Node.js 18. This variable will be available globally.
// This requires the Node.js tool to be configured in Jenkins.
NODE_HOME = tool 'Node.js 18'
}
stages {
stage('Checkout Code') {
steps {
checkout scm
}
}
stage('Setup Node.js & Install Bruno CLI') {
steps {
// Ensure the bin directory is added to PATH for all subsequent sh commands
withEnv(["PATH+NODE=${NODE_HOME}/bin"]) {
echo 'Verifying Node.js and npm versions...'
sh 'node -v'
sh 'npm -v'
echo 'Installing Bruno CLI globally...'
sh 'npm install -g @usebruno/cli'
sh 'bru --version'
}
}
}
stage('Run API Tests') {
steps {
withEnv(["PATH+NODE=${NODE_HOME}/bin"]) { // Still need this or declare it inside script block
echo 'Executing Bruno API tests...'
sh 'bru run --env ci --reporter-html results.html'
}
}
}
stage('Archive Test Results') {
steps {
echo 'Archiving test report...'
archiveArtifacts artifacts: 'results.html', fingerprint: true
}
}
}
post {
always {
echo 'Pipeline finished.'
}
success {
echo 'API Tests Passed Successfully!'
}
failure {
echo 'API Tests Failed! Check console output for details.'
}
}
}
```
### Step 4: Configure Jenkins Pipeline Job
1. Create a New Jenkins Job:
* From the Jenkins Dashboard, click New Item.
* Enter an Item name (e.g., bruno-api-tests-pipeline).
* Select Pipeline as the item type and click OK.
2. Configure Job Details:
* In the job configuration page, navigate to the Pipeline section.
* Set Definition to Pipeline script from SCM and Choose your SCM (e.g., Git).
* Enter your Repository URL (e.g., `https://github.com/your-org/your-repo.git`).
* Specify any Credentials if your repository is private.
* Set Branches to build to \***/main** (or the branch containing your Jenkinsfile).
* Ensure Script Path is Jenkinsfile (this is the default).
3. Click Save to finalize your job configuration.
### Step 5: Run and Monitor Your Pipeline
1. Trigger a Build:
* From your Jenkins pipeline job page, click Build Now (in the left-hand menu) to manually start the pipeline.
2. Monitor Progress:
* Observe the Build History section to see the status of your builds (green checkmark for success, red cross mark for failure).
* Click on a specific build number, then select Console Output to view real-time logs and detailed execution steps.
3. View Test Results:
* Once a build that generated results.html completes, navigate to that specific build's page.
* Look for the **Artifacts** section (usually on the left sidebar or at the top of the build summary).
* Click on `results.html` to download the report, then open it in your web browser for detailed test outcomes.
## Learn More
For more advanced Jenkins features and configurations, visit the [Jenkins documentation](https://www.jenkins.io/doc/).
# Bruno CLI
Source: https://docs.usebruno.com/bru-cli/overview
With Bruno CLI, you can run your API collections with ease using simple command line commands.
This makes it easier to test your APIs in different environments, automate your testing process, and integrate your API tests with your continuous integration and deployment workflows.
New to the CLI? Start with the [Quick Start](/bru-cli/quick-start) walkthrough.
**v3.0.0 Breaking Change**
Starting from Bruno CLI v3.0.0, the default runtime mode has changed from **Developer Mode** to **Safe Mode** for improved security. If you need to use Developer Mode features (external npm packages, filesystem access), you must explicitly pass the `--sandbox=developer` flag when running collections.
## Key Features of Bruno CLI:
**1. Execute API Requests & Collections**: Run individual API requests or entire collections directly from the command line.
**2. Generate Test Reports** : Easily create reports in multiple formats, including JSON, JUnit, and HTML, to analyze and share test results.
**3. CI/CD Integration**: Effortlessly integrate with CI/CD pipelines for automated testing and validation.
# Proxy and mTLS
Source: https://docs.usebruno.com/bru-cli/proxyConfiguration
Bruno CLI allows you to configure proxy settings directly from the terminal. You can also add client certificates through the CLI.
Follow the steps below to set up configurations quickly and efficiently.
### Disabling Proxies
If you need to bypass all proxy settings (both collection-defined and system proxies) when running requests, you can use the `--noproxy` flag:
```bash copy theme={null}
bru run --noproxy
```
The flag completely disables all proxy settings for that particular CLI run.
## Using Client Certificates for API Requests
If your API requests require client certificates for authentication, you can specify using the `--client-cert-config` option. The configuration should be provided in a JSON file. Here's an example of how to use this option:
```bash copy theme={null}
bru run --client-cert-config /path/to/client-cert-config.json
```
The client-cert-config.json file should contain the following fields:
```json theme={null}
{
"enabled": true,
"certs": [
{
"domain": "usebruno.com",
"type": "cert",
"certFilePath": "certs/server_1.crt",
"keyFilePath": "private/server_1.key",
"passphrase": "Iu$eBrun0_#Secure!"
},
{
"domain": "the-example.com",
"type": "pfx",
"pfxFilePath": "pfx/server_3.pfx",
"passphrase": "L!ghT_Y@g@mi_2024!"
}
]
}
```
# Quick Start
Source: https://docs.usebruno.com/bru-cli/quick-start
Bruno CLI lets you run API collections from the terminal — ideal for automation, CI/CD pipelines, and repeatable test runs. This guide walks you through installing the CLI, running collections, generating reports, and integrating with your workflow.
## Prerequisites
* A basic understanding of HTTP and REST
* **[Node.js](https://nodejs.org/en/download/)** (v18 LTS or higher recommended)
* **[Git](https://git-scm.com/install/)** (to clone the sample collection)
## Getting started
Use the **[Bruno Starter Guide](https://github.com/bruno-collections/bruno-starter-guide)** collection as the hands-on sample for this walkthrough. It contains example requests, tests, assertions, and a `local` environment — the same collection used in the [Bruno Quick Start](/introduction/quick-start).
Clone the repository:
```bash theme={null}
git clone https://github.com/bruno-collections/bruno-starter-guide.git
cd bruno-starter-guide
```
Use the **[Bruno Starter Guide](https://github.com/bruno-collections/bruno-starter-guide)** as a reference while following this guide. If a step fails, open the collection in Bruno to compare your setup with the expected request configuration.
Work through the steps **in order**. Each step assumes the previous one.
***
## 1. Install Bruno CLI
**Objective:** Install Bruno CLI globally and verify it is available in your terminal.
```bash theme={null}
npm install -g @usebruno/cli
```
```bash theme={null}
pnpm install -g @usebruno/cli
```
```bash theme={null}
yarn global add @usebruno/cli
```
Verify the installation:
```bash theme={null}
bru --version
```
You should see the installed version number (for example, `3.4.2`).
**Success Criteria:**
* `bru` command is available in your terminal.
* `bru --version` prints a version number without errors.
More: [Installation](/bru-cli/installation)
***
## 2. Run the entire collection
**Objective:** Execute all requests in the Bruno Starter Guide collection from the command line.
Make sure you are inside the cloned collection directory (the folder containing `opencollection.yml`):
```bash theme={null}
cd path/to/bruno-starter-guide
bru run
```
Bruno CLI runs each request sequentially and prints a summary table when finished:
```
01-github-api (200 OK) - 471 ms
02-echo-bru (getaddrinfo ENOTFOUND {{baseurl}})
03-script-request (200 OK) - 1186 ms
...
📊 Execution Summary
┌───────────────┬────────────────────────┐
│ Metric │ Result │
├───────────────┼────────────────────────┤
│ Status │ ✗ FAIL │
├───────────────┼────────────────────────┤
│ Requests │ 5 (4 Passed, 1 Failed) │
└───────────────┴────────────────────────┘
```
Some requests may fail without an environment — that is expected. You will fix this in the next step.
**Success Criteria:**
* `bru run` executes without installation errors.
* CLI output lists all five requests in the collection.
* An execution summary table is displayed at the end.
More: [Command Examples](/bru-cli/runCollection)
***
## 3. Run with an environment
**Objective:** Run the collection using the `local` environment so variable placeholders like `{{baseURL}}` resolve correctly.
The Bruno Starter Guide includes a `local` environment at `environments/local.yml` with a `baseURL` variable. Activate it with the `--env` flag:
```bash theme={null}
bru run --env local
```
With the environment applied, requests that depend on `{{baseURL}}` resolve and their tests and assertions run:
```
02-echo-bru (200 OK) - 1067 ms
Tests
✓ body has title
✓ 200 status code
Assertions
✓ res.status: eq 200
✓ res.body.title: eq Bruno
```
**Success Criteria:**
* `bru run --env local` completes with **✓ PASS** in the execution summary.
* Tests and assertions for `02-echo-bru` pass (green checkmarks).
* All five requests show a successful status.
More: [Environment Variables](/variables/environment-variables)
***
## 4. Run a single request
**Objective:** Run one specific request instead of the entire collection.
Specify the request file path after `bru run`:
```bash theme={null}
bru run 01-github-api.yml
```
Bruno CLI executes only that request:
```
01-github-api (200 OK) - 148 ms
📊 Execution Summary
┌───────────────┬──────────────┐
│ Status │ ✓ PASS │
│ Requests │ 1 (1 Passed) │
└───────────────┴──────────────┘
```
You can also run multiple files or folders in one command:
```bash theme={null}
bru run 01-github-api.yml 03-script-request.yml
```
**Success Criteria:**
* Only the specified request runs.
* Execution summary shows **1 (1 Passed)**.
* Response status is **200 OK**.
***
## 5. Run requests with tests only
**Objective:** Filter the collection run to requests that have tests or active assertions.
Use the `--tests-only` flag to skip requests without tests:
```bash theme={null}
bru run --env local --tests-only
```
Only `02-echo-bru` (which has tests and assertions) is executed:
```
02-echo-bru (200 OK) - 1158 ms
Tests
✓ body has title
✓ 200 status code
Assertions
✓ res.status: eq 200
✓ res.body.title: eq Bruno
📊 Execution Summary
│ Requests │ 1 (1 Passed) │
│ Tests │ 2/2 │
│ Assertions │ 2/2 │
```
**Success Criteria:**
* Only requests with tests or assertions are executed.
* Tests show **2/2** passed.
* Assertions show **2/2** passed.
More: [Command Options](/bru-cli/commandOptions)
***
## 6. Generate test reports
**Objective:** Export test results to HTML, JSON, and JUnit report files.
Run the collection with reporter flags to write results to disk:
```bash theme={null}
bru run --env local \
--reporter-html results.html \
--reporter-json results.json \
--reporter-junit results.xml
```
When the run completes, Bruno CLI writes the report files:
```
Wrote html results to results.html
Wrote json results to results.json
Wrote junit results to results.xml
```
Open `results.html` in a browser for a visual summary. Use `results.json` for programmatic analysis or `results.xml` for CI tools that consume JUnit format.
**Success Criteria:**
* `results.html`, `results.json`, and `results.xml` are created in the current directory.
* HTML report opens in a browser and shows request results.
* JSON and XML files contain test execution data.
More: [Generating Reports](/bru-cli/builtInReporters)
***
## 7. Data-Driven Testing
**Objective:** Create a CSV or JSON data file, use row values in the `02-echo-bru` request, and run it from the CLI with an HTML report.
The Bruno Starter Guide includes `02-echo-bru`, a POST request to the [echo.usebruno.com](https://echo.usebruno.com) endpoint. You will send a different JSON body on each iteration using data from a file.
### Update the request in Bruno
1. Open the **Bruno Starter Guide** collection in Bruno.
2. Open the **`02-echo-bru`** request.
3. Select the **`local`** environment (top-right).
4. Go to the **Body** tab and update the JSON to use placeholders that match your data file columns:
```json theme={null}
{
"title": "{{title}}",
"msg": "{{msg}}"
}
```
5. Save the request.
The data file does not replace your request body automatically. Each column (CSV) or key (JSON) becomes an iteration variable. Use `{{columnName}}` in the body so Bruno sends that row's values on each run.
### Create a data file
Create **one** of the following files in your collection folder (`bruno-starter-guide/`):
Create `users.csv`:
```csv theme={null}
title,msg
Bruno,Loved by developers. Built for developers.
Opensource,Git-friendly API client.
CLI,Run collections from the terminal.
```
Create `users.json`:
```json theme={null}
[
{ "title": "Bruno", "msg": "Loved by developers. Built for developers." },
{ "title": "Opensource", "msg": "Git-friendly API client." },
{ "title": "CLI", "msg": "Run collections from the terminal." }
]
```
### Try it in the Bruno app (optional)
1. Open the collection **Runner** (runner icon in the top right corner).
2. Attach `users.csv` or `users.json` as the data file.
3. Select the **`local`** environment and run **`02-echo-bru`**.
4. Review the **Timeline** to confirm each iteration sent a different `title` and `msg`.
### Run from the CLI
From the collection directory, run `02-echo-bru` once per data row and write an HTML report:
```bash theme={null}
bru run --env local 02-echo-bru.yml \
--csv-file-path users.csv \
--reporter-html results.html
```
```bash theme={null}
bru run --env local 02-echo-bru.yml \
--json-file-path users.json \
--reporter-html results.html
```
Bruno CLI runs the request three times (once per row) and creates `results.html`:
```
02-echo-bru (200 OK) - 1067 ms
02-echo-bru (200 OK) - 1042 ms
02-echo-bru (200 OK) - 1038 ms
📊 Execution Summary
│ Status │ ✓ PASS │
│ Requests │ 3 (3 Passed) │
Wrote html results to results.html
```
Open `results.html` in your browser for a visual summary of all three iterations. See [Generating Reports](/bru-cli/builtInReporters) for JSON, JUnit, and other reporter options.
**Success Criteria:**
* `users.csv` or `users.json` exists in the collection folder with `title` and `msg` columns.
* `02-echo-bru` body uses `{{title}}` and `{{msg}}`.
* CLI run completes with **3 (3 Passed)** in the execution summary.
* `results.html` is created and shows one result per data row.
More: [Data Driven Testing](/testing/automate-test/data-driven-testing) · [Command Examples](/bru-cli/runCollection#running-a-collection-with-a-csv-file) · [Generating Reports](/bru-cli/builtInReporters)
***
## 8. Override environment variables
**Objective:** Pass or override environment variables at runtime without editing environment files.
Use the `--env-var` flag to set variables on the command line. This is useful for secrets and CI-specific values:
```bash theme={null}
bru run --env local --env-var baseURL=https://echo.usebruno.com
```
You can chain multiple overrides:
```bash theme={null}
bru run --env local \
--env-var baseURL=https://echo.usebruno.com \
--env-var API_KEY=your-key-here
```
Variables marked as secrets in the Bruno app are not accessible via the CLI. Pass them directly with `--env-var`.
**Success Criteria:**
* Collection runs successfully with overridden variables.
* No changes are required to environment files on disk.
More: [Command Examples](/bru-cli/runCollection#passing-environment-variables)
***
## 9. Import an OpenAPI specification
**Objective:** Create a Bruno collection from an OpenAPI spec using the CLI.
Bruno CLI can import OpenAPI documents directly into a collection folder — useful for bootstrapping collections or CI pipelines that sync with API specs:
```bash theme={null}
bru import openapi \
--source https://petstore3.swagger.io/api/v3/openapi.json \
--output ./petstore-api \
--collection-name "Petstore API"
```
Bruno CLI fetches the spec, converts it, and writes the collection:
```
Reading OpenAPI specification from https://petstore3.swagger.io/api/v3/openapi.json...
Converting OpenAPI specification to Bruno format...
Bruno collection created at ./petstore-api
```
Navigate into the new collection and run it:
```bash theme={null}
cd petstore-api
bru run
```
**Success Criteria:**
* `petstore-api` directory is created with `opencollection.yml` and request files.
* `bru run` inside the imported collection executes without import errors.
More: [Import Data](/bru-cli/import)
***
## 10. Automate with CI/CD
**Objective:** Understand how Bruno CLI fits into automated testing pipelines.
Bruno CLI is designed for CI/CD. A typical GitHub Actions workflow:
1. Checks out your repository
2. Installs `@usebruno/cli`
3. Runs `bru run` with environment and reporter flags
4. Uploads the HTML report as a build artifact
Example workflow snippet:
```yaml theme={null}
- name: Install Bruno CLI
run: npm install -g @usebruno/cli
- name: Run Bruno collection
working-directory: collections/my-api
run: |
bru run \
--env ci \
--reporter-html ../../reports/test-report.html \
--reporter-junit ../../reports/test-results.xml
```
**Success Criteria:**
* You understand the basic CI/CD pattern for Bruno CLI.
* You know where to find a complete GitHub Actions example.
More: [GitHub Actions Integration](/bru-cli/gitHubCLI) · [Jenkins Integration](/bru-cli/jenkins) · [Docker](/bru-cli/docker)
***
Congratulations on completing the Bruno CLI Quick Start! You have learned how to install the CLI, run collections, use environments, generate reports, run data-driven tests, import OpenAPI specs, and integrate with CI/CD.
**Next steps:**
* [Command Options](/bru-cli/commandOptions) — full list of CLI flags
* [Command Examples](/bru-cli/runCollection) — data-driven testing, tags, and parallel runs
* [Data Driven Testing](/testing/automate-test/data-driven-testing) — CSV/JSON iteration in the app and CLI
* [Generating Reports](/bru-cli/builtInReporters) — HTML, JSON, and JUnit reporters
* [Proxy Configuration](/bru-cli/proxyConfiguration) — run collections behind a proxy
* [Bruno Quick Start](/introduction/quick-start) — build the same collection in the Bruno app
# Command Examples
Source: https://docs.usebruno.com/bru-cli/runCollection
Bruno CLI allows you to run your API collections with ease, either by directly executing requests or using external data sources.
This guide explains how to run entire collections, specific folders, and how to use data sources like CSV and JSON files to drive your API tests.
**v3.0.0 Breaking Change**
Starting from Bruno CLI v3.0.0, the default runtime mode is **Safe Mode**. If your collection requires Developer Mode features (external npm packages, filesystem access), pass the `--sandbox=developer` flag: `bru run --sandbox=developer`
## Basic Collection Execution
To run an entire collection, navigate to your collection directory and use the `run` command:
```bash copy theme={null}
bru run
```
## Running a Folder within a Collection
You can run all the requests within a specific folder by specifying the folder name:
```bash copy theme={null}
bru run
```
For example, to run all requests in the **users** folder:
```bash copy theme={null}
bru run users
```
## Running a Collection with a CSV File
This feature requires [Bruno CLI ↗](https://www.npmjs.com/package/@usebruno/cli) version 1.35.0 or higher.
If you need to run a collection using data from a CSV file, specify the path to the file with the `--csv-file-path` option:
```bash copy theme={null}
bru run --csv-file-path /path/to/csv/file.csv
```
This will execute the collection once for each row in the CSV file, with each row's data available as variables in your requests.
## Running a Collection with a JSON File
To run a collection using data from a JSON file, provide the file path using the `--json-file-path` option:
```bash copy theme={null}
bru run --json-file-path /path/to/json/file.json
```
## Running a Collection Multiple Times
You can run a collection multiple times in a single command using the `--iteration-count` flag:
```bash copy theme={null}
bru run --iteration-count=2
```
This will execute the collection twice. This is useful for load testing or when you need to repeat the same set of requests multiple times.
## Running a Collection with Environments
You can run a collection using environment variables from either a `.bru` file or a `.json` file. This allows you to attach environments via the CLI from anywhere in the filesystem.
### Using Environment Files
To run a collection with an environment file, use the `--env-file` option:
```bash copy theme={null}
bru run --env-file /path/to/environment.bru
```
You can specify either a relative or absolute path to the environment file:
```bash copy theme={null}
# Using relative path
bru run --env-file ./environments/local.bru
# Using absolute path
bru run --env-file /Users/username/projects/api-testing/environments/prod.bru
```
The environment file should be in Bruno's `.bru` format. Make sure the file contains valid environment variables and their values.
### Using JSON Environment Files
This feature requires [Bruno CLI ↗](https://www.npmjs.com/package/@usebruno/cli) version 2.13.0 or higher.
Bruno CLI now supports JSON environment files, which is particularly useful for global environments created in the Bruno app. This bridges the gap between UI-only global environments and CLI-based workflows.
To use a JSON environment file:
```bash copy theme={null}
bru run --env-file /path/to/environment.json
```
#### JSON Environment File Format
The JSON environment file should follow Bruno's environment schema:
```json copy theme={null}
{
"name": "My Environment",
"variables": [
{
"name": "host",
"value": "https://api.example.com",
"enabled": true
},
{
"name": "api_key",
"value": "your-api-key-here",
"enabled": true
}
]
}
```
### Using Environments Names
If you need to use a specific environment, you can pass it with the `--env` option:
```bash copy theme={null}
bru run --env Local
```
### Using Global/Workspace-Level Environments
Bruno CLI now supports referencing global/workspace-level environments when running a collection. This feature allows you to use environment variables defined at the workspace level rather than at the collection level.
#### Using Global Environments
Use the `--global-env` flag to reference a global/workspace-level environment:
```bash copy theme={null}
bru run --global-env Beta
```
#### Specifying Workspace Path
When your collection is not located at the workspace root, use the `--workspace-path` flag to specify the workspace path:
```bash copy theme={null}
bru run --global-env Beta --workspace-path path/to/workspace/from/collection/root
```
#### Combined Usage
You can combine global environments with collection-level environments:
```bash copy theme={null}
bru run --env Local --global-env Production
```
#### Passing Environment Variables
Variables marked as secrets in Bruno app are not accessible via the CLI. Pass them directly as command-line arguments.
```bash copy theme={null}
bru run --env Local --env-var JWT_TOKEN=1234
```
## Multiple Environment Variables
You can override multiple environment variables by using additional `--env-var` flags:
```bash copy theme={null}
bru run --env Local --env-var JWT_TOKEN=1234 --env-var API_KEY=abcd1234
```
Each `--env-var` flag adds or overrides a single environment variable, and you can chain as many as needed.
## Filtering Requests with Tags
Bruno CLI supports filtering requests by tags, allowing you to run only specific subsets of your collection based on tag criteria.
### Include Tags
Run only requests that have at least one matching tag.
```bash copy theme={null}
bru run --tags=smoke,sanity
```
### Exclude Tags
Skip requests that have ANY of the specified tags:
```bash copy theme={null}
bru run --exclude-tags=skip,draft
```
### Combined Filtering
You can combine include and exclude filters:
```bash copy theme={null}
bru run --tags=smoke,sanity --exclude-tags=skip,draft
```
## Parallel Execution and Progress Tracking
Bruno CLI supports running requests in parallel and displaying real-time progress during collection execution.
### Parallel Execution
By default, Bruno CLI runs requests sequentially. You can enable parallel execution using the `--parallel` flag:
```bash copy theme={null}
bru run --iteration-count 2 --parallel
```
# Language Design
Source: https://docs.usebruno.com/bru-lang/language
A Bru file is made up of blocks.
There are three kinds of blocks
* Dictionary block
* Text block
* Array block
### Dictionary block
A dictionary block contains a set of key value pairs.
```bash theme={null}
get {
url: https://api.textlocal.in/send
}
headers {
content-type: application/json
Authorization: Bearer 123
~transaction-id: {{transactionId}}
}
```
Any key in the dictionary block can be prefixed with `~` to indicate that it is disabled.
### Text block
A text block is a set of lines
```bash theme={null}
body {
{
"hello": "world"
}
}
tests {
expect(res.status).to.equal(200);
}
```
### Array block
An array block is a list of strings
```bash theme={null}
vars:secret [
access_key,
access_secret,
~transactionId
]
```
Any key in the array block can be prefixed with `~` to indicate that it is disabled.
# Bru Markup Language
Source: https://docs.usebruno.com/bru-lang/overview
Bruno will continue to fully support `.bru` files. However, [OpenCollection YAML](/opencollection-yaml/overview) is now the recommended format for new collections. OpenCollection is an open specification by Bruno that uses the industry-standard YAML format, enabling seamless integration with existing tooling and workflows.
Bru is a simple markup language that utilizes plain text files to document and organize information for your API requests.
The API requests in your collections are stored as plain text files using this language.
This allows you to save your API collections in a folder within your code repository, and use your preferred version control system to manage and share them with your team. Collaboration on your API collections can be done through pull requests, as the human-readable file format makes it easy for developers to understand the changes made to the API collection.
Below is a sample of a Bru file for a `GET` request with some query params
You can checkout the sample repository which contains GitHub rest API collection [here](https://github.com/usebruno/github-rest-api-collection)
If you are wondering why we designed a DSL instead of just using JSON/YAML, you can checkout this [github discussion](https://github.com/usebruno/bruno/discussions/360)
# Samples
Source: https://docs.usebruno.com/bru-lang/samples
Here are a few sample Bru files.
## GET
```bash theme={null}
get {
url: https://api.github.com/users/usebruno
}
```
## GET with headers
```bash theme={null}
get {
url: https://api.textlocal.in/send?apiKey=secret&numbers=9988776655&message=hello
}
headers {
content-type: application/json
Authorization: Bearer topsecret
}
```
## POST with body
```bash theme={null}
post {
url: https://api.textlocal.in/send
}
body {
{
"apiKey": "secret",
"numbers": "9988776655",
"message": "Woof! lets play with some apis"
}
}
headers {
content-type: application/json
Authorization: Bearer topsecret
}
```
## Scripting
```bash theme={null}
post {
url: https://api.textlocal.in/login
}
body {
{
"username": "johnnash",
"password": "governingdynamics"
}
}
script:post-response {
bru.setVar("token", res.body.token);
}
```
## Testing
```bash theme={null}
post {
url: https://api.textlocal.in/login
}
body {
{
"username": "johnnash",
"password": "governingdynamics"
}
}
tests {
test("should be able to login", function() {
expect(res.status).to.equal(201);
});
test("should receive the token", function() {
expect(res.body.token).to.be.a('string');
});
}
```
# Syntax Highlighting Support
Source: https://docs.usebruno.com/bru-lang/syntax-highlighting
Bruno has an editor extension released for VS Code that enables syntax highlighting for `.bru` files. You can download it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=bruno-api-client.bruno).
# Bru Tag Reference
Source: https://docs.usebruno.com/bru-lang/tag-reference
## meta
Store metadata about your request
```bash theme={null}
meta {
name: Get users
type: http
seq: 1
tags: [
smoke
sanity
]
}
```
The `seq` is used to store the sequence number. This decides the sort position of your request in the UI.
The `type` can be either `http` or `graphql`
The `tags` is an array of strings that can be used to filter requests during collection runs. Tags are useful for organizing and selectively running requests based on categories like environment, functionality, or priority.
## get
Make a `GET` http call
```bash theme={null}
get {
url: https://api.github.com/users/usebruno
}
```
## post
Make a `POST` http call
```bash theme={null}
post {
url: https://api.github.com/users/usebruno
}
```
## put
Make a `PUT` http call
```bash theme={null}
put {
url: https://api.github.com/users/usebruno
}
```
## delete
Make a `DELETE` http call
```bash theme={null}
delete {
url: https://api.github.com/users/usebruno
}
```
## options
Make a get `OPTIONS` call
```bash theme={null}
options {
url: https://api.github.com/users/usebruno
}
```
## trace
Make a `TRACE` http call
```bash theme={null}
trace {
url: https://api.github.com/users/usebruno
}
```
## connect
Make a `CONNECT` http call
```bash theme={null}
connect {
url: https://api.github.com/users/usebruno
}
```
## head
Make a `HEAD` http call
```bash theme={null}
head {
url: https://api.github.com/users/usebruno
}
```
## query
The request query params
```bash theme={null}
get {
url: https://api.textlocal.in/send?apiKey=secret&numbers=9988776655&message=hello
}
params:query {
apiKey: secret
numbers: 9988776655
message: hello
}
```
## path
The request path params
```bash theme={null}
get {
url: https://api.textlocal.in/user/:userId
}
params:path {
userId: 13
}
```
## headers
The request query headers
```bash theme={null}
get {
url: https://api.textlocal.in/send?apiKey=secret&numbers=9988776655&message=hello
}
headers {
content-type: application/json
Authorization: Bearer topsecret
}
```
## body
The request body (defaults to json)
```bash theme={null}
body {
{
username: 'john',
password: 'governingdynamics'
}
}
```
## body:text
The request body as text
```bash theme={null}
body:text {
This is a text body
}
```
## body:xml
The request body as xml
```bash theme={null}
body:xml {
John30
}
```
## body:form-urlencoded
The request body as form-urlencoded
```bash theme={null}
body:form-urlencoded {
apikey: secret
numbers: +91998877665
~message: hello
}
```
## body:multipart-form
The request body as multipart-form
```bash theme={null}
body:multipart-form {
apikey: secret
numbers: +91998877665
~message: hello
}
```
## body:graphql
The request body as graphql
```bash theme={null}
body:graphql {
{
launchesPast {
launch_site {
site_name
}
launch_success
}
}
}
```
## body:graphql:vars
The request body as graphql vars
```bash theme={null}
body:graphql:vars {
{
"limit": 5
}
}
```
## script:pre-request
The request body as pre-request
```bash theme={null}
script:pre-request {
req.setHeader("Authorization", "{{token}}");
}
```
## script:post-response
The request body as post-response
```bash theme={null}
script:post-response {
bru.setVar("token", res.body.token);
}
```
## test
The tests
```bash theme={null}
body:test {
expect(res.status).to.equal(200);
}
```
# Convert Insomnia collection to Bruno collection
Source: https://docs.usebruno.com/converters/insomnia-to-bruno
```javascript theme={null}
const { insomniaToBruno } = require('@usebruno/converters');
const brunoCollection = insomniaToBruno(insomniaCollection);
```
```javascript theme={null}
const { insomniaToBruno } = require('@usebruno/converters');
const { readFile, writeFile } = require('fs/promises');
const path = require('path');
async function convertInsomniaToBruno(inputFile, outputFile) {
try {
const inputData = await readFile(inputFile, 'utf8');
const brunoCollection = await insomniaToBruno(JSON.parse(inputData));
await writeFile(outputFile, JSON.stringify(brunoCollection, null, 2));
console.log('Insomnia conversion successful!');
} catch (error) {
console.error('Error during Insomnia conversion:', error);
}
}
convertInsomniaToBruno(
path.join(__dirname, 'demo_collection.insomnia_collection.json'),
path.join(__dirname, 'demo_collection.bruno_collection.json')
);
```
# Convert OpenAPI to Bruno collection
Source: https://docs.usebruno.com/converters/openapi-to-bruno
### Convert OpenAPI JSON to Bruno collection
```javascript theme={null}
const { openApiToBruno } = require('@usebruno/converters');
const brunoCollection = openApiToBruno(openApiSpecification);
```
```javascript theme={null}
const { openApiToBruno } = require('@usebruno/converters');
const { readFile, writeFile } = require('fs/promises');
async function convertOpenApiToBruno(inputFile, outputFile) {
try {
const jsonContent = await readFile(inputFile, 'utf8');
const openApiSpec = JSON.parse(jsonContent);
const brunoCollection = openApiToBruno(openApiSpec);
await writeFile(outputFile, JSON.stringify(brunoCollection, null, 2));
console.log('OpenAPI JSON conversion successful!');
} catch (error) {
console.error('Error during OpenAPI JSON conversion:', error);
}
}
convertOpenApiToBruno('path/to/openapi-spec.json', 'path/to/bruno-collection.json');
```
### Convert OpenAPI YAML to Bruno collection
```javascript theme={null}
const { openApiToBruno, yamlToJson } = require('@usebruno/converters');
const { readFile, writeFile } = require('fs/promises');
async function testOpenApiConversion(yamlFile, outputFile) {
const yamlContent = await readFile(yamlFile, 'utf8');
const jsonSpec = yamlToJson(yamlContent);
if (jsonSpec) {
try {
const brunoCollection = openApiToBruno(jsonSpec);
await writeFile(outputFile, JSON.stringify(brunoCollection, null, 2));
console.log('Full conversion pipeline successful!');
} catch (error) {
console.error('Bruno conversion error:', error.message);
}
}
}
testOpenApiConversion('path/to/your/openapi.yaml', 'path/to/bruno-collection.json');
```
# Bruno Converters
Source: https://docs.usebruno.com/converters/overview
Bruno converter is a standalone NPM package that provides programmatic conversion of various API specification formats to Bruno collections. This package allows you to convert Postman collections, Insomnia collections, OpenAPI specifications, WSDL files, and Postman environments into Bruno collection format.
## Installation
Make sure you have Node.js (Node 18 or higher) installed on your local system. It is recommended to use the latest LTS version.
To install the Bruno Converters, use the node package manager of your choice:
### Using pnpm
```bash theme={null}
pnpm install @usebruno/converters
```
### Using npm
```bash theme={null}
npm install @usebruno/converters
```
### Using yarn
```bash theme={null}
yarn add @usebruno/converters
```
For more details, visit the official [NPM Page for Bruno Converters](https://www.npmjs.com/package/@usebruno/converters)
## Conversions
1. [Postman to Bruno](./postman-to-bruno)
2. [Insomnia to Bruno](./insomnia-to-bruno)
3. [OpenAPI to Bruno](./openapi-to-bruno)
4. [WSDL to Bruno](./wsdl-to-bruno)
## API Reference
### `postmanToBruno(postmanCollection: object)`
Converts a Postman collection to a Bruno collection.
**Parameters:**
* `postmanCollection`: The Postman collection JSON object
### `postmanToBrunoEnvironment(postmanEnvironment: object)`
Converts a Postman environment to a Bruno environment.
**Parameters:**
* `postmanEnvironment`: The Postman environment JSON object
### `insomniaToBruno(insomniaCollection: object)`
Converts an Insomnia collection to a Bruno collection.
**Parameters:**
* `insomniaCollection`: The Insomnia collection JSON object
### `openApiToBruno(openApiSpec: object)`
Converts an OpenAPI specification to a Bruno collection.
**Parameters:**
* `openApiSpec`: The OpenAPI specification JSON object
### `wsdlToBruno(wsdlContent: string)`
Converts a WSDL file to a Bruno collection with SOAP requests.
**Parameters:**
* `wsdlContent`: The WSDL file content as a string
# Postman Conversions
Source: https://docs.usebruno.com/converters/postman-to-bruno
### Convert Postman collection to Bruno collection
```javascript theme={null}
const { postmanToBruno } = require('@usebruno/converters');
const brunoCollection = postmanToBruno(postmanCollection);
```
```javascript theme={null}
const { postmanToBruno } = require('@usebruno/converters');
const { readFile, writeFile } = require('fs/promises');
const path = require('path');
async function convertPostmanToBruno(inputFile, outputFile) {
try {
const inputData = await readFile(inputFile, 'utf8');
const brunoCollection = postmanToBruno(JSON.parse(inputData));
await writeFile(outputFile, JSON.stringify(brunoCollection, null, 2));
console.log('Conversion successful!');
} catch (error) {
console.error('Error during conversion:', error);
}
}
convertPostmanToBruno(
path.join(__dirname, 'demo_collection.postman_collection.json'),
path.join(__dirname, 'demo_collection.bruno_collection.json')
);
```
### Convert Postman Environment to Bruno Environment
```javascript theme={null}
const { postmanToBrunoEnvironment } = require('@usebruno/converters');
const brunoEnvironment = postmanToBrunoEnvironment(postmanEnvironment);
```
```javascript theme={null}
const { postmanToBrunoEnvironment } = require('@usebruno/converters');
const { readFile, writeFile } = require('fs/promises');
const path = require('path');
async function convertPostmanEnvironment(inputFile, outputFile) {
try {
const inputData = await readFile(inputFile, 'utf8');
const brunoEnvironment = await postmanToBrunoEnvironment(JSON.parse(inputData));
await writeFile(outputFile, JSON.stringify(brunoEnvironment, null, 2));
console.log('Environment conversion successful!');
} catch (error) {
console.error('Error during environment conversion:', error);
}
}
convertPostmanEnvironment(
path.join(__dirname, 'demo_environment.postman_environment.json'),
path.join(__dirname, 'demo_environment.bruno_environment.json')
);
```
# Convert WSDL to Bruno Collection
Source: https://docs.usebruno.com/converters/wsdl-to-bruno
Bruno supports importing WSDL (Web Services Description Language) files to automatically generate SOAP API collections. This feature is particularly useful when working with legacy SOAP-based web services, as it eliminates the need for manual configuration of SOAP requests.
WSDL import is available in Bruno v2.14.0 and later versions.
## What Gets Generated
When you import a WSDL file, Bruno automatically:
* **Parses the WSDL structure** and extracts all available service operations
* **Creates a structured collection** with operations organized as individual `.bru` requests
* **Generates SOAP envelope templates** for each operation based on the XML schema definitions
* **Pre-configures required headers** including `Content-Type: text/xml` and `SOAPAction`
* **Includes XML schema documentation** as comments within the request body
## Import WSDL via UI
### Step 1: Access Import Collection
1. Click on the three-dot menu next to any collection in the sidebar
2. Select **Import Collection** from the dropdown menu
### Step 2: Choose WSDL Option
1. In the Import Collection dialog, select the **WSDL** option
2. You can either:
* Click the upload area to browse for a local `.wsdl` file
* Drag and drop a WSDL file directly into the dialog
### Step 3: Import and Review
1. After selecting your WSDL file, Bruno will parse it and generate the collection
2. A new folder will be created containing all the SOAP operations
3. Each operation will be a separate request with a pre-configured SOAP envelope
Bruno automatically validates the WSDL structure during import. If there are any issues, you'll receive clear error messages.
## Import WSDL via CLI
You can also import WSDL files using the Bruno CLI, which is useful for automation and CI/CD workflows.
### Basic Command
```bash theme={null}
bru import wsdl path/to/service.wsdl --output ./my-collection
```
### Example
```bash theme={null}
# Import a WSDL file
bru import wsdl ./services/calculator.wsdl --output ./calculator-collection
# Import with nested directory structure
bru import wsdl ./wsdl-files/user-service.wsdl --output ./collections/soap/users
```
For detailed CLI import options and examples, see the [CLI Import Guide](/bru-cli/import).
## Programmatic Conversion
For advanced use cases, you can use the `@usebruno/converters` package to programmatically convert WSDL to Bruno collections.
### Installation
```bash theme={null}
npm install @usebruno/converters
```
### Basic Usage
```javascript theme={null}
const { wsdlToBruno } = require('@usebruno/converters');
const { readFile, writeFile } = require('fs/promises');
async function convertWsdlToBruno(wsdlFile, outputFile) {
try {
// Read the WSDL file
const wsdlContent = await readFile(wsdlFile, 'utf8');
// Convert to Bruno collection
const brunoCollection = await wsdlToBruno(wsdlContent);
// Write the output
await writeFile(outputFile, JSON.stringify(brunoCollection, null, 2));
console.log('WSDL conversion successful!');
} catch (error) {
console.error('Error during WSDL conversion:', error.message);
}
}
// Convert WSDL to Bruno
convertWsdlToBruno('path/to/service.wsdl', 'path/to/bruno-collection.json');
```
## Generated Request Structure
Each SOAP operation in the imported collection will have the following structure:
### Request Headers
```
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://example.com/ServiceName/OperationName"
```
### Request Body
```xml theme={null}
valuevalue
```
# Dev Tools
Source: https://docs.usebruno.com/debugging/dev-tools
Bruno provides built-in Dev Tools to help you debug your APIs, catch bugs, test functionality, and get all essential details related to your API requests.
## Script errors
When a pre-request script, post-response script, or test fails, Bruno displays an inline error card directly in the response pane. Each error card includes:
* **Error source** -- identifies whether the error originated from a request, folder, or collection-level script, with a clickable file path that navigates you to the exact script location.
* **Code snippet** -- shows the lines of code surrounding the error, with the failing line highlighted. Line numbers match what you see in the script editor.
* **Error message** -- displays the error type and message (e.g., `ReferenceError: undefinedVar is not defined`).
* **Stack trace** -- a collapsible section with the full stack trace for deeper investigation.
If errors occur in multiple script phases (for example, both a pre-request script and a test), each error gets its own card. You can dismiss individual error cards by clicking the close button.
## Dev Tools tabs
The Dev Tools feature four main tabs:
1. **Console** - For testing, scripting, and debugging
2. **Network** - For analyzing network configurations and performance
3. **Performance** - For monitoring request performance metrics
4. **Terminal** - For executing git commands and terminal operations
The Dev Tools are available in [Bruno 2.8.0](https://www.usebruno.com/downloads) or higher versions.
## Accessing Dev Tools
The Dev Tools are located in the bottom-right corner of the interface, next to the cookies button. Simply click on the Dev Tools icon to open and start using the debugging features.
## Console
The Console tab is your primary debugging companion in Bruno. It provides a powerful environment for:
* **Testing and Scripting**: Execute JavaScript code and test your API logic
* **Debugging**: Troubleshoot API issues and scripting problems
* **Logging**: View console outputs, errors, and debugging information
* **Interactive Development**: Similar to browser developer tools but customized for Bruno
## Network
The Network tab provides comprehensive insights into your API requests and responses. It's essential for:
* **Security Validation**: Review security headers and authentication details
* **Request/Response Inspection**: Examine headers, body, and status codes
* **Network Configuration**: Verify proxy settings and connection details
* **Troubleshooting**: Identify network-related issues and errors
This tab gives you complete visibility into the network layer of your API interactions, helping you validate response data and understand the full request lifecycle.
## Performance
The Performance tab allows you to monitor Bruno's application performance and system resource usage. It provides real-time insights into:
* **CPU Usage**: Monitor current CPU consumption by Bruno
* **Memory Usage**: Track current memory usage of the application
* **Uptime**: View how long the Bruno process has been running
* **Process ID**: Identify the specific process ID for debugging purposes
* **Process View**: Switch between different process views (Browser, GPU, Utility, Tab)
This tab helps you monitor Bruno's resource consumption and identify any performance issues with the application itself, ensuring smooth operation during your API development workflow.
## Terminal
The Terminal tab provides a command-line interface within Bruno for executing git commands and terminal operations. It's useful for:
* **Git Operations**: Execute git commands directly from Bruno (e.g., git checkout, git branch)
* **Version Control**: Manage your API collection's version control without leaving Bruno
* **Workflow Integration**: Streamline your development workflow by combining API testing with git operations
* **Session Management**: View and manage multiple terminal sessions
This tab integrates git functionality directly into your development environment, making it easier to manage collections that are version-controlled.
# Timeline
Source: https://docs.usebruno.com/debugging/timeline
Inspect the main request, script-triggered HTTP calls, and console output in one chronological view.
**Timeline** tab shows everything that happened during a request run. It includes the main HTTP call, any extra requests made from scripts, and the resolved request details for each step.
## Timeline Interface
The Timeline tab is organized into three categories:
1. **Request**
2. **Response**
3. **Network Logs**
### 1. Request Tab
The Request tab displays everything that was sent with your API request:
* **URL** - The complete endpoint URL
* **Query Parameters** - Query strings and path variables
* **Request Body** - The data payload sent with the request
* **Request Headers** - All headers included in the request
* **Authentication** - Authentication details and credentials
* **Variables** - Variables used in the request
### 2. Response Tab
The Response tab shows all information received from the server:
* **Status Code** - HTTP status code of the response
* **Response Headers** - All headers returned by the server
* **Response Body** - The complete response data
* **Response Size** - Size of the response payload
* **Execution Time** - Time taken to complete the request
### 3. Network Logs Tab
The Network Logs tab provides low-level network insights for advanced debugging:
* **Request/Response Flow** - Detailed timeline of the network transaction
* **Network Timing** - Breakdown of connection, DNS, SSL, and transfer times
* **Protocol Details** - HTTP version and connection information
* **Debugging Information** - Additional network-level diagnostics
This comprehensive view helps you identify performance bottlenecks, debug connection issues, and understand the complete lifecycle of your API requests.
## Script Logs in Timeline
When scripts use [`bru.sendRequest()`](/testing/script/javascript-reference#brusendrequestoptions-callback) or [`bru.runRequest()`](/testing/script/javascript-reference#brurunrequestrequestpathname), Timeline lists every HTTP call made during the run. Use the filter tabs to narrow the view.
The number on each tab is how many requests match that filter.
| Tab | What it shows |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **All** | Every HTTP request from the run including the main request plus any calls made from scripts (with `sendRequest` / `runRequest`). |
| **Request** | Only the **main request** — the HTTP call defined in the request URL bar (method, URL, body, and headers you configured in Bruno). |
| **Pre-Request** | Only HTTP requests executed during the **pre-request script** phase (from `bru.sendRequest()` or `bru.runRequest()` before the main request is sent). |
| **Post-Response** | Only HTTP requests executed during the **post-response** or **test** script phase (from `bru.sendRequest()` or `bru.runRequest()` after the main response returns). |
Click a row to expand it and inspect that call's request body, headers, response, and network timing. Use **Clear Timeline** to reset the list before the next run.
# Billing
Source: https://docs.usebruno.com/license-administrators/billing
License Administrators can manage everything related to their Bruno subscription's billing directly from the License Management Portal. Bruno partners with [Stripe ↗](https://stripe.com/) to securely process payments and manage your billing details.
## Accessing Billing
* Navigate to `Settings`
* Select `Billing`
Here you can view the type of plan, number of licenses, and your subscription dates.
## Managing Billing
To make changes to your subscription, payment details, or invoices:
* From the `Billing` page, click `Manage Billing`
You'll be taken to a secure billing portal hosted by Stripe, where you can perform all of the actions below.
### View outstanding invoices
Review your invoice history and view or download any outstanding or past invoices.
### Update your payment method
If you're paying by credit card, you can add, update, or remove a card on file.
### Update billing and shipping information
Modify the billing and shipping information associated with your account, such as your company name, address, and tax details.
### Change your plan
Update your subscription to adjust your plan or the number of licenses associated with your account.
All payment and billing changes are processed securely through Stripe. Bruno does not store your credit card details.
# License Management Portal
Source: https://docs.usebruno.com/license-administrators/license-portal
Bruno provides a licensing management portal for the purposes of:
* Provisioning and deprovisioning licenses
* Managing License Administrators associated with your account
* Viewing details related to your Bruno subscription
## Accessing the Portal
If you've been designated as a License Administrator, either through purchasing on our website or through assignment by another admin:
* Navigate to [https://license.usebruno.com/](https://license.usebruno.com/)
* Enter your email address and press `continue`
* Retrieve and enter the OTP (one time password) that was sent to your email
* You have now accessed the portal!
## Assigning licenses
### Adding a single user
* Select `Add User`
* Enter the name and email of the user you'd like to add
* Click `Create`
* The user will automatically receive an email with their license key
### Adding users in bulk
> Ultimate licenses come with the ability to add users via a CSV upload
* Click ellipsis next to `Add User`
* Select `Import Users`
* *optionally download the sample CSV to view the format for user upload*
* Click `Upload`and select your file
* Click `Add Users`
* Users will automatically receive an email with their respective license key
## Removing licenses
### Removing a Single User
* Click on the trash can icon in the user's row
* Select `Delete`
### Remove Users in bulk
* Click ellipsis next to `Add User`
* Select `Delete Users`
* *optionally download the sample CSV to view the format for user deletion*
* Click `Upload`and select your file
* Click `Delete Users`
## Adding License Administrators
The number of License Administrators you can have associated with your account is based on your [plan type ↗](https://www.usebruno.com/pricing).
* Navigate to `Settings`
* Click on `Admins` page
* Select `Add Admin`
* Enter their name and email
* Select `Add`
## View Audit Logs
You can view the audit logs for your account by:
* Navigate to `Settings`
* Select `Audit Logs`
## View Plan Details
You can view the type of plan, number of licenses, and subscription dates by:
* Navigate to `Settings`
* Select `Billing`
# Configure SAML SSO with Microsoft Entra ID
Source: https://docs.usebruno.com/license-administrators/saml-sso/configure-saml-sso-with-entra-id
This guide walks you through configuring SAML Single Sign-On for Bruno using Microsoft Entra ID (formerly Azure Active Directory) as your identity provider.
Before you begin, make sure you have completed the [prerequisites](./overview#prerequisites) and have admin access to both Microsoft Entra ID and the Bruno License Portal.
## Configure SSO in Bruno
Before configuring a SAML application in Microsoft Entra ID, first configure SSO in Bruno.
1. Log in to the [Bruno License Portal](https://license.usebruno.com/)
2. Navigate to **Settings** → **SSO** in the left sidebar
3. Toggle the **Enable SSO** switch on
4. Note the following values (you'll need these when configuring Entra ID):
* **SAML ACS URL**: Copy this URL exactly as shown in Bruno
* **SP Issuer ID / Entity ID**: Set your own unique identifier (e.g., `bruno-sso`, `bruno-entra`, `your-company-bruno`, etc.)
Keep this page open in a separate tab - you'll return here after configuring Entra ID to complete the Bruno SSO setup.
## Configure SSO with Microsoft Entra ID
### Step 1: Create a New Enterprise Application
1. Log in to the [Microsoft Entra admin center](https://entra.microsoft.com/)
2. On the left sidebar, navigate to **Enterprise apps** → **+ New application**
3. Click **Create your own application**
4. Enter the application name: Set your own unique identifier (e.g., `Bruno`, `Bruno-SAML-App`, etc.)
5. Select **Integrate any other application you don't find in the gallery (Non-gallery)**
6. Click **Create**
### Step 2: Select SAML as Single Sign-On Method
1. In the created Enterprise Application, navigate to **Manage** → **Single sign-on** in the left sidebar
2. Select **SAML** as the single sign-on method
### Step 3: Configure Basic SAML Configuration
1. Under **Single sign-on** section in the **Basic SAML Configuration** section, click **Edit**
2. Copy the values from the Bruno SSO settings page and paste them into your SAML configuration in Microsoft Entra ID:
* **Identifier (Entity ID)**: Paste the **SP Issuer ID / Entity ID** value from the Bruno License Portal
* **Reply URL (Assertion Consumer Service URL)**: Copy and paste the **SAML ACS URL** from the Bruno License Portal
3. Click **Save**
**Critical**: The Entity ID in Entra ID must match EXACTLY what you configured in Bruno's **SP Issuer ID / Entity ID** field. A mismatch will cause authentication failures.
### Step 4: Configure Attributes & Claims
Bruno requires three specific SAML attributes to be configured in Entra ID:
* `Unique User Identifier (Name ID)`, `roles`, and `fullName`
These attributes map user information from Entra ID to your Bruno subscription, ensuring users get the correct access levels.
**How Attribute Mapping Works:**
* **User Identification**: Bruno uses the email address (NameID) to match the SSO user with existing Bruno users in your subscription
* **Role Assignment**: The `roles` attribute determines whether the user gets admin or standard access in Bruno
* **Profile Information**: The `fullName` attribute populates the user's display name in Bruno
#### Required Claims
In the **Attributes & Claims** section:
1. Click **Edit**
2. Delete any existing claims that are not on this list
3. You will now update the claims to match the following:
| Claim Name | Source Attribute | Notes |
| ------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Unique User Identifier (Name ID)** | `user.mail` | Required for user identification. |
| **roles** or **groups** | `user.assignedroles`, `user.groups`, or other attribute | Use `roles` with App Roles (`user.assignedroles`), OR use `groups` via "Add a group claim" for AD/Entra groups. See details below. |
| **fullName** | Transformation: `user.givenname + " " + user.surname` or equivalent attribute | Represents the combined user's first and last name. |
#### Configuring the `Unique User Identifier (Name ID)` claim
This claim is required for user identification. It will be mapped to the user's email address in Entra ID.
2. Click the `Unique User Identifier` claim
3. Select the **Source Attribute** option
4. Choose `user.mail` as the attribute
5. Click **Save**
#### Configuring Role or Group Claims
Bruno needs to know what role a user should have (admin or standard user). You can send this information using **either** of two approaches — choose the one that fits your organization:
* **Option A: Use App Roles or User Attributes** — Create a `roles` claim mapped to App Roles, user attributes like `user.department`, or static values
* **Option B: Use Entra ID Group Claims** — Send existing AD/Entra ID group memberships directly in the SAML assertion
Both approaches are equally valid. Choose based on how your organization already manages access.
#### Option A: Using App Roles or User Attributes
The `roles` claim will represent the user's roles in Bruno. This role value will be mapped to Bruno access levels for the License Portal and for License Activation. These roles could be hardcoded, mapped to existing user attributes, originate from [App Roles](https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-use-app-roles-customers#app-roles), etc.
On the **Attributes & Claims** page:
1. Click **Add new claim**
2. Enter `roles` as the name
3. Configure the claim as follows:
* **Map to existing user attribute**:
* Under **Source**, select **Attribute**
* Choose an existing user attribute like `user.assignedroles`, `user.department`, `user.jobtitle`, or custom attributes
4. Click **Save**
**Important**: The role value sent by Entra ID will be mapped to Bruno access levels in the License Portal's SSO Settings. You'll configure which role values correspond to admin or user access in Bruno (see Step 2 in the Bruno configuration section below).
#### Option B: Using Entra ID Group Claims
If your organization already has Active Directory or Entra ID groups set up for managing access (e.g., `Bruno-Admins`, `Bruno-Users`), you can use **Group Claims** to send group membership directly in the SAML assertion.
Bruno's backend recognizes SAML attributes named `role`, `roles`, `group`, or `groups` (case-insensitive) for role mapping.
**On the Attributes & Claims page:**
1. Click **"Add a group claim"** (not "Add new claim")
2. Under "Which groups associated with the user should be returned in the claim?", select **"Groups assigned to the application"**
* Do **not** select "All groups" or "Security groups" — this can include unrelated groups and may hit Entra ID's 150-group limit for SAML tokens
3. Configure the **Source attribute** based on your group type:
* **Cloud-only Entra ID groups**: Select `Cloud-only group display names`
* **On-premises AD groups** (synced via Entra Connect): Select `sAMAccountName`
* If unsure, try `Cloud-only group display names` first
4. Expand **Advanced options** and ensure:
* **"Filter groups" is unchecked** — if checked with empty or incorrect filter fields, no groups will be included in the SAML response
* **"Customize the name of the group claim"** is checked
* Set the **Name** to `groups` (Bruno recognizes `groups`, `group`, `roles`, and `role` as valid claim names)
5. Click **Save**
**Important:** The group must be **assigned to the Enterprise Application** in Entra ID. Go to your Bruno Enterprise Application → **Users and groups** → click **Add user/group** and add the relevant groups. Simply being a member of the AD group is not enough — the group must be explicitly assigned to the application.
After saving, your finalized Attributes & Claims should look like the following. Note that the `roles` and `groups` claims can be used **alone or together** — Bruno will accept either or both for role mapping:
| Claim Name | Type | Value | Required |
| ------------------------------------ | ---- | ------------------------------------------ | ------------------------------------------ |
| **Unique User Identifier (Name ID)** | SAML | `user.mail` | Yes |
| **fullName** | SAML | `Join (user.givenname, " ", user.surname)` | Yes |
| **groups** | SAML | `user.groups` | Optional — use alone or alongside `roles` |
| **roles** | SAML | `user.assignedroles` | Optional — use alone or alongside `groups` |
Then in the Bruno License Portal under **Settings → SSO → Role Mapping**, enter the **exact group display names** as they appear in Entra ID:
* **Admin Roles**: e.g., `Bruno-Admins` or `Your-Company_Bruno_Admin`
* **User Roles**: e.g., `Bruno-Users` or `Your-Company_Bruno_Users`
Role values are case-sensitive and must match exactly with the group names in Entra ID.
**Example Scenarios:**
* If you set a static value of `Engineering`, you'll add the value `Engineering` in the Bruno License Portal to the corresponding Admin or User role field
* If you map to `user.department` and a user's department is `IT`, you'll add `IT` to the appropriate role field in the Bruno License Portal
#### Configuring the `fullName` claim
The `fullName` claim represents the user's full name. This may already be available in your Entra ID user profile as a single field (e.g., `user.displayname`). If so, you can map the `fullName` attribute directly to that field.
If a full name field is not available, you can concatenate the first and last name fields using a transformation as follows:
On the **Attributes & Claims** page:
1. Click **Add new claim**
2. Enter `fullName` as the name
3. Under **Source**, select **Transformation**
4. In **Manage Transformation**, configure the transformation:
* **Transformation**: `Join()`
* **Parameter 1**: `user.givenname`
* **Separator**: ` ` (space)
* **Parameter 2**: `user.surname`
5. Click **Save**
#### Finalized Attributes & Claims Configuration
Return to the **Attributes & Claims** page and verify the following:
1. Any other claims that are not shown below have been deleted
2. The `Unique User Identifier (Name ID)` and `fullName` claims are configured
3. Either `roles` (Option A), `groups` (Option B), or both are configured based on your chosen approach
**Important**: A role or group claim (`roles` or `groups`) and the `fullName` attribute are required for Bruno SAML SSO to function correctly. The attribute names are case-sensitive and must match the appropriate values configured in Entra ID.
## Finish SSO Configuration in Bruno
### Step 1: Add SSO URL to Bruno License Portal
**IdP Login URL / SSO URL**
1. Return to the Enterprise Application page and navigate to **Manage** → **Single sign-on** in the left sidebar
2. In the **Set up 'AppName'** section 4, copy the following value:
* **Login URL**: Copy this URL
3. Return to the [Bruno License Portal](https://license.usebruno.com/) tab you opened [from the earlier configuration](#configure-sso-in-bruno)
4. Navigate to **Settings** → **SSO** (if not already there)
5. Under **SAML Configuration** paste the **Login URL** from Entra ID into the **IdP Login URL / SSO URL** field
### Step 2: Add IdP Certificate to Bruno License Portal
**Entra ID IdP Certificate**
1. In the **SAML Certificates** section 3:
* **Certificate (Base64)**: Download the certificate
2. Open the downloaded certificate file and copy the contents (include the `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` lines)
3. Return to the Bruno License Portal tab
4. Under **SAML Configuration** paste the certificate contents into the **IdP Certificate** field
### Step 3: Map the role values from Entra ID to Bruno access levels
1. Under the Bruno License Portal in the SSO Configuration page, scroll down to the **Role Mapping** section
2. **Admin Roles**: Enter the role values (comma-separated) that will have admin access to the Bruno License Portal
* Example: `admin,BrunoAdmin,IT-Administrators`
* These values must match what you configured in the `roles` attribute in Entra ID
* Users with these roles can access the admin panel and manage licenses
3. **User Roles**: Enter the role values (comma-separated) that should have user access to Bruno
* Example: `user,Engineering,Developers,QA`
* These values must match what you configured in the `roles` attribute in Entra ID
* Users with these roles will be able to activate their Bruno licenses with SSO. **They will not have access to the admin panel.**
**How Role Mapping Works:**
The role value you configured in Entra ID's `roles` claim will be sent in the SAML assertion. Bruno will check if this value matches any role in the "Admin Roles" or "User Roles" fields.
**Example:**
* In Entra ID, you set the `roles` claim to map to `user.department`
* A user's department is `Engineering`
* In Bruno Admin Roles, you enter: `admin,IT`
* In Bruno User Roles, you enter: `user,Engineering,QA`
* Result: Users from the Engineering department get standard access (matches "Engineering" in User Roles)
**Important**: Role values are case-sensitive. Ensure the values in Entra ID's `roles` claim match exactly with the values you enter in Bruno's Admin Roles or User Roles fields.
### Step 4: Configure Session Settings
1. Scroll down to the **Session Timeout** section:
* Set the session timeout in seconds (default: 3600 = 1 hour)
2. Click **Save Configuration** to apply your SAML SSO configuration
## Test Your SAML Configuration
### Assign Users or Groups
1. In your Entra ID Enterprise Application, navigate to **Manage** → **Users and groups** in the left sidebar
2. Click **Add user/group**
3. Select the users or groups that should have access to Bruno, if using [App Roles](https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-use-app-roles-customers#app-roles) they can be assigned here
4. Click **Assign**
### Test SSO Login
1. Navigate to the Bruno License Portal ([https://license.usebruno.com/](https://license.usebruno.com/))
2. Enter the email address of a user assigned to the Bruno app in Entra ID
3. Click **Login with SSO**
4. You should be redirected to Entra ID to authenticate
5. If your user is an admin in Bruno and contains the correct role mapping, you should be redirected back to the Bruno License Portal
## Next Steps
After setting up SSO with Microsoft Entra ID, you can:
* [Configure SCIM Provisioning](../scim-provisioning/overview) to automate user provisioning and deprovisioning
* [Manage your Bruno licenses](../license-portal) in the License Portal
## Related Resources
For more information about configuring SAML SSO and managing roles in Microsoft Entra ID, refer to these Microsoft documentation resources:
* [SAML authentication with Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/architecture/auth-saml) - Overview of SAML protocol and authentication flow
* [Enable SAML single sign-on for an enterprise application](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/add-application-portal-setup-sso) - Step-by-step guide for SAML SSO setup
* [Customize SAML token claims](https://learn.microsoft.com/en-us/entra/identity-platform/saml-claims-customization) - Configure custom attributes and claims for SAML applications
* [Using App Roles for role-based access control](https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-use-app-roles-customers) - Configure and use App Roles for fine-grained access control
* [Manage federation certificates for federated single sign-on](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/tutorial-manage-certificates-for-federated-single-sign-on) - Manage and renew SAML certificates
* [Plan a single sign-on deployment](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/plan-sso-deployment) - Best practices and planning guide for SSO deployment
# Configure SAML SSO with Okta
Source: https://docs.usebruno.com/license-administrators/saml-sso/configure-saml-sso-with-okta
This guide walks you through configuring SAML Single Sign-On for Bruno using Okta as your identity provider.
Before you begin, make sure you have completed the [prerequisites](./overview#prerequisites) and have admin access to both Okta and the Bruno License Portal.
## Configure SSO in Bruno
Before configuring a SAML application in Okta, first configure SSO in Bruno.
1. Log in to the [Bruno License Portal](https://license.usebruno.com/)
2. Navigate to **Settings** → **SSO** in the left sidebar
3. Toggle the **Enable SSO** switch on
4. Note the following values (you'll need these when configuring Okta):
* **SAML ACS URL**: Copy this URL exactly as shown in Bruno
* **SP Issuer ID / Entity ID**: Set your own unique identifier (e.g., `bruno-sso`, `bruno-okta`, `your-company-bruno`, etc.)
Keep this page open in a separate tab - you'll return here after configuring Okta to complete the Bruno SSO setup.
## Configure SSO with Okta
### Step 1: Create a New SAML Application
1. Log in to your Okta Admin Console
2. Navigate to **Applications** → **Applications** in the left sidebar
3. Click **Create App Integration**
4. Select **SAML 2.0** as the sign-in method
5. Click **Next**
### Step 2: Configure General Settings
1. Enter the following information:
* **App name**: Set your own unique identifier (e.g., `Bruno`, `Bruno-SAML-App`, etc.)
* **App logo**: (Optional) Upload Bruno logo
* **App visibility**: Configure based on your organization's preferences
2. Click **Next**
### Step 3: Configure SAML Settings
Copy the values from the Bruno SSO settings page and paste them into your SAML configuration in Okta.
**Single Sign-On URL:**
1. In the **Single sign-on URL** field, copy and paste the **SAML ACS URL** from Bruno
2. Check **Use this for Recipient URL and Destination URL**
**Audience URI (SP Entity ID):**
1. In the **Audience URI (SP Entity ID)** field, paste the **SP Issuer ID / Entity ID** value from the Bruno License Portal
* **Important**: This value must match EXACTLY what you configured in the Bruno SSO Configuration page
**Name ID Format:**
1. Select **EmailAddress** from the **Name ID format** dropdown
**Application Username:**
1. Select **Email** from the **Application username** dropdown
### Step 4: Configure Attribute Statements
Bruno needs the user's full name plus a role/group value to map them to access levels in the License Portal. You can send the role/group value using **either or both** of two approaches — choose what fits how your organization manages access in Okta:
* **Option A: `roles` Profile Attribute Statement** — Send a per-user role value (a static value, a user attribute, etc.)
* **Option B: `groups` Group Attribute Statement** — Send the names of Okta groups the user belongs to (required if you assign users to the Bruno app via **Assign to Groups**)
Both approaches are equally valid and can be used together. Bruno recognizes the attribute names `role`, `roles`, `group`, and `groups` (case-insensitive) for role mapping.
Add the following attribute statements to your Okta SAML configuration:
| Name | Source | Notes |
| ----------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **fullName** | `user.firstName+" "+user.lastName` or equivalent attribute | Required. Represents the combined user's first and last name. |
| **roles** or **groups** | A role value (Option A) or a group filter (Option B) | Use `roles` as a Profile Attribute Statement (Option A), OR use `groups` via Group Attribute Statements (Option B), OR both. See details below. |
#### Option A: `roles` Profile Attribute Statement
In Okta's **Attribute Statements** section, add:
| Name | Name Format | Value |
| --------- | ----------- | ------------------------ |
| **roles** | Unspecified | Any role value from Okta |
**Configuring the `roles` attribute:**
The `roles` attribute can be configured in several ways:
1. **Static value for testing**: Set a hardcoded value like `"admin"` for all users assigned to this app
* Example: Value = `"admin"`
2. **Map to an existing or created Okta user attribute**: If your Okta users already have a role attribute or you create a specific attribute for Bruno roles
* Example: Value = `user.userType` or `user.role` or `user.brunoRole`
**Important**: The role value sent by Okta will be mapped to Bruno access levels in the License Portal's SSO Settings. You'll configure which role values correspond to admin or user access in Bruno (see Step 3 in the Bruno configuration section below).
**Example Scenarios:**
* If you set Value = `"Engineering"`, you'll add `Engineering` to either "Admin Roles" or "User Roles" in Bruno
* If you set Value = `user.department`, and a user's department is `IT`, you'll add `IT` to the appropriate role field in Bruno
#### Option B: `groups` Group Attribute Statement
If you assign users to the Bruno app via Okta groups (**Assign to Groups**) and rely on group membership for role mapping, configure a Group Attribute Statement. Without it, Okta will not include group membership in the SAML assertion and group-assigned users with no Profile `roles` value will receive a "You do not have necessary permissions" error.
In Okta's **Group Attribute Statements** section, add:
| Name | Name Format | Filter | Value |
| ---------- | ----------- | ------------- | ----- |
| **groups** | Unspecified | Matches regex | `.*` |
**Filtering Groups:**
Using `Matches regex` with `.*` sends all group names the user belongs to. You can narrow this down:
* **Starts with**: e.g., `bruno-` to only send groups starting with "bruno-"
* **Equals**: e.g., `bruno-admins` to send only a specific group
* **Contains**: e.g., `bruno` to send groups containing "bruno"
The group names sent must match the values you configure in Bruno's **Admin Roles** or **User Roles** fields.
#### Configuring the `fullName` attribute
The `fullName` attribute is always required as a Profile Attribute Statement, regardless of which option(s) you use above. It can be configured by:
* Concatenating first and last name: `user.firstName+" "+user.lastName`
* Using a single field if your Okta user profile has a combined name field
* Mapping to any existing user property that contains the full name
**Important**: The `fullName` attribute plus at least one role/group source (`roles` Profile Attribute Statement, `groups` Group Attribute Statement, or both) are required for Bruno SAML SSO to function correctly. Attribute names are case-sensitive and must match exactly as shown.
**Preview the SAML Assertion**
1. Scroll down to the **Preview the SAML assertion generated from the information above** section
2. Click **Preview the SAML Assertion**
3. Verify the generated XML contains the configured attributes: `NameID Format`,`roles`, `fullName`
4. Click **Next**
### Step 5: Complete Okta App Setup
1. On the **Feedback** page:
* Select **I'm an Okta customer adding an internal app**
* Check **This is an internal app that we have created**
2. Click **Finish**
## Finish SSO Configuration in Bruno
### Step 1: Add SSO URL to Bruno License Portal
**IdP Login URL / SSO URL**
1. In your configure Okta application, navigate to the **Sign On** tab
2. Scroll down to the **SAML 2.0** section, expand the **Hide details** section
3. Copy the following values (you'll need these for Bruno configuration):
* **Sign on URL**: Copy this URL
4. Return to the Bruno License Portal tab you opened [from the earlier configuration](#configure-sso-in-bruno)
5. Navigate to **Settings** → **SSO** (if not already there)
6. Under **SAML Configuration** paste the **Sign on URL** from Okta into the **IdP Login URL / SSO URL** field
### Step 2: Add IdP Certificate to Bruno License Portal
**Okta IdP Certificate**
1. Scroll down the **Sign On** tab to **SAML Signing Certificates**
2. Click **Generate new certificate**
3. For the newly generated certificate, click the **Actions** dropdown and select **Download Certificate**
4. Open the downloaded certificate file and copy the contents (include the `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` lines)
5. Return to the Bruno License Portal tab you opened [from the earlier configuration](#configure-sso-in-bruno)
6. Under **SAML Configuration** paste the certificate contents into the **IdP Certificate** field
### Step 3: Map the role values from Okta to Bruno access levels
1. Under the Bruno License Portal in the SSO Configuration page, scroll down to the **Role Mapping** section
2. **Admin Roles**: Enter the role values (comma-separated) that should have admin access to Bruno
* Example: `admin,BrunoAdmin,IT-Administrators`
* These values must match what you configured in the `roles` attribute in Okta
* Users with these roles can access the admin panel and manage licenses
3. **User Roles**: Enter the role values (comma-separated) that should have user access to Bruno
* Example: `user,Engineering,Developers,QA`
* These values must match what you configured in the `roles` attribute in Okta
* Users with these roles will be able to activate their Bruno licenses with SSO. **They will not have access to the admin panel.**
**How Role Mapping Works:**
The role value you configured in Okta's `roles` attribute statement will be sent in the SAML assertion. Bruno will check if this value matches any role in the "Admin Roles" or "User Roles" fields.
**Example:**
* In Okta, you set the `roles` attribute to `"Engineering"`
* In Bruno Admin Roles, you enter: `admin,BrunoAdmin`
* In Bruno User Roles, you enter: `user,Engineering,QA`
* Result: Users with the `Engineering` role are able to activate their Bruno licenses with SSO (matches "Engineering" in User Roles)
**Important**: Role values are case-sensitive. Ensure the values in Okta's `roles` attribute match exactly with the values you enter in Bruno's Admin Roles or User Roles fields.
### Step 4: Configure Session Settings
1. Scroll down to the **Session Timeout** section:
* Set the session timeout in seconds (default: 3600 = 1 hour)
2. Click **Save Configuration** to apply your SAML SSO configuration
## Test Your SAML Configuration
### Assign Users or Groups
1. In your Okta Bruno application, navigate to the **Assignments** tab
2. Click **Assign** → **Assign to People** or **Assign to Groups**
* **Note**: Users assigned must already exist in your subscription under the Bruno License Portal in order to login with SSO
* **Important**: If assigning via **Assign to Groups**, make sure you have configured a **Group Attribute Statement** in [Step 4](#step-4-configure-attribute-statements). Without it, Okta will not send group membership in the SAML assertion and users will get a permissions error.
3. Select the users or groups that should have access to Bruno
4. Click **Assign** and **Done**
### Test SSO Login
1. Navigate to the Bruno License Portal ([https://license.usebruno.com/](https://license.usebruno.com/))
2. Enter the email address of a user assigned to the Bruno app in Okta
3. Click **Login with SSO**
4. You should be redirected to Okta to authenticate
5. If your user is an admin in Bruno and contains the correct role mapping, you should be redirected back to the Bruno License Portal
## Next Steps
After setting up SSO with Okta, you can:
* [Configure SCIM Provisioning](../scim-provisioning/overview) to automate user provisioning and deprovisioning
* [Manage your Bruno licenses](../license-portal) in the License Portal
## Related Resources
For more information about configuring SAML SSO and managing attributes in Okta, refer to these Okta documentation resources:
* [SAML app integrations](https://help.okta.com/en-us/content/topics/apps/apps-about-saml.htm) - Overview of SAML protocol and Okta SAML applications
* [Create SAML app integrations](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_saml.htm) - Step-by-step guide for creating SAML applications in Okta
* [Define attribute statements](https://help.okta.com/en-us/content/topics/apps/define-attribute-statements.htm) - Configure SAML attribute statements for user attributes and roles
* [Define group attribute statements](https://help.okta.com/en-us/content/topics/apps/define-group-attribute-statements.htm) - Map Okta groups to SAML attributes for role-based access control
* [Application Integration Wizard SAML field reference](https://help.okta.com/en-us/content/topics/apps/aiw-saml-reference.htm) - Reference guide for SAML configuration fields
* [Build a Single Sign-On (SSO) integration](https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/) - Developer guide for building SAML SSO integrations
# SAML Single Sign-On (SSO) Overview
Source: https://docs.usebruno.com/license-administrators/saml-sso/overview
SAML SSO is available for Ultimate Edition customers behind a feature flag. Please contact your Bruno Account Manager to enable this feature.
## What is SAML SSO?
SAML (Security Assertion Markup Language) Single Sign-On allows your organization to use your existing identity provider (IdP) to authenticate users accessing Bruno. With SAML SSO, Admins can login to the License Portal without an OTP code and Users can activate their licenses without a key.
## Benefits of SAML SSO with Bruno
### Centralized Authentication
* **Single set of credentials**: Admins and users access Bruno with the same credentials they use for other corporate applications
* **Streamlined access**: One-click access to Bruno through your identity provider's portal
### Enhanced Security
* **Centralized access control**: Manage Bruno access through your existing identity management system
* **Multi-factor authentication**: Leverage your IdP's MFA capabilities for Bruno access
* **Audit trail**: Track all authentication activities in your identity provider
* **Automatic session management**: Control session timeouts and security policies centrally
### Operational Efficiency
* **Simplified onboarding**: New employees automatically receive Bruno Ultimate access as part of their standard onboarding
* **Simplified offboarding**: Departing employees automatically lose Bruno Ultimate access when removed from your IdP
* **Reduced administrative overhead**: IT teams spend less time on manual user management
## Supported Identity Providers
Bruno supports SAML 2.0 SSO with the following identity providers:
* **Okta** - [Configuration Guide](./configure-saml-sso-with-okta)
* **Microsoft Entra ID (Azure AD)** - [Configuration Guide](./configure-saml-sso-with-entra-id)
* **Generic SAML 2.0 providers** - Any SAML 2.0 compliant identity provider
## Prerequisites
Before configuring SAML SSO, ensure you have:
* **Ultimate Edition subscription** with SAML SSO feature enabled
* **Admin access** to the Bruno License Portal (`https://license.usebruno.com/`)
* **Admin access** to your identity provider (Okta, Entra ID, etc.)
## General Setup Flow
Setting up SAML SSO involves configuration in both your identity provider and Bruno:
### 1. Configure Your Identity Provider
In your identity provider (Okta, Entra ID, etc.):
* Create a new SAML application for Bruno
* Configure the SAML settings using Bruno's ACS URL and Entity ID
* Set up attribute mappings for user information
* Configure role mappings for admin and user access
* Assign users or groups to the Bruno application
### 2. Configure Bruno
In the Bruno License Portal:
* Navigate to Settings → SSO
* Toggle **Enable SSO** to on
* Enter your IdP's SAML metadata (Entity IDLogin URL, Certificate)
* Configure **Role Mapping** to match your IdP's role attributes
* Set session timeout preferences
* Save Configuration
### 3. Test and Verify
* Test SSO login with a test user account
* Verify user attributes are correctly mapped
* Confirm role assignments work as expected
* Test session timeout behavior
## SAML Configuration Values
When configuring your identity provider, you'll need these Bruno-specific values:
| Field | Value | Notes |
| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **ACS URL** | Specific to your Bruno subscription | Copy the exact SAML ACS URL from your Bruno SSO settings page. Format: `https://license.usebruno.com/api/v2/auth/sso/saml/acs/{subscription-id}` |
| **SP Issuer ID / Entity ID** | Enter a unique value | Can be any unique identifier (e.g., `bruno`, `bruno-demo`). Must match exactly between Bruno and your IdP |
**Important**: Always copy the ACS URL and SP Issuer ID / Entity ID directly from your Bruno SSO settings page. The Entity ID can be any unique value you choose, but it must match exactly between Bruno and your identity provider.
## Required SAML Attributes
Bruno requires the following SAML attributes to be configured in your identity provider. These attributes are sent in the SAML assertion and used to provision and manage user access:
### User Attributes
| Attribute Name | Description | Example Value |
| -------------- | ---------------------------------------- | --------------------------------------------------------------------------------- |
| **NameID** | User's email address (unique identifier) | `user.mail` or `user.email` - Must be in email format |
| **roles** | User role(s) for access control | Any role value from your IdP (e.g., `BrunoAdmin`, `Engineering`, `admin`, `user`) |
| **fullName** | User's full name | `user.firstName+" "+user.lastName` or equivalent |
**Attribute Configuration Details:**
* **NameID**: Bruno uses the NameID as the unique user identifier and email address. Configure your IdP to send the user's email address as the NameID with format `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`. This is critical for user identification and license activation.
* **roles**: This attribute contains the role value(s) from your identity provider. The value can be:
* Any existing role or group attribute from your IdP (e.g., `Engineering`, `BrunoAdmin`, `IT-Team`)
* A hardcoded value for testing purposes (e.g., `admin`, `user`)
* Mapped from existing user attributes or group memberships in your IdP
**Important**: The role values sent in the SAML assertion must be mapped to either "Admin Roles" or "User Roles" in the Bruno License Portal's SSO Settings (see [Role Mapping](#role-mapping) section below).
* **fullName**: This attribute should contain the user's complete name. It can be mapped to:
* A single field if your IdP has a combined name field
* A concatenation of first and last name fields (e.g., `user.firstName+" "+user.lastName`)
* Any existing user property in your IdP that contains the full name
### Example SAML Assertion
Here's an example of how these attributes appear in a SAML assertion:
```xml theme={null}
adminuser.firstName+" "+user.lastName
```
**Important**: Both attributes must be configured in your identity provider for SAML SSO to work correctly. The attribute names are case-sensitive and must match exactly as shown above.
## Role Mapping
After configuring the `roles` attribute in your identity provider, you need to map those role values to Bruno access levels in the License Portal.
### Configuring Role Mapping in Bruno
In the Bruno License Portal's SSO Settings, you'll find two fields under "Role Mapping":
1. **Admin Roles**: Comma-separated list of role values that should have admin access
* Example: `admin,BrunoAdmin,IT-Administrators`
* Users with these roles can access the admin panel and manage licenses
2. **User Roles**: Comma-separated list of role values that should have user access
* Example: `user,Engineering,Developers`
* Users with these roles have standard access to Bruno
**How Role Mapping Works:**
1. Your IdP sends the `roles` attribute value in the SAML assertion (e.g., `Engineering`)
2. Bruno checks if this value matches any role in the "Admin Roles" or "User Roles" fields
3. If it matches "Admin Roles", the user will be able to access the license portal and manage licenses
4. If it matches "User Roles", the user will be able to activate their license with SSO but will not have license portal access
5. If it doesn't match either, the user will be denied access
**Example Configuration:**
* IdP sends: `roles="Engineering"`
* Bruno Admin Roles: `admin,BrunoAdmin`
* Bruno User Roles: `user,Engineering,QA`
* Result: User gets standard access (matches "Engineering" in User Roles)
**Important Implementation Details:**
* Role matching is **case-sensitive** - `admin` ≠ `Admin`
* A user can have multiple roles in the IdP; Bruno checks if **any** of them match the configured roles
* If a user matches "Admin Roles", they get admin access (even if they also match "User Roles")
* When a user is removed from the IdP, they lose access on their next login attempt
**Important**: The role values are case-sensitive. Ensure the values in your IdP's `roles` attribute match exactly with the values you configure in Bruno's Admin Roles or User Roles fields.
## License Activation and Access Control
### License Activation Flow
**For User Role Users:**
* [Users can activate their Bruno license using SSO](/license-end-users/activate-license#activating-with-sso)
* They receive a license key via email on first login
* They can use this key to activate Bruno on their local machine
* They do NOT have access to the License Portal
**For Admin Role Users:**
* Admins can access the License Portal to manage licenses and users
* Admins can view all users in their subscription
* Admins can manage role mappings and SSO configuration
* Admins can view subscription details and usage
### Session Management
* **Session timeout**: Configured in Bruno SSO settings (default: 1 hour / 3600 seconds)
* **Minimum timeout**: 60 seconds
* **Session cookies**: Secure, HTTP-only cookies are used for session management
* **Logout**: Users are logged out when the session expires or when they manually log out
**Coordination with IdP Sessions**: Bruno's session timeout is independent of your IdP's session timeout. The shorter timeout will take precedence. For example, if your IdP session expires after 30 minutes but Bruno is set to 1 hour, users will be logged out after 30 minutes.
## Next Steps
Ready to configure SAML SSO? Choose your identity provider:
* [Configure SAML SSO with Okta](./configure-saml-sso-with-okta) - Complete setup guide for Okta
* [Configure SAML SSO with Entra ID](./configure-saml-sso-with-entra-id) - Complete setup guide for Microsoft Entra ID
Need help? See our [Troubleshooting Guide](./troubleshooting) for common issues and solutions.
# SAML SSO Troubleshooting
Source: https://docs.usebruno.com/license-administrators/saml-sso/troubleshooting
This guide helps you diagnose and resolve common issues when configuring or using SAML Single Sign-On with Bruno.
## Quick Pre-Flight Checks
Before diving into technical troubleshooting, verify these basic prerequisites. Most SSO login failures are due to one of these issues:
### 1. Is SSO Enabled in Bruno?
**Check this first:**
* Go to Bruno License Portal (`https://license.usebruno.com/`)
* Navigate to **Settings** → **SSO**
* Verify the **"Enable SSO"** toggle is **ON**
* If it's OFF, toggle it ON and click **Save Configuration**
**Why this matters**: If SSO is disabled, all SSO login attempts will fail with a 500 error.
**Important**: SSO must be enabled in the License Portal before users can log in via SSO. This is the first and most common cause of SSO login failures.
### 2. Is the User Already an Admin in Bruno License Portal?
Accessing the license portal requires admin access. Users will not need to be added as admins if they only need to activate their license via SSO.
**Check this next:**
* Go to Bruno License Portal → **Settings** → **Admins**
* Look for the user's email address in the Admins list
* If the user is NOT in the list, they must be added first before they can use SSO
**Why this matters**: Users must be configured as admins in Bruno BEFORE they can log in via SSO. SSO only authenticates users who are already admins in the system.
**If the user is not listed:**
1. Click the **"Add Admin"** button
2. Enter the user's email address
3. Click **Add**
4. The user will now be able to log in via SSO
**After the user is added as an admin, verify role mapping:**
* Go to Bruno License Portal → **Settings** → **SSO**
* Look at the **"Admin Roles"** field
* The user's role (from their IdP) must match one of the values in this field
* **Example**: If Admin Roles is set to `admin,BrunoAdmin`, the user's role must be exactly `admin` or `BrunoAdmin`
**How to verify the user's role:**
1. In your IdP, check what role value is assigned to the user
2. In Bruno License Portal → Settings → SSO, check the "Admin Roles" field
3. Ensure they match exactly (case-sensitive)
**Two-Step Process**:
1. First, add the user as an admin in Bruno License Portal (Settings → Admins)
2. Then, ensure their IdP role matches the "Admin Roles" field in SSO settings
Both steps are required for SSO login to work.
**Role Matching is Case-Sensitive**: `admin` ≠ `Admin`. The role value from your IdP must match exactly what's in the Admin Roles field.
### 3. Is SSO Configuration Complete?
**Verify all required fields are filled:**
* Go to Bruno License Portal → **Settings** → **SSO**
* Check that these fields are NOT empty:
* **SP Issuer ID / Entity ID**
* **IdP Login URL**
* **IdP Entity ID**
* **IdP Certificate**
* **Admin Roles** (at least one role value)
**Why this matters**: If any required field is missing, Bruno cannot generate a SAML request and will return a 500 error.
**Tip**: If you're not sure what values to use, refer to the [SAML SSO Overview](./overview) page for detailed explanations of each field.
## Inspect the SAML Response with SAML-tracer
**Highly Recommended Early Step**: Before working through specific error messages below, capture the SAML response using the [SAML-tracer browser extension](https://chromewebstore.google.com/detail/saml-tracer/mpdajninpobndbfcldcmbpnnbhibjmch). It exposes the exact attributes (e.g., `roles`, `fullName`, `NameID`) your IdP is sending to Bruno, which makes diagnosing most common issues significantly faster.
The SAML-tracer extension captures SAML requests and responses as they flow through the browser, letting you see exactly what your IdP is sending without having to decode `SAMLResponse` payloads from the Network tab manually.
**Setup:**
1. Install [SAML-tracer](https://chromewebstore.google.com/detail/saml-tracer/mpdajninpobndbfcldcmbpnnbhibjmch) from the Chrome Web Store (also available for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/saml-tracer/))
2. Click the SAML-tracer icon in your browser toolbar to open the tracer window
3. Leave the tracer window open and attempt SSO login from Bruno
**What to verify in the captured SAML response:**
1. In the SAML-tracer window, find the request flagged with a `SAML` badge that POSTs to `/api/v2/auth/sso/saml/acs/...`
2. Click the request and open the **SAML** tab to view the decoded XML
3. Confirm the following values are present and correct:
* **NameID**: Should be the user's email address (Bruno uses this to identify the user)
* **`roles` attribute**: Should contain a value that exactly matches one of your Admin Roles or User Roles in Bruno (case-sensitive)
* **`fullName` attribute** (or `firstName` + `lastName` as fallback): Should contain the user's name
* **Destination** and **AudienceRestriction**: Should match Bruno's ACS URL and SP Entity ID respectively
**Example of a healthy SAML response (key sections):**
```xml theme={null}
jane.doe@example.combruno-demoadminJane Doe
```
In this example:
* `NameID` is `jane.doe@example.com` — Bruno uses this email to identify the user
* `` is `bruno-demo` — must match Bruno's **SP Issuer ID / Entity ID** exactly
* `roles` is `admin` — must match a value in Bruno's **Admin Roles** (or **User Roles**) field, case-sensitive
* `fullName` is `Jane Doe` — populates the user's display name in Bruno
If you're using `firstName` and `lastName` as a fallback instead of `fullName`, the `` would contain two separate attributes named `firstName` and `lastName`, which Bruno will combine with a space.
If you're using **group claims** (common with Entra ID), the attribute will be named `groups` instead of `roles`, and may contain multiple `` entries — one per group. Bruno checks if ANY of the values match your configured Admin Roles or User Roles.
**Common issues this quickly reveals:**
* The `roles` (or `groups`) attribute is missing entirely → IdP attribute statement not configured
* The `roles` value doesn't match what's in Bruno's Admin Roles / User Roles → role mapping mismatch (often a casing or whitespace issue)
* NameID is not an email address → IdP NameID format misconfigured
* `fullName` is missing → user profile shows email instead of name
* Destination / Audience values don't match Bruno → ACS URL or Entity ID mismatch
* Attribute name uses a full URI (e.g., `http://schemas.microsoft.com/.../groups`) instead of `groups` → Bruno only recognizes the short names `role`, `roles`, `group`, `groups`
**Sensitive Data**: SAML responses contain user identity information and signed assertions. When sharing captures with Bruno support, redact any data you don't want to expose.
## Common Error Messages
### "500 Internal Server Error" When Clicking "Login with SSO"
**First, complete the pre-flight checks above** (Is SSO enabled? Is the user an admin? Is configuration complete?)
**Possible Causes:**
* SSO is not enabled in Bruno (see Pre-Flight Check #1)
* User is not a License Manager Admin (see Pre-Flight Check #2)
* SSO configuration is incomplete (see Pre-Flight Check #3)
* Entity ID mismatch between Bruno and your IdP
* Invalid or malformed certificate in Bruno configuration
* IdP Login URL is missing or incorrect
**What This Error Means:**
This error occurs when Bruno tries to generate a SAML authentication request but the SSO configuration is incomplete or invalid. The backend checks that all required fields are present before proceeding.
**Solutions:**
1. **Complete the pre-flight checks first**:
* Verify SSO is enabled
* Verify the user is a License Manager Admin (role matches Admin Roles)
* Verify all required configuration fields are filled
2. **Verify Entity ID matches exactly**:
* The "SP Issuer ID / Entity ID" in Bruno must match the "Identifier (Entity ID)" in your IdP configuration
* Example: If Bruno has `bruno-demo`, your IdP must also have `bruno-demo`
* The value can be anything, but it must be identical in both places
3. **Check certificate format**:
* Open the certificate file you downloaded from your IdP
* Copy the entire contents including `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` lines
* Paste into Bruno's IdP Certificate field
* Ensure there are no extra spaces or line breaks at the beginning or end
4. **Verify IdP Login URL is correct**:
* The IdP Login URL should be the SAML entry point from your IdP
* Example: `https://idp.example.com/app/123/sso/saml`
* This is typically found in your IdP's SAML metadata
5. **Save the configuration**: Click "Save Configuration" button after making changes
6. **Check browser console**: Open Developer Tools (F12) → Console tab to see detailed error messages
7. **Try disabling and re-enabling SSO**: Toggle the "Enable SSO" switch off, save, then toggle it back on and save again
**Backend Validation**: Bruno validates that SSO is enabled and all required configuration fields are present before generating the SAML request. If any field is missing or SSO is disabled, you'll see a 500 error. Check the browser console for the specific error message.
### IdP Test Login Shows Error or Redirects to Bruno Login Page
**Possible Causes:**
* Reply URL (ACS URL) is configured incorrectly in your IdP
* Certificate or metadata mismatch
* SSO not fully enabled in Bruno
**Solutions:**
1. **Verify Reply URL (ACS URL)**: In your IdP's SAML application configuration:
* Should be: `https://license.usebruno.com/api/v2/auth/sso/saml/acs/`
* This is where your IdP sends the SAML response after authentication
* The subscription ID is shown in the Bruno License Portal SSO settings page
* This URL must match EXACTLY what's configured in Bruno
* Copy this URL exactly from the Bruno SSO settings page
2. **Ensure SSO is enabled in Bruno**:
* IdP test login will fail if SSO is not fully configured and enabled in Bruno
* Complete the Bruno SSO configuration first, then test from your IdP
3. **Check the full error message**: The error message may be truncated. Try:
* Opening the test in a new incognito/private browser window
* Checking the browser console for the full error message
* Looking at the Network tab in Developer Tools for the failed request
4. **Common error patterns**:
* Reply URL mismatch: Check ACS URL configuration
* Authentication method mismatch: Verify SAML 2.0 is configured
* User not assigned: Ensure user is assigned to the Bruno application
* Application not found: Verify application is properly configured
**Microsoft Entra ID Specific**: If you see `AADSTS` error codes:
* `AADSTS50011`: Reply URL mismatch - verify the ACS URL in Basic SAML Configuration
* `AADSTS75011`: Authentication method mismatch - ensure SAML 2.0 is selected
* `AADSTS50105`: User not assigned - assign user/group to the Bruno application in Enterprise Applications
* `AADSTS700016`: Application not found - verify the application exists and is properly configured
* `AADSTS50020`: User account from identity provider does not exist - ensure the user is assigned to the Bruno application in Entra ID
**Okta Specific**: Common Okta-specific issues:
* **"Invalid SAML Response"**: Check that the Okta application's SAML settings match Bruno's requirements exactly
* **"User not assigned"**: In Okta Admin → Applications → Bruno app → Assignments, ensure users/groups are assigned
* **"Invalid audience"**: Verify the Audience URI in Okta matches Bruno's SP Issuer ID exactly
* **"Invalid destination"**: Ensure the Single Sign On URL in Okta matches Bruno's ACS URL
* **Preview SAML assertion**: Use Okta's "Preview SAML assertion" feature to verify attribute values before testing
**Testing Order**: Always complete the Bruno SSO configuration first before testing from your IdP. The recommended testing flow is:
1. Configure SSO in Bruno and save
2. Test login by clicking "Login with SSO" from Bruno's login page (SP-initiated)
3. Once that works, test login from your IdP portal (IdP-initiated)
### "SAML Response Validation Failed"
**Possible Causes:**
* Certificate mismatch between IdP and Bruno configuration
* Expired or invalid SAML certificate
* Clock skew between IdP and Bruno servers
**Solutions:**
1. Verify the certificate in Bruno matches the certificate from your IdP
2. Ensure you've copied the entire certificate including `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` lines
3. Check that your IdP's certificate hasn't expired
4. Verify your server's system clock is synchronized (NTP)
**Entra ID Specific**: Entra ID certificates auto-rotate. If you see certificate validation errors:
* Check if Entra ID has issued a new certificate (Enterprise Applications → Bruno app → Single sign-on → SAML Certificates)
* Download the new certificate and update it in Bruno's SSO configuration
* Entra ID typically notifies admins 30 days before certificate expiration
### "Invalid ACS URL" or "Redirect URI Mismatch"
**Possible Causes:**
* Incorrect ACS URL configured in your IdP
* Missing or incorrect domain ID in the ACS URL
* ACS URL format doesn't match what Bruno expects
**Solutions:**
1. **Use the exact ACS URL shown in Bruno**: Copy the SAML ACS URL directly from the Bruno SSO settings page
* The URL format may vary depending on your Bruno version
* Common formats:
* `https://license.usebruno.com/saml/acs/{your-domain-id}`
* `https://license.usebruno.com/api/v2/auth/sso/saml/acs/{your-domain-id}`
2. Check that you've replaced `{your-domain-id}` with your actual domain ID from Bruno
3. Ensure there are no trailing slashes or extra spaces in the URL
4. **Important**: The ACS URL in your IdP must match EXACTLY what Bruno shows in the SSO settings page
Always copy the ACS URL directly from the Bruno SSO settings page rather than using the documentation examples, as the URL format may vary between Bruno versions.
### "403 Forbidden" Error After Successful IdP Authentication
**Symptoms:**
* User successfully authenticates with their IdP
* User is redirected back to Bruno
* Bruno shows a 403 Forbidden error or "Access Denied" message
* Login does not complete
**What This Error Means:**
Bruno received a valid SAML response from your IdP, but the user's roles don't match any configured role in Bruno's "Admin Roles" or "User Roles" fields. This is a **role mapping issue** - the role value sent by your IdP doesn't match what's configured in Bruno.
**How Role Matching Works (Backend Logic):**
1. Bruno extracts the `roles` attribute from the SAML response
2. Bruno checks if ANY of the user's roles match the "Admin Roles" field
3. If not, Bruno checks if ANY of the user's roles match the "User Roles" field
4. If neither match, Bruno returns a 403 Forbidden error
5. **Important**: Role matching is case-sensitive and exact (`admin` ≠ `Admin`)
**Debugging Steps:**
1. **Check the SAML response** to see what role value is being sent:
* Open browser dev tools (F12) → Network tab
* Attempt SSO login again
* Look for the POST request to `/api/v2/auth/sso/saml/acs/`
* In the request payload, find the `roles` attribute value
* Example: `admin`
* Note: Users can have multiple roles; Bruno checks if ANY match
2. **Verify role mapping in Bruno**:
* Go to Bruno License Portal → Settings → SSO
* Check the "Admin Roles" and "User Roles" fields
* Ensure the exact role value from the SAML response is listed in one of these fields
* **Important**: Role matching is case-sensitive (`admin` ≠ `Admin`)
**Example Fix:**
* If SAML response shows: `admin`
* In Bruno Admin Roles field, add: `admin`
* Or in Bruno User Roles field, add: `admin`
* Click Save and test again
**Other Possible Causes:**
* Missing `roles` attribute in SAML response (not configured in IdP)
* User not assigned to the Bruno application in IdP
* Quotes in role values (e.g., `"admin"` vs `admin`)
* Role value is empty or whitespace
**Additional Checks:**
1. **User Assignment**: Verify the user is assigned to the Bruno application in your IdP
2. **Role Attribute Present**: Confirm the `roles` attribute is being sent in the SAML response (check Network tab)
3. **Quote Issues**: Remove quotes from role values in your IdP configuration
* ❌ Bad: `"admin"` (with quotes)
* ✅ Good: `admin` (no quotes)
4. **Empty Roles**: Ensure the role value is not empty or just whitespace
**Okta Specific**: For 403 errors in Okta:
* Check the **Group Assignments** tab in your Okta Bruno application - users must be assigned either directly or through groups
* Verify **Application Visibility** settings - if "Hide application for users" is enabled, users may not be able to access it
* Check **Sign On Policy** - ensure there are no conditional access rules blocking the user
* Use Okta's **System Log** (Reports → System Log) to see detailed authentication attempts and failures
**Entra ID Specific**: For 403 errors in Entra ID:
* Check **Users and groups** assignment in Enterprise Applications → Bruno app
* Verify **Conditional Access policies** aren't blocking the user (Azure AD → Security → Conditional Access)
* Check **User Risk Policy** and **Sign-in Risk Policy** if Identity Protection is enabled
* Review **Sign-in logs** (Azure AD → Monitoring → Sign-ins) for detailed error information
* Ensure the user's account is not disabled or requires password reset
**Quick Test**: If you're unsure about role mapping, temporarily add a wildcard or common value like `user` to both Admin Roles and User Roles fields in Bruno, then check what role value your IdP is actually sending in the SAML response.
### "User Not Found" or "Access Denied" After Role Mapping
**Symptoms:**
* User's role matches Bruno's role mapping (403 error is gone)
* But user still cannot access Bruno
* Error message indicates user not found or access denied
**What This Error Means:**
The user's role matched Bruno's configuration, but there's an issue with the user's email or account status. Bruno uses the NameID (email) from the SAML response to identify users.
**How User Identification Works (Backend Logic):**
1. Bruno extracts the NameID from the SAML response (this should be the user's email)
2. Bruno converts the email to lowercase for consistency
3. Bruno looks up the user in the database using this email
4. If the user doesn't exist, Bruno creates a new user account automatically
5. If the user exists but is inactive, Bruno reactivates them
**Debugging Steps:**
1. **Verify the NameID is an email address**:
* Open browser dev tools (F12) → Network tab
* Attempt SSO login again
* Look for the POST request to `/api/v2/auth/sso/saml/acs/`
* In the SAML response, find the NameID value
* Example: `user@example.com`
* Ensure it's a valid email address
2. **Check email consistency**:
* The NameID email should match the user's email in your IdP
* Bruno uses this email to identify and create user accounts
* If the email changes between logins, Bruno will create a new account
3. **Verify user assignment in IdP**:
* Ensure the user is assigned to the Bruno application
* Check that the user's email is correctly configured in the IdP
**Solutions:**
1. Verify the user is assigned to the Bruno application in your IdP
2. Ensure the NameID in your IdP is configured to send the user's email address
3. Check that the user's email in the IdP is valid and consistent
4. Verify the email attribute is correctly mapped in your IdP (should be NameID, not a separate attribute)
### "Invalid Role" or "Insufficient Permissions"
**Possible Causes:**
* `roles` attribute not configured correctly in IdP
* `roles` attribute value doesn't match any role in Bruno's Admin Roles or User Roles fields
* `roles` attribute is missing from the SAML assertion
* Role mapping not configured in Bruno License Portal
**Solutions:**
1. Verify the `roles` attribute is configured in your IdP's SAML attribute statements
2. Ensure the attribute name is exactly `roles` (case-sensitive)
3. Check the SAML response to confirm the `roles` attribute is being sent and note its value
4. In Bruno License Portal → Settings → SSO, verify that the role value from the SAML assertion is listed in either:
* **Admin Roles** field (for admin access), or
* **User Roles** field (for standard access)
5. Ensure role values are case-sensitive matches (e.g., `Engineering` ≠ `engineering`)
6. For your IdP: Verify the attribute/claim name is `roles` and the value matches what you configured in Bruno's role mapping
**Okta Role Debugging**: Use Okta's **Preview SAML assertion** feature:
1. Go to Okta Admin → Applications → Bruno app → General tab
2. Click **Preview SAML assertion**
3. Select a test user to see exactly what role values will be sent
4. Verify the `roles` attribute appears with the expected value
5. Copy this exact value to Bruno's Admin Roles or User Roles field
**Entra ID Role Debugging**: Use the **Test single sign-on** feature:
1. Go to Enterprise Applications → Bruno app → Single sign-on
2. Click **Test single sign-on with Bruno**
3. In the test results, expand **View SAML response** to see the actual claims being sent
4. Look for the `roles` claim and note its value
5. Ensure this exact value is configured in Bruno's role mapping fields
### "Missing User Information" or "Name Not Displayed"
**Possible Causes:**
* `fullName` attribute not configured correctly in IdP
* `fullName` attribute is missing from the SAML assertion
* Name fields in IdP are empty
* `firstName` and `lastName` attributes are not configured as fallback
**What This Error Means:**
Bruno tries to extract the user's full name from the SAML response. If the `fullName` attribute is missing, Bruno attempts to construct it from `firstName` and `lastName` attributes. If all are missing, Bruno uses the email address as the name.
**How Name Extraction Works (Backend Logic):**
1. Bruno first looks for a `fullName` attribute in the SAML response
2. If `fullName` is not found, Bruno looks for `firstName` and `lastName` attributes
3. If found, Bruno combines them: `firstName + " " + lastName`
4. If none of these are found, Bruno uses the email address (NameID) as the name
5. The name is stored in the user's profile
**Debugging Steps:**
1. **Check what name attributes are being sent**:
* Open browser dev tools (F12) → Network tab
* Attempt SSO login again
* Look for the POST request to `/api/v2/auth/sso/saml/acs/`
* In the SAML response, look for these attributes:
* `fullName` (preferred)
* `firstName` and `lastName` (fallback)
* Example: `John Doe`
2. **Verify attribute names are case-insensitive**:
* Bruno looks for attributes case-insensitively (e.g., `fullName`, `FullName`, `FULLNAME` all work)
* But the attribute values are case-sensitive
3. **Check user profile after login**:
* After a successful SSO login, check the user's profile in Bruno
* If the name is just an email address, the name attributes weren't found
**Solutions:**
1. **Configure `fullName` attribute** (recommended):
* Verify the `fullName` attribute is configured in your IdP's SAML attribute statements
* Configure the attribute to combine first and last name (e.g., concatenation of first name + space + last name)
* Check that user profiles in your IdP have first name and last name populated
2. **Or configure `firstName` and `lastName` attributes** (fallback):
* If you can't use `fullName`, configure both `firstName` and `lastName` attributes
* Bruno will automatically combine them with a space
3. **Verify attributes are being sent**:
* Check the SAML response to confirm the name attributes are being sent
* Use your IdP's preview/test feature to verify attribute values
**Okta fullName Configuration**: In Okta attribute statements:
* **Name**: `fullName`
* **Name format**: Unspecified
* **Value**: `user.firstName+" "+user.lastName`
* Alternative: `user.displayName` if your Okta users have display names configured
**Entra ID fullName Configuration**: In Entra ID claims configuration:
* **Claim name**: `fullName`
* **Source**: Transformation
* **Transformation**: `Join()`
* **Parameter 1**: `user.givenname`
* **Separator**: ` ` (space)
* **Parameter 2**: `user.surname`
* Alternative: Use `user.displayname` if available and properly formatted
## Configuration Verification Checklist
Use this checklist to verify your SAML SSO configuration:
### Identity Provider Configuration
* [ ] SAML application created for Bruno
* [ ] ACS URL configured correctly (copy from Bruno SSO settings page)
* [ ] Entity ID configured to match Bruno's SP Issuer ID / Entity ID
* [ ] Name ID format set to EmailAddress
* [ ] **Required attribute: `roles`** configured to send user role values from your IdP (e.g., `Engineering`, `BrunoAdmin`, `IT-Team`, etc.)
* [ ] **Required attribute: `fullName`** configured (e.g., concatenation of first and last name)
* [ ] Users or groups assigned to the Bruno application
* [ ] SAML certificate is valid and not expired
### Bruno Configuration
* [ ] SAML SSO enabled in Bruno License Portal
* [ ] SP Issuer ID / Entity ID configured (must match IdP Entity ID exactly)
* [ ] IdP Login URL entered correctly
* [ ] IdP Entity ID entered correctly
* [ ] IdP Certificate pasted correctly (including BEGIN/END lines)
* [ ] **Admin Roles** configured with role values that should have admin access
* [ ] **User Roles** configured with role values that should have user access
* [ ] Role values match exactly (case-sensitive) with the values sent in the `roles` attribute from IdP
* [ ] Session timeout configured appropriately
## Testing and Debugging
### Test SSO Connection
1. In Bruno License Portal, navigate to **Settings** → **SAML SSO**
2. Click **Test SSO Connection**
3. You should be redirected to your IdP
4. Authenticate with your IdP credentials
5. You should be redirected back to Bruno
6. Verify your user information and roles are displayed correctly
### Check SAML Response
If SSO login fails, you can inspect the SAML response to diagnose issues:
1. Open your browser's developer tools (F12)
2. Go to the **Network** tab
3. Attempt to log in via SSO
4. Look for the POST request to `/saml/acs/`
5. Inspect the SAML response payload for errors
The SAML response contains assertions about the user's identity and attributes. Look for missing or incorrect attribute values.
### Verify Attribute Mapping
To verify that user attributes are being sent correctly:
1. Complete a successful SSO login
2. In Bruno, check the user's profile information
3. Verify that the `fullName` attribute is populated correctly
4. If attributes are missing, check your IdP's attribute mapping configuration
5. Inspect the SAML response (see "Check SAML Response" section) to verify both `roles` and `fullName` attributes are present
### Verify Role Assignment
To verify that roles are being assigned correctly:
1. Complete a successful SSO login
2. Check if the user has the expected access level (admin vs. user)
3. If roles are incorrect:
* Check the SAML response to see what value is being sent in the `roles` attribute
* Verify the `roles` attribute is configured in your IdP
* In Bruno License Portal → Settings → SSO, check the "Admin Roles" and "User Roles" fields
* Ensure the role value from the SAML response matches one of the values in either field (case-sensitive)
* Example: If SAML sends `roles="Engineering"`, make sure `Engineering` is listed in either Admin Roles or User Roles in Bruno
## Certificate Management
### Certificate Expiration
SAML certificates typically expire after 1-3 years. When a certificate expires:
1. Generate a new certificate in your IdP
2. Update the certificate in Bruno SAML SSO settings
3. Test the SSO connection to verify it works
Set a reminder to renew your SAML certificate before it expires to avoid service disruption.
### Certificate Format Issues
If you encounter certificate errors:
1. Ensure the certificate is in PEM format (Base64 encoded)
2. Include the `-----BEGIN CERTIFICATE-----` header
3. Include the `-----END CERTIFICATE-----` footer
4. Remove any extra whitespace or line breaks within the certificate body
5. Do not include the private key (only the public certificate)
## Session and Timeout Issues
### Users Being Logged Out Unexpectedly
**Possible Causes:**
* Session timeout set too low
* IdP session timeout shorter than Bruno session timeout
* Browser cookie issues
**Solutions:**
1. Increase the session timeout in Bruno SAML SSO settings
2. Check your IdP's session timeout settings
3. Clear browser cookies and cache
4. Ensure cookies are enabled in the browser
**Okta Session Management**: Check these Okta settings:
* **Global Session Policy** (Security → Authentication → Global Session Policy)
* **Application Sign On Policy** (Applications → Bruno app → Sign On tab → Sign On Policy)
* **Okta session lifetime** vs **Bruno session timeout** - the shorter one will take precedence
**Entra ID Session Management**: Check these Entra ID settings:
* **Conditional Access policies** with session controls (Azure AD → Security → Conditional Access)
* **Token lifetime policies** (Azure AD → Security → Token lifetime policies)
* **User sign-in frequency** settings in Conditional Access
* **Persistent browser session** settings
### Session Not Ending When Expected
**Possible Causes:**
* Session timeout set too high
* Browser keeping session alive
**Solutions:**
1. Decrease the session timeout in Bruno SAML SSO settings
2. Implement IdP-initiated logout if available
3. Educate users to log out manually when finished
## Attribute Configuration Troubleshooting
### SAML Attribute Configuration
**Issue**: Users not getting correct roles or missing name information
**Solutions:**
1. **Verify the `roles` attribute is configured in your IdP**:
* Attribute/Claim name: `roles`
* Value: Can be any value (e.g., `"admin"`, `"Engineering"`, department name, group name, etc.)
* Ensure the attribute is included in the SAML assertion
2. **Verify the `fullName` attribute is configured in your IdP**:
* Attribute/Claim name: `fullName`
* Value: Combination of first and last name (e.g., "John Doe")
* Common configurations:
* Static concatenation: `firstName + " " + lastName`
* Transformation/expression to join name fields
* Direct mapping from a single name field
3. **Check attribute preview/test in your IdP** to see what values will be sent
4. **Ensure attribute names are case-sensitive and match exactly**
5. **Verify role mapping in Bruno**: In Bruno License Portal → Settings → SSO:
* Check that the role value from your IdP is listed in either "Admin Roles" or "User Roles"
* Example: If your IdP sends `roles="Engineering"`, ensure `Engineering` is in one of the role fields in Bruno
### Testing Roles with Hardcoded Values
**When to use this**: If you're having trouble getting role values to work correctly, you can temporarily configure a hardcoded role value in your IdP. This applies the same role to all users logging in via SSO and helps you isolate whether the issue is with role mapping or attribute configuration.
**Important**: This is for testing only. Once you verify SSO is working, configure proper role values based on your user directory.
**Steps to test with hardcoded roles:**
1. **In your IdP, configure the `roles` attribute with a static/hardcoded value**:
* **Okta**: In the SAML app → General → SAML Attribute Statements, set:
* Name: `roles`
* Value: `admin` (or any test value)
* This will send `roles="admin"` for all users
* **Entra ID**: In Enterprise Applications → Single sign-on → Attributes & Claims, add a new claim:
* Name: `roles`
* Source: Attribute
* Source attribute: Select any attribute (or use a static value if available)
* Or use Transformation with a static value like `"admin"`
2. **In Bruno License Portal, add this test value to Admin Roles**:
* Go to Settings → SSO
* In the "Admin Roles" field, add the value you used (e.g., `admin`)
* Click Save Configuration
3. **Test SSO login**:
* Try logging in via SSO
* If it works, the issue is with your role attribute configuration in the IdP
* If it still fails, the issue is elsewhere (certificate, ACS URL, etc.)
4. **Once testing is complete**:
* Remove the hardcoded value from your IdP
* Configure proper role values based on your user directory (departments, groups, etc.)
* Update Bruno's Admin Roles and User Roles fields with the actual role values
**Important**: Hardcoded role values apply to ALL users logging in via SSO. Only use this for testing. Once you've verified SSO is working, configure role values that map to your actual user attributes or groups.
### Common IdP-Specific Configurations
**For Okta:**
* Attribute statements with Name format: Unspecified
* `roles` value examples: `"admin"`, `user.department`, static values
* `fullName` value: `user.firstName+" "+user.lastName`
**For Microsoft Entra ID:**
* Claims configuration with source: Attribute or Transformation
* `roles` value examples: static `admin`, `user.department`, group mappings
* `fullName` transformation: Join() with `user.givenname`, space separator, and `user.surname`
**For other SAML 2.0 providers:**
* Configure SAML attribute statements or claims
* Ensure attribute names match exactly: `roles` and `fullName`
* Use appropriate value mappings for your user directory
**Okta Troubleshooting Tools**: Leverage these Okta-specific debugging features:
* **System Log** (Reports → System Log): Search for authentication events and errors
* **Preview SAML assertion**: Test attribute values before going live
* **Application dashboard**: Monitor sign-in attempts and failures
* **SAML Tracer browser extension**: Capture and analyze SAML requests/responses
* **Okta Expression Language**: Use for complex attribute transformations
**Entra ID Troubleshooting Tools**: Use these Entra ID-specific debugging features:
* **Sign-in logs** (Azure AD → Monitoring → Sign-ins): Detailed authentication logs with error codes
* **Audit logs** (Azure AD → Monitoring → Audit logs): Track configuration changes
* **Test single sign-on**: Built-in testing tool with SAML response viewer
* **Conditional Access insights**: See which policies are affecting sign-ins
* **Identity Protection**: Monitor for risky sign-ins that might block SSO
* **Enterprise Applications insights**: Application-specific usage and error analytics
## Troubleshooting Entra ID Group Claims
If you're using Entra ID group claims instead of App Roles, these additional troubleshooting steps apply.
### No Groups Appearing in the SAML Response
**Symptoms:** SSO redirects to Entra ID successfully, user authenticates, but Bruno shows "SSO Login failed" or "You do not have necessary permissions". Decoded SAML response has no `groups` or `roles` attribute in the ``.
**Possible Causes and Fixes:**
1. **No group claim configured**
* You need to explicitly add a group claim via "Add a group claim" in Attributes & Claims
* Entra ID does not send group information by default
* **Fix:** Follow the [Group Claims configuration guide](./configure-saml-sso-with-entra-id#option-b-using-entra-id-group-claims)
2. **"Filter groups" is checked with empty/incorrect filters**
* Under Advanced options in the Group Claims panel, if "Filter groups" is checked but the filter fields are empty or misconfigured, all groups will be filtered out
* **Fix:** Uncheck "Filter groups" unless you specifically need to filter
3. **Group not assigned to the Enterprise Application**
* Even if users are members of the AD group, the group must also be assigned to the Bruno Enterprise Application under **Users and groups**
* **Fix:** Go to Enterprise Application → Users and groups → Add the group
4. **Wrong source attribute for cloud-only groups**
* If using `sAMAccountName` but your groups are cloud-only Entra ID groups (not synced from on-prem AD), no group names will be resolved
* **Fix:** Switch to `Cloud-only group display names` or check "Emit group name for cloud-only groups"
5. **Selected wrong group type**
* At the top of the Group Claims panel, ensure you selected "Groups assigned to the application" (not "None", "Security groups", or "All groups")
### How to Inspect the SAML Response
To diagnose group claim issues, inspect the actual SAML response:
1. Open Chrome DevTools → **Network** tab
2. Check **"Preserve log"** (important — the redirect will clear the log otherwise)
3. Attempt the SSO login
4. After the failure, find the POST request to `license.usebruno.com/api/v2/auth/sso/saml/acs/...`
5. In the **Payload** tab, copy the `SAMLResponse` value
6. Decode it at [samltool.io](https://samltool.io) or [samltool.com/decode.php](https://www.samltool.com/decode.php)
7. Look for the `` section — verify a `groups` or `roles` attribute is present with the expected group names
### Group Name Doesn't Match Bruno Role Mapping
**Symptoms:** Groups appear in the SAML response but SSO still fails with "You do not have necessary permissions".
**Cause:** The group name in the SAML assertion doesn't exactly match what's configured in Bruno's Admin Roles or User Roles fields. Values are case-sensitive.
**Fix:** Compare the exact group name from the decoded SAML response with the values in Bruno License Portal → Settings → SSO → Role Mapping. They must match exactly, including casing and spacing.
### Claim Name Not Recognized by Bruno
**Symptoms:** A `groups` attribute is present in the SAML response with correct values, but Bruno still rejects the user.
**Cause:** The claim name may be a full URI (e.g., `http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`) instead of a simple name. Bruno only recognizes: `role`, `roles`, `group`, `groups` (case-insensitive).
**Fix:** In the Group Claims configuration, check **"Customize the name of the group claim"** and set the Name to `groups`. Without this customization, Entra ID uses the full URI format which Bruno does not match.
**Quick Checklist for Group Claims:**
* "Add a group claim" was used (not "Add new claim")
* "Groups assigned to the application" is selected
* "Filter groups" is unchecked under Advanced options
* "Customize the name of the group claim" is checked and Name is set to `groups`
* Source attribute matches your group type (cloud-only vs on-prem)
* The group is assigned to the Enterprise Application (not just users being members)
* Group name in Bruno role mapping matches exactly (case-sensitive)
## Getting Additional Help
If you've tried the troubleshooting steps above and still encounter issues:
1. **Review Documentation**: Re-read the configuration guides for your specific IdP:
* [Configure SAML SSO with Okta](./configure-saml-sso-with-okta)
* [Configure SAML SSO with Entra ID](./configure-saml-sso-with-entra-id)
2. **Contact [Support](https://www.usebruno.com/support)**: Reach out to Bruno support with the following information:
* Your Bruno subscription ID
* Identity provider (Okta, Entra ID, or other SAML 2.0 provider)
* Error messages you're seeing
* Steps you've already tried
* Screenshots of your configuration (redact sensitive information)
## Related Resources
* [SAML SSO Overview](./overview)
* [Configure SAML SSO with Okta](./configure-saml-sso-with-okta)
* [Configure SAML SSO with Entra ID](./configure-saml-sso-with-entra-id)
* [SCIM Provisioning](../scim-provisioning/overview)
# Bruno SCIM API
Source: https://docs.usebruno.com/license-administrators/scim-provisioning/bruno-scim-api
The Bruno SCIM API provides a direct way to manage users and groups programmatically without requiring integration through identity provider applications like Okta or Microsoft Entra. This is ideal for organizations that want to build custom integrations.
**Prerequisites**: Before using the Bruno SCIM API, you must first enable SCIM provisioning in Bruno and generate an API key. Follow the [Enabling SCIM Provisioning](./overview#enabling-scim-provisioning) section in the overview page to complete this setup and save your API key for use in the steps below.
## Who Should Use the SCIM API?
The Bruno SCIM API is designed for:
* **Organizations with custom identity providers** that do not want to leverage a SCIM 2.0 configuration
* **Development teams** building custom user management workflows
* **System administrators** who need programmatic control over user provisioning
## API Collection
Bruno provides a complete API collection to help you get started quickly:
📁 **[Bruno SCIM API Collection](https://github.com/bruno-collections/bruno-scim-api-collection)**
[](https://fetch.usebruno.com?url=https://github.com/bruno-collections/bruno-scim-api-collection.git)
This collection includes:
* **User management** endpoints (create, read, update, delete)
* **Group management** endpoints (create, read, update, delete)
* **Service provider configuration** endpoints
* **Pre-configured environments** for testing
* **Example requests and responses** for all operations
## Authentication
All SCIM API requests require authentication using a bearer token:
```http theme={null}
Authorization: Bearer YOUR_SCIM_API_TOKEN
```
The bearer token is specific to your Bruno authentication domain and can be generated from the SCIM Provisioning settings in your license portal.
## Supported SCIM Operations
Bruno's SCIM implementation follows the SCIM 2.0 specification and supports the following operations:
### User Operations
| Operation | HTTP Method | Endpoint | Description |
| ------------ | ----------- | ------------------- | --------------------------------------- |
| Create User | `POST` | `/Users` | Provision a new user with Bruno license |
| Get User | `GET` | `/Users/{id}` | Retrieve user information by ID |
| Update User | `PUT` | `/Users/{id}` | Update user attributes |
| Patch User | `PATCH` | `/Users/{id}` | Partial user updates |
| Delete User | `DELETE` | `/Users/{id}` | Deprovision user and revoke license |
| List Users | `GET` | `/Users` | Retrieve all users with pagination |
| Filter Users | `GET` | `/Users?filter=...` | Search users by attributes |
### Group Operations
| Operation | HTTP Method | Endpoint | Description |
| ------------- | ----------- | -------------------- | ----------------------------------- |
| Create Group | `POST` | `/Groups` | Create a new user group |
| Get Group | `GET` | `/Groups/{id}` | Retrieve group information by ID |
| Update Group | `PUT` | `/Groups/{id}` | Update group attributes |
| Patch Group | `PATCH` | `/Groups/{id}` | Partial group updates |
| Delete Group | `DELETE` | `/Groups/{id}` | Remove group |
| List Groups | `GET` | `/Groups` | Retrieve all groups with pagination |
| Filter Groups | `GET` | `/Groups?filter=...` | Search groups by attributes |
## User Schema
Bruno supports the following SCIM user attributes:
### Required Fields
* `userName` - User's email address (unique identifier)
* `emails` - Array of email addresses (primary email required)
* `active` - Boolean indicating if user should have access
### Optional Fields
* `externalId` - External system identifier for the user
* `name.givenName` - First name
* `name.familyName` - Last name
* `timezone` - User's timezone (IANA format, e.g., "America/Los\_Angeles")
* `groups` - Array of group memberships
### Bruno-Specific Extensions
* `urn:ietf:params:scim:schemas:extension:newrelic:2.0:User:nrUserType` - User type (Basic User, Core User, or Full User)
## Group Schema
Bruno supports the following SCIM group attributes:
### Required Fields
* `displayName` - Name of the group (must be unique)
### Optional Fields
* `members` - Array of users in the group
* `externalId` - External system identifier for the group
## Example API Calls
### Create a User
```http theme={null}
POST /Users
Authorization: Bearer YOUR_TOKEN
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"externalId": "emp-12345",
"userName": "john.doe@company.com",
"name": {
"givenName": "John",
"familyName": "Doe"
},
"emails": [{
"value": "john.doe@company.com",
"primary": true
}],
"timezone": "America/New_York",
"active": true,
"groups": []
}
```
### Create a Group
```http theme={null}
POST /Groups
Authorization: Bearer YOUR_TOKEN
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"displayName": "Engineering Team",
"members": []
}
```
### Update User Group Membership
```http theme={null}
PATCH /Users/{userId}
Authorization: Bearer YOUR_TOKEN
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [{
"op": "Add",
"path": "groups",
"value": [{
"value": "{groupId}",
"display": "Engineering Team"
}]
}]
}
```
## Error Handling
The Bruno SCIM API returns standard HTTP status codes and SCIM-compliant error responses:
* `200 OK` - Successful operation
* `201 Created` - Resource created successfully
* `204 No Content` - Successful operation with no response body
* `400 Bad Request` - Invalid request format or missing required fields
* `401 Unauthorized` - Invalid or missing authentication token
* `404 Not Found` - Resource not found
* `409 Conflict` - Resource already exists or conflict with current state
* `500 Internal Server Error` - Server error
## Rate Limiting
The Bruno SCIM API implements rate limiting to ensure service stability:
* **Rate limit**: 100 requests per minute per authentication domain
* **Burst limit**: 20 requests per 10-second window
* **Headers**: Rate limit information is included in response headers
## Best Practices
### Security
* **Secure token storage**: Store API tokens securely and rotate them regularly
* **HTTPS only**: Always use HTTPS for API requests
* **Minimal permissions**: Use dedicated service accounts with minimal required permissions
### Performance
* **Batch operations**: Use PATCH operations for bulk updates when possible
* **Pagination**: Implement proper pagination for large result sets
* **Caching**: Cache user and group data to reduce API calls
### Error Handling
* **Retry logic**: Implement exponential backoff for transient errors
* **Logging**: Log all API interactions for debugging and audit purposes
* **Validation**: Validate data before sending API requests
## Getting Started
1. **Download the collection**: Clone or download the [Bruno SCIM API Collection](https://github.com/bruno-collections/bruno-scim-api-collection)
2. **Configure environment**: Set up your base URL and API token in the environment variables
3. **Test connectivity**: Start with the Service Provider Config endpoint to verify your setup
4. **Create test user**: Use the Create User endpoint to provision your first user
5. **Explore operations**: Try different SCIM operations using the provided examples
## Next Steps
* [Configure SCIM with Okta](./configure-scim-with-okta) - If you prefer using Okta's built-in integration
* [SCIM Provisioning Overview](./overview) - Learn more about SCIM concepts and benefits
# Microsoft Entra ID SCIM provisioning with Bruno
Source: https://docs.usebruno.com/license-administrators/scim-provisioning/configure-scim-with-entra-id
This guide will walk you through setting up SCIM (System for Cross-domain Identity Management) provisioning with Bruno using Microsoft Entra ID (formerly Azure Active Directory).
**Prerequisites**: Before configuring Microsoft Entra ID, you must first enable SCIM provisioning in Bruno and generate an API key. Follow the [Enabling SCIM Provisioning](./overview#enabling-scim-provisioning) section in the overview page to complete this setup and save your API key for use in the steps below.
## Create a custom application in Microsoft Entra ID
**Already have an Enterprise Application for SSO?** If you previously set up a Bruno Enterprise Application for [SAML SSO](../saml-sso/configure-saml-sso-with-entra-id), you can reuse it for SCIM provisioning. Skip to the [Configure automatic provisioning](#configure-automatic-provisioning) section to add provisioning to your existing application.
Microsoft Entra ID queries the Bruno SCIM endpoint every 40 minutes for assigned users, and creates or modifies them according to the assignment details you configure.
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/)
2. On the left sidebar, navigate to **Enterprise apps** → **+ New application**
3. Click **Create your own application**
4. In the **Create your own application** panel:
* Enter a name for your application (e.g., "Bruno SCIM Integration")
* Select **Integrate any other application you don't find in the gallery (Non-gallery)**
* Click **Create**
## Configure automatic provisioning
1. In your newly created (or existing) Enterprise Application, select **Provisioning** from the left navigation menu
2. In the **Provisioning Mode** dropdown, select **Automatic**
3. Under **Admin Credentials**, configure the following:
* **Tenant URL**: `https://license.usebruno.com/scim/v2`
* **Secret Token**: Enter the SCIM API key you generated in the [prerequisites step](./overview#enabling-scim-provisioning)
4. Click **Test Connection** to verify that Microsoft Entra ID can connect to Bruno's SCIM endpoint
5. If the test is successful, click **Save** to save the admin credentials
## Configure attribute mappings
Attribute mappings control how user data from Microsoft Entra ID is mapped to Bruno user accounts.
### Configure user attribute mappings
1. Under the **Mappings** section, click **Provision Microsoft Entra ID Users**
2. Under **Target Object Actions**, ensure the following are enabled:
* **Create**
* **Update**
* **Delete**
3. Under **Attribute Mappings**, configure the following mappings. **You must remove any existing attribute mappings that are not on this list** to avoid conflicts:
| Bruno Attribute | Microsoft Entra ID Attribute | Mapping Type | Match objects using this attribute | Apply this mapping |
| ------------------------------ | ------------------------------------------------------------- | ------------ | ---------------------------------- | ------------------ |
| `userName` | `userPrincipalName` | Direct | Yes (Matching precedence: 1) | Always |
| `active` | `Switch([IsSoftDeleted], , "False", "True", "True", "False")` | Expression | No | Always |
| `displayName` | `displayName` | Direct | No | Always |
| `emails[type eq "work"].value` | `userPrincipalName` | Direct | No | Always |
| `name.givenName` | `givenName` | Direct | No | Always |
| `name.familyName` | `surname` | Direct | No | Always |
**Important**:
* The `userName` attribute must map to the user's email address and is used as the unique identifier
* Set **Matching precedence** to `1` for the `userName` mapping
* The `emails[type eq "work"].value` attribute must also map to `userPrincipalName` to ensure email addresses are properly provisioned
* Remove any attribute mappings not listed above to prevent provisioning errors
4. Click **Save** to save the attribute mappings
## Configure provisioning settings
1. Under **Settings**, configure the **Scope**:
* Select **Sync only assigned users and groups** to provision only users and groups explicitly assigned to the Bruno application
* Or select **Sync all users and groups** to provision all users in your directory
2. (Optional) Under **Notification Email**, enter an email address to receive provisioning error notifications
3. Set **Provisioning Status** to **On**
4. Click **Save**
The initial provisioning cycle will begin immediately. Microsoft Entra ID will continue to sync every 40 minutes.
## Assign users and groups to Bruno
1. In your Enterprise Application, navigate to **Users and groups** from the left navigation menu
2. Click **+ Add user/group**
3. Select the users or groups you want to provision with Bruno licenses
4. Click **Assign**
5. Assigned users will receive an email with their **License Key** once the provisioning cycle completes
**Note**: Microsoft Entra ID syncs changes every 40 minutes. For immediate provisioning of specific users, you can use the [on-demand provisioning feature](https://learn.microsoft.com/entra/identity/app-provisioning/provision-on-demand) in Microsoft Entra ID.
## Deprovision users from Bruno
1. To remove a user's access to Bruno, navigate to **Users and groups** in your Bruno application
2. Select the user(s) you want to remove and click **Remove**
3. The user's license will be deactivated during the next provisioning cycle (within 40 minutes), and they will receive an email notification
**Note**: Microsoft Entra ID syncs changes every 40 minutes. For immediate provisioning of specific users, you can use the [on-demand provisioning feature](https://learn.microsoft.com/entra/identity/app-provisioning/provision-on-demand) in Microsoft Entra ID.
## Troubleshooting
### Monitor provisioning activity
1. To monitor provisioning activity, navigate to **Provisioning** in your Bruno application
2. Click **View provisioning logs** to see detailed information about provisioning operations
3. The provisioning logs show:
* Users and groups that were created, updated, or deleted
* Any errors that occurred during provisioning
* Timestamps for each operation
### Common issues
**Connection test fails**
* Verify the SCIM Base URL is correct: `https://license.usebruno.com/scim/v2`
* Ensure your SCIM API key is valid and hasn't been revoked
* Check that SCIM is enabled in your Bruno license portal
**Users not provisioning**
* Verify users are assigned to the Bruno application
* Check the provisioning logs for specific error messages
* Ensure attribute mappings are configured correctly
* Confirm the provisioning status is set to **On**
**Duplicate user errors**
* Ensure the `userName` attribute mapping uses a unique identifier (email address)
* Check that no users with the same email already exist in Bruno
For additional help, [contact Bruno Support](https://www.usebruno.com/support) or visit the [Bruno SCIM API documentation](./bruno-scim-api).
## Related Resources
For more information about configuring SCIM provisioning with Microsoft Entra ID, refer to these Microsoft documentation resources:
* [SCIM synchronization with Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/architecture/sync-scim) - Overview of SCIM protocol and how Microsoft Entra ID uses SCIM for provisioning
* [What is automated app user provisioning in Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/user-provisioning) - Introduction to automated provisioning and SCIM
* [Understand how Application Provisioning in Microsoft Entra ID works](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/how-provisioning-works) - Detailed explanation of the provisioning process and lifecycle
* [Tutorial - Develop a SCIM endpoint for user provisioning](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups) - Guide for implementing SCIM endpoints and attribute mappings
* [On-demand provisioning in Microsoft Entra ID](https://learn.microsoft.com/entra/identity/app-provisioning/provision-on-demand) - Provision specific users immediately without waiting for the sync cycle
* [Troubleshoot provisioning to a Microsoft Entra gallery app](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/troubleshoot) - Common provisioning issues and troubleshooting steps
* [Known issues for provisioning in Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/known-issues) - Known issues and limitations with application provisioning
# Okta SCIM provisioning with Bruno
Source: https://docs.usebruno.com/license-administrators/scim-provisioning/configure-scim-with-okta
This guide will walk you through setting up SCIM (System for Cross-domain Identity Management) provisioning with Bruno using Okta SWA (Secure Web Authentication).
**Prerequisites**: Before configuring Okta, you must first enable SCIM provisioning in Bruno and generate an API key. Follow the [Enabling SCIM Provisioning](./overview#enabling-scim-provisioning) section in the overview page to complete this setup and save your API key for use in the steps below.
## Create your Okta App
**Already have an Okta App for SSO?** If you previously set up a Bruno Okta application for [SAML SSO](../saml-sso/configure-saml-sso-with-okta), you can reuse it for SCIM provisioning. Skip to the [Configure your Okta App](#configure-your-okta-app) section to add provisioning to your existing application.
1. As an Okta admin, navigate to **Applications** and click **Create App Integration**
2. Select **SWA - Secure Web Authentication** and click **Next**
3. On the **Create SWA Integration** page, add the following values and click **Finish**:
* **App name**: (Unique App Name - e.g. Bruno SCIM Integration)
* **App's login page URL**: `https://license.usebruno.com/`
## Configure your Okta App
1. On your newly created application page, select the **General** tab
2. Under **App Settings**, select **Edit**, and check the **Provisioning** option **SCIM** and click **Save**
3. Return to your Okta app, select the **Provisioning** tab, under **SCIM Connection** select **Edit** and configure the following values:
* **SCIM connector base URL**: `https://license.usebruno.com/scim/v2`
* **Unique identifier field for users**: `email`
* **Supported provisioning actions**: Check the following options:
* `Import New Users and Profile Updates`
* `Push New Users`
* `Push Profile Updates`
* `Push Groups`
* **Authentication**: set to **HTTP Header**
* Under **HTTP Header**, for **token**: add the generated API Key value from the [prerequisites step](./overview#enabling-scim-provisioning)
4. Click **Test Connector Configuration**. If successful, a **Connector configured successfully** message dialogue appears.
5. Click **Save** to complete the Provisioning integration.
6. Under the **Provisioning** tab, select the **To Apps** setting, edit and enable the following options and click **Save**:
* `Create Users`
* `Update User Attributes`
* `Deactivate Users`
Your SCIM integration is now complete. You can now begin [assigning users to Bruno](#assign-people-and-groups-to-bruno-in-okta).
## Assign people and groups to Bruno in Okta
1. Under your created Bruno Application, navigate to the **Assignments** tab
2. Select **Assign** and either **Assign to People** or **Assign to Groups** to provision Bruno license keys
3. Assigned Users will receive an email with their **License Key**
## Deprovision users from Bruno in Okta
1. Under your created Bruno Application, navigate to the **Assignments** tab
2. Remove the desired users or groups, their license key(s) will be deactivated and they will receive an email notification
## Related Resources
For more information about configuring SCIM provisioning with Okta, refer to these Okta documentation resources:
* [Add SCIM provisioning to app integrations](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_scim.htm) - Step-by-step guide for adding SCIM provisioning to Okta applications
* [SCIM 2.0 protocol overview](https://help.okta.com/en-us/content/topics/provisioning/opp/opp-provision-scim-messages.htm) - Reference for SCIM 2.0 messages and provisioning operations
* [On-premises provisioning with SCIM](https://help.okta.com/en-us/content/topics/provisioning/opp/opp-architecture.htm) - Architecture and setup for SCIM provisioning
* [Test SCIM connectors](https://help.okta.com/en-us/content/topics/provisioning/opp/opp-test-scim-server.htm) - Guide for testing SCIM connectors and servers
* [Okta provisioning documentation](https://help.okta.com/en-us/content/topics/provisioning/provisioning.htm) - Comprehensive provisioning documentation and best practices
# SCIM Provisioning Overview
Source: https://docs.usebruno.com/license-administrators/scim-provisioning/overview
# SCIM Provisioning Overview
SCIM Provisioning and the SCIM API is available for Ultimate Edition customers behind a feature flag. Please contact your Bruno Account Manager to enable this feature.
## What is SCIM?
SCIM (System for Cross-domain Identity Management) is an open standard that allows for the automation of user provisioning and deprovisioning between identity providers and service providers. With SCIM, organizations can automatically manage user access to Bruno based on their identity provider configurations.
## Benefits of SCIM with Bruno
### Automated User Management
* **Automatic provisioning**: New users are automatically granted Bruno licenses when added to your identity provider
* **Automatic deprovisioning**: Users lose access to Bruno when removed from your identity provider
### Enhanced Security
* **Centralized access control**: Manage Bruno access through your existing identity management system
* **Reduced manual errors**: Eliminate manual license assignment and removal processes
* **Audit trail**: Track all provisioning and deprovisioning activities in your identity provider
### Operational Efficiency
* **Streamlined onboarding**: New employees automatically receive Bruno access as part of their standard onboarding
* **Simplified offboarding**: Departing employees automatically lose Bruno access
* **Reduced administrative overhead**: IT teams spend less time on manual license management
## Enabling SCIM Provisioning
### Prerequisites
You must have Ultimate Edition with SCIM provisioning enabled before you can configure SCIM for your Bruno team.
### Enabling SCIM in Bruno
1. Navigate to the Bruno license portal: [https://license.usebruno.com/](https://license.usebruno.com/)
2. Click **Settings** in the navigation menu
3. Select **SCIM Provisioning** from the settings page
4. Toggle **Enable SCIM** to turn it on
### Generating SCIM API Key
1. Under **SCIM Provisioning**, note the **SCIM Base URL**: `https://license.usebruno.com/scim/v2`
2. Click **Generate New API Key**
3. Copy your new API key for later use when configuring your identity provider - this key will not be shown again
You can revisit this page to manage your SCIM API keys. The SCIM Base URL and API key will be needed when configuring your identity provider.
## Next Steps
Ready to configure SCIM? Choose your integration method:
* [Configure SCIM with Okta](./configure-scim-with-okta) - Complete setup guide for Okta integration
* [Configure SCIM with Microsoft Entra ID](./configure-scim-with-entra-id) - Complete setup guide for Microsoft Entra ID integration
* [Bruno SCIM API](./bruno-scim-api) - Direct API integration for custom implementations
# Activating Your License
Source: https://docs.usebruno.com/license-end-users/activate-license
## Activating with License Key
If you've been provisioned a license, you should have received a license key via email. If you have not received a key and already checked your spam folder, please reach out to your license administrator.
### Step 1: Click on the Settings or Key icon in Bruno
Both icons are located at the bottom left corner of the Bruno window. The Key icon will take you directly to the `License` section. If you choose to use the Settings icon, simply select `License` from the settings menu.
### Step 2: Enter License Details
Enter your license key and associated email address
If your company uses a self-hosted licensing server, select `License Server` and enter the details provided to you by your License Administrator.
### Step 3: License Activated
Once the license is activated, you will see a success message.
## Activating with SSO
SSO license activation is available for Ultimate Edition customers. Please see the [SAML SSO Overview](/license-administrators/saml-sso/overview) for more information.
### Step 1: Click on the Settings or Key icon in Bruno
Both icons are located at the bottom left corner of the Bruno window. The Key icon will take you directly to the `License` section. If you choose to use the Settings icon, simply select `License` from the settings menu.
### Step 2: Activate with SSO
If your company has configured SSO for Bruno, select `SSO` and click `Activate License`.
Your email needs to be part of the SSO configuration in order to activate your license, with the `User Role` mapped. See [Role Mapping](/license-administrators/saml-sso/overview#role-mapping) for details.
### Step 3: SSO Login
You will be redirected to your corporate SSO login page. Enter your credentials to login.
A successful login message will be displayed.
Returning to Bruno, you will see the license as activated.
# Overview
Source: https://docs.usebruno.com/license-overview
For our open-source users, there is no concept of license management! Simply download, install, and go!
Our commercial plans have different license management aspects. If you have any questions about what plan is best suited for you, please see our [pricing page ↗](https://www.usebruno.com/pricing) or [contact us ↗](mailto:sales@usebruno.com) to discuss.
## License Management
### For License Administrators
* [License Portal](./license-administrators/license-portal)
* [SAML SSO](./license-administrators/saml-sso/overview)
* [SCIM Provisioning](./license-administrators/scim-provisioning/overview)
### For Users
* [Activate License](./license-end-users/activate-license)
# Create OpenAPI Specification (OAS)
Source: https://docs.usebruno.com/open-api/createOAS
Bruno provides a powerful interface for creating and managing OpenAPI Specifications (OAS). You can either design a new specification from scratch and view one as existing Open API spec.
## Viewing Existing OAS Files
You can view and interact with existing OAS files in Bruno using a SwaggerUI-like interface:
1. Enable OpenAPI Spec feature from **Preferences** to start working with it.
2. Select **Open API Spec** from dropdown in left hand side menu.
3. Browse and select your OAS file (`.yaml` or `.json`)
4. The specification will open in a dedicated section of your Bruno sidebar
## Creating New OAS Files
1. Select **Create API Spec** from dropdown
2. Choose a name and location for your specification
3. Click on **Create** to generate OAS file.
## Editing Your OAS File
Once created, you can edit your OAS file in Bruno's built-in editor:
1. The editor provides syntax highlighting
2. Use the sidebar to navigate through different sections of your API
3. Add or modify endpoints, parameters, and responses
4. Include examples and descriptions for better documentation
# Export to OpenAPI Specification (OAS)
Source: https://docs.usebruno.com/open-api/exportOAS
Bruno allows you to export your collections as OpenAPI Specification (OAS) files, enabling you to share your API definitions with other tools and team members. This feature helps maintain consistency across your API documentation and development workflow.
Bruno exports collections in OpenAPI Specification (OAS) V3 format, which is widely supported by API tools and platforms.
## Prerequisites
Before exporting a collection, ensure:
* Your collection is properly organized with clear request structures
* All endpoints have appropriate HTTP methods defined
* Request parameters and headers are properly configured
### Exporting to a File
1. Open Bruno and navigate to your collection
2. Click on the context menu (three dots) in the collections panel
3. Select **Share** from the dropdown menu
4. Choose **OpenAPI Specification** option
5. Enter name of your file and location.
6. Click **Create**
## Example Exported Structure
```yaml theme={null}
openapi: 3.0.0
info:
title: Example API
version: 1.0.0
description: API documentation exported from Bruno
paths:
/users:
get:
summary: Get all users
parameters:
- name: limit
in: query
schema:
type: integer
responses:
'200':
description: Successful response
```
# Import OpenAPI Specification (OAS)
Source: https://docs.usebruno.com/open-api/importOAS
Bruno provides seamless integration with OpenAPI Specification (OAS), allowing you to import API definitions through both files and URLs. This feature enables you to quickly create collections in Bruno by leveraging existing OpenAPI documentation.
Bruno supports importing **OpenAPI 2.0** and 3.x\*\* specifications, in **YAML** or **JSON**.
## Prerequisites
Before importing an OpenAPI specification, ensure you have:
* A valid **OpenAPI 2.0 or 3.x** specification file (YAML or JSON format)
* Or a publicly accessible URL that returns such a specification
## Import Methods
Bruno offers two ways to import OpenAPI specifications:
1. **File Import**: Import from a local OpenAPI specification file
2. **URL Import**: Import directly from a publicly accessible OpenAPI specification URL
3. **Bruno Converter**: Programmatically convert an OAS file to a Bruno collection [by using our converter ↗.](https://docs.usebruno.com/converters/overview)
### Importing from a File
1. Open Bruno and navigate to your collection
2. Click on the context menu (three dots) in the collections panel
3. Select **Import** from the dropdown menu
4. Choose **OpenAPI** option
5. Browse and select your OpenAPI specification file (`.yaml` or `.json`)
6. Configure the import settings (see [Import Settings](#import-settings) below)
7. Click **Import** to create the collection
### Importing from a URL
1. Select **OpenAPI** and choose the URL option
2. Provide the public URL of the OpenAPI file you're trying to import
3. Configure the import settings (see [Import Settings](#import-settings) below)
4. Click **Import** to create the collection
After importing the OAS file, the sample collection will look like this:
## Import Settings
Bruno provides flexible options for organizing your imported OpenAPI collection. You can choose how to group your API endpoints into folders during the import process.
### Folder Arrangement Options
When importing an OpenAPI specification, you can choose between two folder grouping strategies:
#### Path-Based Grouping
Path-based grouping organizes endpoints into folders based on their URL path structure. This approach mirrors how APIs are typically organized and provides better compatibility with Postman imports.
**Example:**
```
/api/v1/users → /api/v1/users/ folder
/api/v1/users/{id} → /api/v1/users/ folder
/api/v1/orders → /api/v1/orders/ folder
/api/v1/orders/{id} → /api/v1/orders/ folder
```
#### Tag-Based Grouping (default)
Tag-based grouping organizes endpoints into folders based on the `tags` defined in the OpenAPI specification. This approach groups endpoints by their functional purpose as defined by the API author.
**Example:**
```
# OpenAPI spec with tags
paths:
/users:
get:
tags: [User Management]
/users/{id}:
get:
tags: [User Management]
/orders:
get:
tags: [Order Management]
```
**Tag Name Formatting**: BRU format supports alphanumeric characters, hyphens (-), and underscores (\_) in tag names. When importing collections with tags containing spaces, Bruno automatically replaces them with underscores. For example, `User Management` becomes `User_Management`. To preserve original tag names, use the OpenCollection (YAML) format instead.
### Configuring Import Settings
1. **Select Import Method**: Choose either file or URL import
2. **Choose Folder Arrangement**: Select your preferred grouping method from the dropdown:
* **Paths**: Group by URL path structure
* **Tags** (default): Group by OpenAPI tags
3. **Import**: Click the import button to create your collection
## Example OpenAPI Specifications
You can test the import functionality using these example OpenAPI specifications:
1. **Petstore API**:
* URL: `https://petstore3.swagger.io/api/v3/openapi.json`
* Description: A sample API for a pet store
# OpenAPI Sync
Source: https://docs.usebruno.com/open-api/openapi-sync
OpenAPI Sync keeps your Bruno collection aligned with a remote OpenAPI/Swagger spec. It tracks changes locally, removes the manual effort of re-importing when the spec changes, and helps teams stay in sync.
Open Source edition allows **5 syncs per month**. For unlimited syncs, you need [Bruno Pro or Ultimate](https://www.usebruno.com/pricing).
## Connect to OpenAPI Sync
There are two ways to connect to OpenAPI Sync:
1. [Existing Collection](#existing-collection)
2. [New Import](#new-import)
### Existing Collection
1. Click on the **context menu** (**`···`**) in your collection.
2. Select option **OpenAPI (Beta)**.
3. You have two options:
* **Add URL** – Enter the remote OpenAPI spec URL, or
* **Upload file** – Upload an OpenAPI spec file from your machine.
4. Click **Connect**.
### New Import
1. Import collection with OpenAPI spec file or URL.
2. Enable option **Check for Spec Updates** to automatically connect to OpenAPI Sync.
If you have the same endpoints in your OpenAPI spec and collection, you don't need to sync. If the endpoints don't match, then you have to perform **Initial Sync** changes with the collection.
**Beta feature:** OpenAPI Sync is in beta. When you sync, your collection is updated from the spec—your tests and scripts are preserved. We recommend backing up your collection or reviewing the spec before your first sync so you're comfortable with the result.
## OpenAPI Sync UI at a glance
The UI shows a summary of sync status:
### 1. Overview
Your dashboard: summary of sync status, pending spec updates, and local collection changes. Shows **last synced** time and **spec version** (or title) so you know which spec version the collection is based on.
| Term | Meaning |
| ------------------------- | --------------------------------------------------------------------- |
| **Total in Collection** | Total number of endpoints in your collection. |
| **In Sync with Spec** | Endpoints where your local collection matches the OpenAPI spec. |
| **Changed in Collection** | Changes you made locally in Bruno (URL, params, headers, body, etc.). |
| **Spec Updated Pending** | Changes from the remote spec that are not yet applied locally. |
### 2. Collection Changes
Shows **your local collection changes** in Bruno:
* **Deleted** – Endpoints or fields removed in your collection.
* **Added** – Endpoints or fields added in your collection.
* **Modified** – Endpoints or fields modified in your collection.
Only **body, URL, params, and headers** are tracked here. Tests, scripts, test assertions, and other request settings are **not** tracked in this tab. You can reset, open and view the changes.
### 3. Spec Updated
Shows when the **remote spec has been updated** and changes are pending. You can:
* **Sync collection** – Apply the remote spec changes to your local collection
* **Skip All** – Skip all the changes and continue with your local collection
* **Accept All** – Accept all the changes
* **Keep Current** – Keep the current changes
Click **Confirm and Sync Collection** to apply the remote spec changes to your local collection.
**Merge conflicts:** If you're working on the same file and there are changes in the remote spec, you may see conflicts. Review each change in the Spec Updated tab and accept or reject as needed.
## What gets updated when you sync
* **From spec:** URLs, parameters, request bodies, and folder/request structure are regenerated from the latest spec.
* **Preserved:** Your tests, scripts, and assertions are kept.
* **Stored locally:** The latest spec is saved under `collection/resources/spec/` for offline use.
## Connection Settings
OpenAPI Sync checks for spec updates automatically **every 5 minutes**. You can control this setting as follows:
1. Click the context menu (**`···`**) next to **View spec**.
2. Select **Edit connection settings**.
In the connection settings you can enable or disable automatic checks, disconnect sync and adjust when Bruno looks for spec updates.
## Inspect Spec
To view and inspect the OpenAPI spec:
1. Click **View spec** on the top right-hand side of the OpenAPI Sync UI.
Its **read only** SwaggerUI-like interface to the OpenAPI spec.
Please share your feedback or report issues [here](https://github.com/usebruno/bruno/discussions/7401).
# OpenAPI and Bruno
Source: https://docs.usebruno.com/open-api/overview
OpenAPI Specification (OAS) is a standard format for reading and describing HTTP APIs. This allows people to understand how an API works, how a sequence of APIs work together, generate client code, create tests, apply design standards, and more.
Bruno allows you to work with OpenAPI Specification (OAS) files in a number of ways. **Import** supports both **OpenAPI 2.0** (Swagger 2.0) and **OpenAPI 3.x**; see [Import an OpenAPI Spec](/open-api/importOAS) for details.
* [Creating a collection from an OAS file import](/open-api/importOAS)
* [Exporting a collection as an OAS file](/open-api/exportOAS)
* [Viewing and Designing an OAS file](/open-api/createOAS)
# OpenCollection YAML Format
Source: https://docs.usebruno.com/opencollection-yaml/overview
YAML support is available starting with Bruno 3.0.0. Bruno continues to support `.bru` files alongside the new YAML format.
Starting with Bruno 3.0.0, you can save your API request data using YAML (`.yml`) files as an alternative to the `.bru` format. This YAML format follows the [OpenCollection specification](https://spec.opencollection.com/), an **open specification created by Bruno** for defining executable API collections.
## Why OpenCollection YAML?
OpenCollection combines the power of an open specification with the industry-standard YAML format:
### Open Specification
* **Community-driven standard** — OpenCollection is an open specification created by Bruno, designed to be transparent and extensible
* **No vendor lock-in** — Your API collections are stored in a well-documented, open format that you fully own and control
* **Interoperability** — Build tooling, integrations, and workflows around a documented specification
### Industry-Standard YAML Format
* **Universal format** — YAML is one of the most widely adopted data serialization formats, used across the software industry
* **Zero learning curve** — If you've worked with Kubernetes, Docker Compose, GitHub Actions, or countless other tools, you already know YAML
* **Human-readable** — Clean, intuitive syntax that's easy to read, write, and review in pull requests
### Seamless Tooling Integration
Since everything is stored in standard YAML, you can leverage the entire ecosystem of existing tools:
* **IDE support** — Native syntax highlighting in VS Code, JetBrains IDEs, Vim, and virtually any editor without additional extensions
* **Linting & validation** — Use tools like `yamllint`, `prettier`, or custom JSON Schema validators
* **Git integration** — GitHub, GitLab, and Bitbucket provide built-in YAML syntax highlighting and diff views for pull requests
* **Scripting & automation** — Parse and manipulate collections with standard YAML libraries in any programming language (Python, Node.js, Go, etc.)
* **CI/CD pipelines** — Easily integrate with existing pipeline tools that already understand YAML
## OpenCollection vs OpenAPI
OpenCollection and OpenAPI serve complementary purposes:
| OpenAPI | OpenCollection |
| ------------------------------------------------------------------ | --------------------------------------------------------------------- |
| Defines **what** your API is — the contract, schema, and structure | Defines **how** to use your API — scenarios, workflows, and execution |
| API endpoints and HTTP methods | Business workflows and sequences |
| Request and response schemas | Pre-request scripts and tests |
| Authentication requirements | Environment variables and secrets |
| Data types and validation rules | Runnable, shareable collections |
**OpenAPI tells you the shape of the door. OpenCollection shows you how to walk through it.**
Learn more about OpenCollection at [opencollection.com](https://www.opencollection.com/) and view the full specification at [spec.opencollection.com](https://spec.opencollection.com/).
## Quick Example
Here's what a simple POST request looks like in YAML format:
```yaml theme={null}
info:
name: Create User
type: http
seq: 1
http:
method: POST
url: https://api.example.com/users
body:
type: json
data: |-
{
"name": "John Doe",
"email": "john@example.com"
}
auth: inherit
runtime:
scripts:
- type: tests
code: |-
test("should return 201", function() {
expect(res.status).to.equal(201);
});
settings:
encodeUrl: true
```
Compare this to the equivalent `.bru` file:
```bru theme={null}
meta {
name: Create User
type: http
seq: 1
}
post {
url: https://api.example.com/users
body: json
auth: inherit
}
body:json {
{
"name": "John Doe",
"email": "john@example.com"
}
}
tests {
test("should return 201", function() {
expect(res.status).to.equal(201);
});
}
```
## File Storage
When using YAML format, your collections will be stored with `.yml` file extensions instead of `.bru`. The folder structure uses `opencollection.yml` as the collection root file:
```
my-collection/
├── opencollection.yml # Collection configuration (YAML format)
├── environments/
│ └── development.yml
├── users/
│ ├── folder.yml # Folder configuration
│ ├── create-user.yml
│ ├── get-user.yml
│ └── delete-user.yml
└── orders/
└── create-order.yml
```
Compare this to the `.bru` format structure which uses `bruno.json`:
```
my-collection/
├── bruno.json # Collection configuration (Bru format)
├── collection.bru # Collection-level settings
├── environments/
│ └── development.bru
├── users/
│ ├── folder.bru # Folder configuration
│ ├── create-user.bru
│ ├── get-user.bru
│ └── delete-user.bru
└── orders/
└── create-order.bru
```
## Migration
Bruno supports both `.bru` and `.yml` formats, so you can migrate your collections gradually. Both formats can coexist within the same collection during the transition period.
Migration tooling is planned for a future release. For now, you can manually convert files or create new requests using the YAML format.
## Resources
* [OpenCollection Specification](https://spec.opencollection.com/)
* [OpenCollection JSON Schema](https://schema.opencollection.com/)
# YAML Samples
Source: https://docs.usebruno.com/opencollection-yaml/samples
Here are sample YAML files showing common API request patterns.
## GET Request
```yaml theme={null}
info:
name: Get User
type: http
seq: 1
http:
method: GET
url: https://api.github.com/users/usebruno
settings:
encodeUrl: true
```
## GET with Headers
```yaml theme={null}
info:
name: Get with Headers
type: http
seq: 2
http:
method: GET
url: https://api.example.com/data
headers:
- name: Content-Type
value: application/json
- name: Authorization
value: Bearer topsecret
settings:
encodeUrl: true
```
## GET with Query Parameters
```yaml theme={null}
info:
name: Search Users
type: http
seq: 3
http:
method: GET
url: https://api.example.com/users?filter=active&limit=10&page=1
params:
- name: filter
value: active
type: query
- name: limit
value: "10"
type: query
- name: page
value: "1"
type: query
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
```
## GET with Path Parameters
```yaml theme={null}
info:
name: Get User by ID
type: http
seq: 4
http:
method: GET
url: https://api.example.com/users/:id
params:
- name: id
value: ""
type: path
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
```
## POST with JSON Body
```yaml theme={null}
info:
name: Create User
type: http
seq: 5
http:
method: POST
url: https://api.example.com/users
body:
type: json
data: |-
{
"name": "John Doe",
"email": "john@example.com"
}
settings:
encodeUrl: true
```
## POST with Form Data
```yaml theme={null}
info:
name: Upload Form
type: http
seq: 6
http:
method: POST
url: https://api.example.com/submit
body:
type: form-urlencoded
data:
- name: username
value: johndoe
- name: password
value: secret123
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
```
## Request with Authentication
```yaml theme={null}
info:
name: Authenticated Request
type: http
seq: 7
http:
method: GET
url: https://api.example.com/protected
auth:
type: basic
username: admin
password: secret
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
```
## Request with Pre-Request Script
```yaml theme={null}
info:
name: Request with Script
type: http
seq: 8
http:
method: POST
url: https://api.example.com/data
runtime:
scripts:
- type: before-request
code: |-
// Set dynamic timestamp
const timestamp = Date.now();
bru.setVar("timestamp", timestamp);
settings:
encodeUrl: true
```
## Request with Post-Response Script
```yaml theme={null}
info:
name: Login
type: http
seq: 9
http:
method: POST
url: https://api.example.com/login
body:
type: json
data: |-
{
"username": "johnnash",
"password": "governingdynamics"
}
runtime:
scripts:
- type: after-response
code: |-
// Save token for subsequent requests
bru.setVar("token", res.body.token);
settings:
encodeUrl: true
```
## Request with Tests
```yaml theme={null}
info:
name: Login with Tests
type: http
seq: 10
http:
method: POST
url: https://api.example.com/login
body:
type: json
data: |-
{
"username": "johnnash",
"password": "governingdynamics"
}
runtime:
scripts:
- type: tests
code: |-
test("should be able to login", function() {
expect(res.status).to.equal(201);
});
test("should receive the token", function() {
expect(res.body.token).to.be.a('string');
});
settings:
encodeUrl: true
```
## Request with Assertions
```yaml theme={null}
info:
name: Request with Assertions
type: http
seq: 11
http:
method: POST
url: https://api.example.com/users
runtime:
assertions:
- expression: res.status
operator: eq
value: "201"
- expression: res.body.name
operator: isString
settings:
encodeUrl: true
```
# YAML Structure Reference
Source: https://docs.usebruno.com/opencollection-yaml/structure-reference
This page documents the structure of OpenCollection YAML files used in Bruno.
## Top-Level Structure
A Bruno YAML request file contains the following top-level sections:
```yaml theme={null}
info: # Request metadata (name, type, seq, tags)
http: # HTTP request configuration
runtime: # Scripts and assertions
settings: # Request settings
docs: # Request documentation
```
## info
Store metadata about your request.
```yaml theme={null}
info:
name: Get Users
type: http
seq: 1
tags:
- smoke
- regression
```
| Field | Type | Description |
| ------ | ------ | ----------------------------------------------------------------- |
| `name` | string | The display name of the request |
| `type` | string | The request type (`http` for HTTP requests, `folder` for folders) |
| `seq` | number | Sequence number that determines sort position in the UI |
| `tags` | array | Optional tags for filtering requests during collection runs |
## http
The HTTP request configuration.
```yaml theme={null}
http:
method: post
url: https://api.example.com/users
params:
query: [...]
path: [...]
headers: [...]
body:
type: json
data: "..."
auth:
type: basic
basic:
username: admin
password: secret
```
### http.method
The HTTP method. Supported values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`, `TRACE`, `CONNECT` (uppercase).
### http.params
Parameters as an array of objects with a `type` field to distinguish query vs path parameters.
```yaml theme={null}
params:
- name: filter
value: active
type: query
- name: limit
value: "10"
type: query
- name: id
value: "123"
type: path
```
| Field | Type | Description |
| ---------- | ------- | -------------------------------------------- |
| `name` | string | The parameter name |
| `value` | string | The parameter value |
| `type` | string | Either `query` or `path` |
| `disabled` | boolean | Whether the parameter is disabled (optional) |
### http.headers
Request headers as an array of objects.
```yaml theme={null}
headers:
- name: Content-Type
value: application/json
- name: Authorization
value: Bearer {{token}}
disabled: true
```
| Field | Type | Description |
| ---------- | ------- | ----------------------------------------- |
| `name` | string | The header name |
| `value` | string | The header value |
| `disabled` | boolean | Whether the header is disabled (optional) |
### http.body
The request body configuration.
```yaml theme={null}
body:
type: json
data: |-
{
"name": "John Doe"
}
```
| Body Type | Description |
| ----------------- | --------------------- |
| `json` | JSON body |
| `text` | Plain text body |
| `xml` | XML body |
| `form-urlencoded` | Form URL-encoded data |
| `multipart-form` | Multipart form data |
| `graphql` | GraphQL query |
### http.auth
Authentication configuration. Credentials are specified directly under the `auth` object. Use `inherit` to inherit authentication from the parent folder or collection.
```yaml theme={null}
# Inherit from parent
auth: inherit
# Bearer token
auth:
type: bearer
token: "{{token}}"
# Basic authentication
auth:
type: basic
username: admin
password: secret
# API Key
auth:
type: apikey
key: x-api-key
value: "{{api-key}}"
placement: header
# OAuth 1.0
auth:
type: oauth1
consumerKey: "{{consumer_key}}"
consumerSecret: "{{consumer_secret}}"
accessToken: "{{access_token}}"
accessTokenSecret: "{{token_secret}}"
signatureMethod: HMAC-SHA1
version: "1.0"
placement: header
includeBodyHash: false
```
Supported auth types: `none`, `inherit`, `basic`, `bearer`, `apikey`, `digest`, `oauth1`, `oauth2`, `awsv4`, `ntlm`, `wsse`.
## runtime
The runtime section contains scripts and assertions that execute during the request lifecycle.
### runtime.scripts
JavaScript code to run at different points in the request lifecycle.
```yaml theme={null}
runtime:
scripts:
- type: before-request
code: |-
// Runs before the request
console.log('before-request');
req.setHeader("X-Timestamp", Date.now());
- type: after-response
code: |-
// Runs after the response
console.log('after-response');
bru.setVar("token", res.body.token);
- type: tests
code: |-
test("should return 200", function() {
expect(res.status).to.equal(200);
});
```
| Script Type | Description |
| ---------------- | ------------------------------------------------ |
| `before-request` | Runs before the request is sent |
| `after-response` | Runs after the response is received |
| `tests` | Test assertions using the Chai assertion library |
### runtime.assertions
Declarative assertions without writing JavaScript code.
```yaml theme={null}
runtime:
assertions:
- expression: res.status
operator: eq
value: "200"
- expression: res.body.name
operator: isString
```
| Field | Type | Description |
| ------------ | ------ | ------------------------------------------------------------------- |
| `expression` | string | The value to evaluate (e.g., `res.status`, `res.body.name`) |
| `operator` | string | The comparison operator (`eq`, `neq`, `isString`, `isNumber`, etc.) |
| `value` | string | The expected value (for comparison operators) |
## settings
Request-level settings.
```yaml theme={null}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
```
| Field | Type | Description |
| ----------------- | ------- | ------------------------------------------------ |
| `encodeUrl` | boolean | Whether to URL-encode the request URL |
| `timeout` | number | Request timeout in milliseconds (0 = no timeout) |
| `followRedirects` | boolean | Whether to follow HTTP redirects |
| `maxRedirects` | number | Maximum number of redirects to follow |
## docs
Request-level documentation in Markdown format.
```yaml theme={null}
docs: |-
# User Creation API
This endpoint creates a new user in the system.
## Required Fields
- name: User's full name
- email: User's email address
```
# Using secrets in Request Fields
Source: https://docs.usebruno.com/secrets-management/secret-managers/aws-secrets-manager/using-secrets
Secrets are accessed in the same way as collection and environment variables. The secrets can be accessed in headers, query, body, auth input fields similar to collection and environment variables.
Secrets need to be prefixed with `$secrets` followed by the `secret name` and then the `key name`, all separated by periods.
Pattern: `$secrets`.``.``.
If you have a secret named dbCredentials with a key username, you can reference it as: `$secrets.dbCredentials.username`
## Using Secrets in Scripts
You can also access secrets from within Pre-request and Post-request scripts using the `bru.getSecretVar()` function.
```javascript theme={null}
const secretValue = bru.getSecretVar('.');
console.log(secretValue); // This will log the value of your secret
// Example: Using AWS Secrets Manager secrets in authentication
const apiKey = bru.getSecretVar('aws-secrets.api-key');
req.setHeader('Authorization', 'Bearer ' + apiKey);
```
This approach keeps your sensitive data secure while allowing you to leverage AWS Secrets Manager secrets in your API automation scripts.
# Adding Azure Secret Manager
Source: https://docs.usebruno.com/secrets-management/secret-managers/azure-key-vault/adding-a-secret-provider
You can configure Azure Key Vault as a secret provider in Bruno using two authentication methods:
* Manual Authentication
* Azure CLI Authentication
### Manual Authentication
1. Go to the **Preferences** settings and navigate to the **Secrets Manager** section.
2. Click on the `+ Add Secret Provider` button.
3. Choose **Azure Key Vault** as the provider type.
4. Provide the required credentials, including:
* **Tenant ID** : The Azure Active Directory (AAD) tenant ID.
* **Client ID** : The Azure Active Directory (AAD) client ID.
* **Client Secret** : The Azure Active Directory (AAD) client secret.
5. Use the `Test Provider` feature to verify your connection.
6. Click `Add` to save the secret provider configuration.
For **Azure CLI authentication**, see the [Azure CLI Authentication](/secrets-management/secret-managers/azure-key-vault/cli-authentication) guide.
# Azure CLI Authentication
Source: https://docs.usebruno.com/secrets-management/secret-managers/azure-key-vault/cli-authentication
Azure CLI authentication feature allows you to authenticate with Azure Key Vault using your existing Azure CLI session, without requiring manual entry of Tenant ID, Client ID, or Client Secret in Bruno.
## Prerequisites
1. **Azure CLI Installation**: Ensure Azure CLI is installed on your system
2. **Azure CLI Authentication**: Log in to Azure CLI using `az login`
3. **Key Vault Access**: Ensure your Azure account has access to the target Key Vault
## Setup Instructions
### Step 1: Install and Configure Azure CLI
1. Install Azure CLI from [Microsoft's official website](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
2. Open terminal/command prompt and run:
```bash theme={null}
az login
```
3. Follow the browser-based authentication process
### Step 2: Configure Secrets
1. Navigate to the **Secrets** tab in your collection
2. Click on **Select Provider** and select **Azure Key Vault** from dropdown.
3. In the **EDIT PROVIDER** dialog:
* Click **+ Add Secret**
* Enter the **Name** for your secret
* Enter the **Vault Name** (your Azure Key Vault name)
4. Click on **Save** button.
### Step 3: Fetch Secrets
1. Click **Fetch Secrets** to retrieve secrets from Azure Key Vault
2. In the **FETCH AZURE SECRETS** dialog:
* Select the **CLI** tab
* Click **Test CLI** to verify authentication
* Click **Fetch** to retrieve secrets
3. If successful, you'll see secrets fetched in the **Secrets** tab.
Bruno Azure CLI authentication for Azure Key Vault integration, eliminating the need to store credentials locally and providing a more secure authentication method.
# Configuring and Fetching secrets
Source: https://docs.usebruno.com/secrets-management/secret-managers/azure-key-vault/configuring-and-fetching-secrets
Secrets in Bruno are environment-specific, so ensure you have a collection environment selected before proceeding.
1. Go to **Collection Settings** and navigate to the **Secrets** tab.
2. Select `Azure vault` as the provider.
3. Choose an environment if not already selected. (Secrets are environment-specific)
4. Add a new secret by clicking on the `+ Add Secret` button.
5. Provide a name for the secret and specify the Vault name.
6. Click on the `Fetch Secrets` button on the top right corner, choose one of the accounts, and click on the `Fetch` button.
7. The fetched secrets will be displayed in the table along with their names.
# Overview
Source: https://docs.usebruno.com/secrets-management/secret-managers/azure-key-vault/overview
# Azure Key Vault
Azure Key Vault is a secure and scalable service designed to store and retrieve sensitive information such as API keys, database credentials, and other secrets.
Bruno allows you to easily integrate with Azure Key Vault and securely access secrets during test execution without exposing them in test scripts or environment variables.
## Try it out
Explore the [azure-vault](https://github.com/bruno-collections/azure-vault) sample collection to see Bruno's Azure Key Vault integration in action:
This guide provides detailed steps to configure and utilize Azure Key Vault in Bruno.
* [Adding a secret provider](./adding-a-secret-provider)
* [Azure CLI Authentication](./cli-authentication)
* [Configuring and fetching secrets](./configuring-and-fetching-secrets)
* [Using secrets in Bruno](./using-secrets)
Bruno requires two specific permissions to be able to work with Azure Key Vault:
* Microsoft.KeyVault/vaults/secrets/read **to read secret values**
* Microsoft.KeyVault/vaults/secrets/readMetadata/action **to list available secrets**
# Using secrets in Request Fields
Source: https://docs.usebruno.com/secrets-management/secret-managers/azure-key-vault/using-secrets
Secrets are accessed in the same way as collection and environment variables. The secrets can be accessed in headers, query, body, auth input fields similar to collection and environment variables.
Secrets need to be prefixed with `$secrets` followed by the `secret name` and then the `key name`, all separated by periods.
Pattern: `{{$secrets`.``.`}}`.
If you have a secret named dbCredentials with a key username, you can reference it as: `{{$secrets.dbCredentials.username}}`
## Using Secrets in Scripts
You can also access secrets from within Pre-request and Post-request scripts using the `bru.getSecretVar()` function.
```javascript theme={null}
const secretValue = bru.getSecretVar('.');
console.log(secretValue); // This will log the value of your secret
// Example: Using Azure Key Vault secrets in authentication
const apiKey = bru.getSecretVar('azure-secrets.api-key');
req.setHeader('Authorization', 'Bearer ' + apiKey);
```
Make sure your Azure Key Vault secret provider is properly configured and the secrets are fetched before using them in scripts. Verify that the secret names and key names match exactly with your Azure Key Vault configuration.
# Install and Configure
Source: https://docs.usebruno.com/vs-code-extension/install-config
To start using the Bruno VS Code extension, install it from the official [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=bruno-api-client.bruno), or simply open the Extensions panel in your editor and search for **Bruno**.
Once installed, open a Bruno collection and begin sending API requests right from your editor.
## Usage
### Dual View Mode
Bruno VS Code extension offers two distinct viewing modes to suit different workflows:
1. **Using the Explorer** - File mode with syntax highlighting
2. **Using the Extension** - GUI mode with visual interface
### Using the Explorer
If you already have loaded Bruno collection into the editor the File mode will be default view to switch GUI mode click on **Bruno** icon.
### Using the Extension
By clicking the extension in VS Code, you will have a similar experience to the Bruno Desktop App.
To switch between view modes, click the arrow icons in the top right of your editor.
## Working with Collections
Similar to the desktop application, the extension will first start as a clean slate. Click the "+" icon to either open, create, or import a collection.
# Overview
Source: https://docs.usebruno.com/vs-code-extension/overview
The Bruno VS Code extension brings the full power of Bruno's API testing capabilities directly into your development environment. This extension transforms VS Code into a comprehensive API development and testing platform, eliminating the need to switch between multiple tools during your development workflow.
You can install the extension from the official [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=bruno-api-client.bruno)
and share your feedback to help us improve.
## Core Features
* **Native .bru file support** - Open and edit Bruno request files with rich GUI interface
* **Inline API testing** - Send requests directly from VS Code without switching applications
* **Dual view modes** - Switch seamlessly between GUI and text editor views
* **Collection management** - Organize and manage your API collections within VS Code
* **Syntax highlighting** - Rich syntax highlighting for `.bru` files and API request formats
## Advanced Capabilities
* **Environment variables** - Manage multiple environments and switch between them easily
* **Request scripting** - Write pre-request and post-response scripts for advanced testing
* **Authentication support** - Handle various auth methods including Basic, Bearer, OAuth, and more
* **GraphQL support** - Send and test GraphQL queries with syntax highlighting
* **File uploads** - Handle multipart form data and file uploads in requests
# Send Request
Source: https://docs.usebruno.com/vs-code-extension/send-req
Bruno VS Code extension allows you to send API requests directly from your editor. This feature integrates seamlessly with your development workflow, enabling you to test APIs without leaving your coding environment.
## Create Request
You can create requests by navigating to the Bruno extension:
* Click on **New Request**
* Provide request `name` and `URL`
To create a new collection, you can press the **"+"** button and provide a location to store it. Once you have a collection, you can create folders, configure settings, and organize your requests.
You can also open existing collections from your system by simply opening the collection folder in the editor.
## Create Environment
To create a new environment:
* Click on **No Environment**
* Go to **Configure** (Global or Collection) as per your preference
* Enter name for the environment and create values
* Set up variables for different environments (dev, staging, production)
If you have Collection environments in Bruno, the extension will automatically pick them up from the environment folder.
[Global environment](../variables/global-environment-variables) variables created within Bruno desktop app are not accessible in the extension due to security reasons.
## Console
To test, debug, and troubleshoot script issues, the Bruno console is available in the terminal as the **Bruno Console** tab.
**Features:**
* View console output from your scripts
* Debug pre-request and test scripts
* Monitor variable values and execution flow
## Configurations
To add configurations such as SSL, CA certificates, Cookies, and more, click on the **Gear** icon available on the home screen of the extension.
**Available Settings:**
* **SSL/TLS**: Configure client certificates and CA certificates
* **Cookies**: Manage cookie policies
* **Request Timeout**: Configure default timeout values
* **Response Formatting**: Choose how responses are displayed
## JavaScript Sandbox
To switch between safe and developer mode to use JavaScript libraries and APIs features, navigate to the sandbox icon next to environments.
**Sandbox Modes:**
* **Safe Mode**: Restricted environment with limited API access for security
* **Developer Mode**: Full access to Node.js APIs and external libraries
## Defaults
* **File Mode**: Default in explorer view, UI mode for extension
* **Proxy Configuration**: If you have proxy configuration set up in the editor, Bruno will pick up the values directly
* **Cookie Management**: Manage cookies separately from the cookies setting available in the extension home screen
## Future Development
* **gRPC Support**: Currently in development (available in Bruno as beta feature)
* **OAuth2 Authentication**: Not yet supported in the extension
## Feedback
If you encounter any issues or have feature requests, feel free to [share your feedback](https://github.com/usebruno/bruno/issues).