tutorial

How to Monitor OpenApiSpex with Vigilmon

Add uptime monitoring, health checks, and alerts to your Phoenix API that uses OpenApiSpex for OpenAPI 3.0 spec generation and request/response validation.

How to Monitor OpenApiSpex with Vigilmon

OpenApiSpex brings OpenAPI 3.0 to Phoenix: define your schemas with a DSL, get automatic Swagger UI, and validate every request and response at compile time. It's the idiomatic way to build documented, type-safe Phoenix APIs.

When your API is contract-driven with OpenApiSpex, external monitoring becomes even more important. A broken health check or spec endpoint means SDK generators, developer portals, and API consumers all fail silently. You need eyes on /api-spec, Swagger UI, and every critical endpoint — not just your homepage.

This tutorial wires production observability into a Phoenix + OpenApiSpex API:

  • A standards-compliant health check endpoint (complete with its own OpenApiSpex schema)
  • HTTP uptime monitoring with Vigilmon
  • Monitoring for the spec endpoint and Swagger UI
  • Heartbeat monitoring for background workers
  • Slack alerts and a status page

Why Monitor OpenApiSpex Applications?

OpenApiSpex-powered APIs have unique failure modes beyond simple uptime:

  • The spec endpoint (/api-spec or /api/openapi) goes down and SDK generators start failing
  • Swagger UI becomes unreachable and developers lose their interactive docs
  • Request validation rejects valid payloads because a schema was published without a corresponding migration
  • Response validation fails silently because the controller returns a shape that doesn't match the declared schema

Vigilmon catches the first two via HTTP monitoring. The last two require health checks that exercise your schema validation layer, which this tutorial covers.


Step 1: Define a Health Check Schema with OpenApiSpex

Because you're already using OpenApiSpex, define your health endpoint response as a proper schema. This documents the contract and lets you validate it in tests.

# lib/my_app_web/schemas/health.ex
defmodule MyAppWeb.Schemas.Health do
  require OpenApiSpex
  alias OpenApiSpex.Schema

  OpenApiSpex.schema(%{
    title: "HealthResponse",
    type: :object,
    properties: %{
      status: %Schema{type: :string, enum: ["ok", "degraded"]},
      checks: %Schema{
        type: :object,
        properties: %{
          database: %Schema{type: :string, enum: ["ok", "error"]},
          cache: %Schema{type: :string, enum: ["ok", "error"]},
          spec: %Schema{type: :string, enum: ["ok", "error"]}
        }
      },
      version: %Schema{type: :string}
    },
    required: [:status, :checks]
  })
end

Step 2: Add the Health Check Controller

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

  alias MyAppWeb.Schemas.Health

  operation :show,
    summary: "Health check",
    description: "Returns the current health status of the API and its dependencies.",
    responses: [
      ok: {"Health response", "application/json", Health},
      service_unavailable: {"Degraded response", "application/json", Health}
    ]

  def show(conn, _params) do
    checks = %{
      database: check_database(),
      cache: check_cache(),
      spec: check_spec()
    }

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

    conn
    |> put_status(status)
    |> json(%{
      status: if(status == :ok, do: "ok", else: "degraded"),
      checks: checks,
      version: Application.spec(:my_app, :vsn) |> to_string()
    })
  end

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

  defp check_cache do
    case Redix.command(:redix, ["PING"]) do
      {:ok, "PONG"} -> "ok"
      _ -> "error"
    end
  end

  defp check_spec do
    # Verify the spec can be generated (compilation-time validation caught earlier,
    # this catches runtime resolution issues)
    case OpenApiSpex.Plug.PutApiSpec.get_spec(MyAppWeb.ApiSpec) do
      {:ok, _} -> "ok"
      _ -> "error"
    end
  rescue
    _ -> "error"
  end
end

Wire it into your router:

# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
    plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec
  end

  # Health check — no authentication middleware
  scope "/", MyAppWeb do
    get "/health", HealthController, :show
  end

  scope "/api", MyAppWeb do
    pipe_through :api
    # ... your regular routes
  end
end

Test it:

mix phx.server
curl http://localhost:4000/health
# {"status":"ok","checks":{"database":"ok","cache":"ok","spec":"ok"},"version":"0.1.0"}

Step 3: Monitor the Spec Endpoint

The /api-spec endpoint is often overlooked in monitoring but critical for API consumers. Add a separate Vigilmon monitor for it:

  1. Sign up at vigilmon.online
  2. Click New Monitor → HTTP
  3. Enter https://yourdomain.com/api-spec (or wherever you serve the spec)
  4. Enable a keyword check: body must contain "openapi" to confirm a valid JSON spec is returned
  5. Set check interval to 1 minute
  6. Save

Add a second monitor for Swagger UI:

  1. Click New Monitor → HTTP
  2. Enter https://yourdomain.com/swaggerui
  3. Enable a keyword check: body must contain swagger-ui (from the HTML)
  4. Save

Step 4: Set Up HTTP Monitoring for the Health Endpoint

Add the main health monitor:

  1. In Vigilmon, click New Monitor → HTTP
  2. Enter https://yourdomain.com/health
  3. Set interval to 1 minute
  4. Optionally add a keyword check for "ok" to catch degraded responses that still return 200

Step 5: Heartbeat Monitoring for Background Workers

If your API has background workers (sending webhooks, syncing data, processing jobs), add heartbeat monitoring for each one.

# lib/my_app/workers/webhook_dispatcher.ex
defmodule MyApp.Workers.WebhookDispatcher do
  use GenServer
  require Logger

  @interval :timer.seconds(30)

  def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)

  @impl true
  def init(state) do
    schedule_tick()
    {:ok, state}
  end

  @impl true
  def handle_info(:tick, state) do
    case dispatch_pending_webhooks() do
      {:ok, count} ->
        Logger.info("Dispatched #{count} webhooks")
        ping_heartbeat()
      {:error, reason} ->
        Logger.error("Webhook dispatch failed: #{inspect(reason)}")
    end

    schedule_tick()
    {:noreply, state}
  end

  defp schedule_tick, do: Process.send_after(self(), :tick, @interval)

  defp ping_heartbeat do
    url = Application.get_env(:my_app, :vigilmon)[:webhook_heartbeat_url]
    if url, do: Req.get(url, receive_timeout: 5_000)
  end

  defp dispatch_pending_webhooks do
    # your dispatch logic
    {:ok, 0}
  end
end

In Vigilmon, create a Heartbeat Monitor:

  1. Click New Monitor → Heartbeat
  2. Set expected interval (e.g. 1 minute for a 30-second worker, with some slack)
  3. Copy the ping URL and set it as VIGILMON_WEBHOOK_HEARTBEAT_URL

Step 6: Alerts via Slack

In Vigilmon, go to Notifications → New Channel → Slack and paste your Slack webhook URL.

Assign the Slack channel to all three monitors (health, spec, Swagger UI). On downtime:

🔴 DOWN: yourdomain.com/api-spec
Status: 503 Service Unavailable
Detected from: EU-West, US-East
2 minutes ago

Having separate monitors means you know exactly which endpoint failed — the whole API, just the spec, or just the docs UI.


Step 7: Status Page and Badge

  1. Go to Status Pages → New Status Page in Vigilmon
  2. Add all three monitors (health, spec, Swagger UI)
  3. Copy the public URL and add it to your API documentation
![API Status](https://vigilmon.online/badge/your-monitor-slug)

Key Metrics for OpenApiSpex APIs

| Monitor | What it catches | |---------|----------------| | /health HTTP check | DB, cache, and spec generation health | | /api-spec HTTP + keyword check | Spec endpoint down or returning invalid JSON | | /swaggerui HTTP + keyword check | Developer docs unreachable | | Webhook heartbeat | Background dispatch worker stalled | | Response time trend | Slow schema validation causing latency |


What You've Built

| What | How | |------|-----| | Typed health schema | OpenApiSpex schema with spec validation check | | Health endpoint | Phoenix controller, bypasses auth middleware | | HTTP uptime monitoring | Vigilmon monitors for health, spec, and Swagger UI | | Keyword checks | Verify spec and UI content, not just HTTP status | | Heartbeat monitoring | Webhook dispatcher GenServer | | Slack alerts | Per-monitor notifications | | Public status page | All monitors in one place |

OpenApiSpex keeps your API contract honest at compile time. Vigilmon keeps the deployed endpoints reachable at runtime.


Next Steps

  • Add a monitor for each versioned spec endpoint if you maintain multiple API versions
  • Use Vigilmon's response time tracking to detect spec generation regressions after schema additions
  • Add a POST monitor that validates an example request payload goes through successfully end-to-end

Get started free at vigilmon.online.

Monitor your app with Vigilmon

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

Start free →