tutorial

Uptime Monitoring for Bruno API Client Deployments 2026

Bruno is a fast, git-native API client. Add uptime monitoring, CI heartbeats, and alerts to the services your Bruno collections test — free, in under 30 minutes.

Uptime Monitoring for Bruno API Client Deployments 2026

Bruno is the open-source, offline-first API client that stores collections as plain files in your git repo. No account required, no cloud sync, no vendor lock-in. Teams adopt Bruno specifically because it treats API collections like code.

But Bruno's job is to let you interact with APIs — not to watch them. This guide shows you how to add continuous uptime monitoring to every service in your Bruno collections.


Why Bruno collections aren't enough for production monitoring

Bruno collections live in your repo and run when developers trigger them. They're perfect for development, debugging, and PR validation. They're not designed for continuous monitoring.

The gap:

Bruno request → success (service is up at 10:03 AM)
... 
Service goes down at 10:47 AM
...
Next Bruno request → failure (developer hits this at 2:15 PM)
3.5 hours of undetected downtime

External uptime monitoring closes that gap. Vigilmon probes your endpoints every 5 minutes from multiple regions — continuously, automatically, whether any developer has Bruno open or not.


Step 1: Add a health endpoint to each service

For each service in your Bruno collection, add a /health endpoint that reflects real dependency status.

Rust / Axum:

// src/routes/health.rs
use axum::{http::StatusCode, Json};
use serde::Serialize;
use sqlx::PgPool;
use std::collections::HashMap;

#[derive(Serialize)]
pub struct HealthResponse {
    status: String,
    checks: HashMap<String, CheckResult>,
}

#[derive(Serialize)]
pub struct CheckResult {
    status: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    detail: Option<String>,
}

pub async fn health_check(
    axum::extract::State(pool): axum::extract::State<PgPool>,
) -> (StatusCode, Json<HealthResponse>) {
    let mut checks = HashMap::new();

    match sqlx::query("SELECT 1").fetch_one(&pool).await {
        Ok(_) => {
            checks.insert("database".to_string(), CheckResult {
                status: "ok".to_string(),
                detail: None,
            });
        }
        Err(e) => {
            checks.insert("database".to_string(), CheckResult {
                status: "error".to_string(),
                detail: Some(e.to_string()),
            });
        }
    }

    let all_ok = checks.values().all(|c| c.status == "ok");
    let status_code = if all_ok { StatusCode::OK } else { StatusCode::SERVICE_UNAVAILABLE };

    (status_code, Json(HealthResponse {
        status: if all_ok { "ok".to_string() } else { "degraded".to_string() },
        checks,
    }))
}

Elixir / Phoenix:

# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
  use MyAppWeb, :controller

  def check(conn, _params) do
    checks = %{
      database: check_database()
    }

    all_ok = Enum.all?(checks, fn {_, v} -> v.status == "ok" end)
    status = if all_ok, do: 200, else: 503

    conn
    |> put_status(status)
    |> json(%{
      status: if(all_ok, do: "ok", else: "degraded"),
      checks: checks
    })
  end

  defp check_database do
    case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
      {:ok, _} -> %{status: "ok"}
      {:error, e} -> %{status: "error", detail: Exception.message(e)}
    end
  end
end

Add the health request to your Bruno collection — create a GET /health request in a _monitoring folder at the root of your collection. This way the endpoint is documented alongside all other requests.

my-api/
├── .bruno/
│   └── bruno.json
├── _monitoring/
│   └── health-check.bru
├── auth/
│   ├── login.bru
│   └── logout.bru
└── users/
    ├── get-users.bru
    └── create-user.bru
# _monitoring/health-check.bru
meta {
  name: Health Check
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/health
  body: none
  auth: none
}

tests {
  test("Service is healthy", function() {
    expect(res.status).to.equal(200);
    expect(res.body.status).to.equal("ok");
  });
}

Step 2: Set up external HTTP monitoring with Vigilmon

With health endpoints in place, point Vigilmon at them:

  1. Sign up at vigilmon.online — free tier, no credit card
  2. Click New Monitor → HTTP
  3. Enter https://yourservice.com/health
  4. Set the check interval (5 minutes on free tier)
  5. Save

Create monitors that mirror your Bruno environments. Bruno stores environments as files in environments/:

my-api/
└── environments/
    ├── production.bru
    ├── staging.bru
    └── local.bru

For each named environment, create a Vigilmon monitor:

| Bruno Environment | Monitor URL | |---|---| | production.bru | https://api.yourdomain.com/health | | staging.bru | https://staging.api.yourdomain.com/health |


Step 3: Heartbeat monitoring for Bruno / Bru CLI pipelines

Bruno's CLI (@usebruno/cli) lets you run collections in CI. Add heartbeat monitoring so you know if the pipeline stops running.

In Vigilmon:

  1. New Monitor → Heartbeat
  2. Set expected interval (e.g. 25 hours for daily CI)
  3. Copy the ping URL

GitHub Actions with Bruno CLI:

# .github/workflows/bruno-tests.yml
name: Bruno API Tests

on:
  schedule:
    - cron: '0 6 * * *'  # Daily at 6 AM UTC
  push:
    branches: [main]

jobs:
  api-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Bruno CLI
        run: npm install -g @usebruno/cli

      - name: Run Bruno collection
        run: |
          bru run \
            --env production \
            --output results.json \
            collections/my-api

      - name: Ping heartbeat on success
        if: success()
        run: curl -fsS "${{ secrets.BRUNO_HEARTBEAT_URL }}"

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: bruno-test-results
          path: results.json

Add BRUNO_HEARTBEAT_URL to your GitHub repository secrets.

Pre-commit hook for local validation:

Since Bruno stores collections in git, you can run a subset of tests as a pre-commit hook:

#!/bin/sh
# .git/hooks/pre-commit

echo "Running Bruno smoke tests..."
bru run --env local collections/my-api/_monitoring

if [ $? -ne 0 ]; then
  echo "Health checks failed — is your local service running?"
  exit 1
fi

This is local validation only — the CI heartbeat is what tells you about production.


Step 4: Monitor authenticated endpoints

Bruno handles authentication through its environment files. For monitoring authenticated endpoints, Vigilmon supports custom headers:

  1. In Vigilmon: New Monitor → HTTP → Advanced
  2. Add headers:

| Header | Value | |---|---| | Authorization | Bearer your-monitoring-token | | X-Monitor-Key | your-secret-key |

Create a dedicated monitoring API token with read-only permissions — don't use production user credentials in your monitor config.

// Express middleware example
router.get('/health/authenticated', authenticateMonitor, async (req, res) => {
  // Deep health check available only to monitoring systems
  const checks = await deepHealthCheck();
  res.json(checks);
});

function authenticateMonitor(req, res, next) {
  const key = req.headers['x-monitor-key'];
  if (key !== process.env.MONITOR_API_KEY) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

Step 5: Alert configuration

In Vigilmon Notifications → New Channel, set up alerts that go to wherever your team already communicates:

Slack:

POST to: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
Alert message example:
🔴 DOWN: api.yourdomain.com/health
Status: 503 Service Unavailable  
Started: 3 min ago | Regions: US-East, EU-West

Discord:

  1. Server Settings → Integrations → Webhooks
  2. Create webhook for #alerts channel
  3. Paste URL in Vigilmon

Email escalation:

Use Vigilmon's multi-step alerting: first alert goes to Slack, if not acknowledged in 15 minutes it emails the on-call engineer.


Step 6: Add a status page

A public status page tells users what's happening without requiring you to post in every forum and social channel:

  1. Status Pages → New Status Page
  2. Add your production monitors
  3. Link from your API README and docs

Since Bruno collections are in git, add the status page URL to your collection README:

# My API Collection

Bruno collection for the My API service.

- **Production status**: https://status.yourdomain.com
- **API docs**: https://docs.yourdomain.com

Anyone who clones your repo gets the status page link immediately.


What you've built

| What | How | |---|---| | Health endpoints | /health per service with dependency checks, in Bruno collection | | External uptime monitoring | Vigilmon HTTP monitors per Bruno environment | | Bru CLI heartbeats | Ping on CI success; missed ping triggers alert | | Authenticated monitoring | Dedicated monitoring token for sensitive endpoints | | Instant alerts | Slack / Discord / email with escalation | | Public status page | Linked from git repo README |

Bruno's git-native design means your API collections, environments, and documentation live alongside your code. Your monitoring setup should be just as intentional — continuous, external, and automatic.


Get started free at vigilmon.online — your first monitor is running in under a minute.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →