Vigilia

Vigilia watches your live app, explains every problem in plain language and writes the fix prompt. Your AI fixes it; Vigilia confirms with real traffic.

Documentation

Set up Vigilia

One script tag to start. Everything else — your AI, alerts, the API — is optional and takes a minute each.

Install

Add your app in the dashboard and it shows this tag with your key filled in. It goes in the <head> of every page:

<script src="https://cdn.getvigilia.com/vigilia.js" data-key="vgl_pub_…" defer></script>

Where that is, by framework:

FrameworkWhere the tag goes
Plain HTMLPaste the tag inside <head> of every HTML page (usually just index.html), before </head>.
Vite, Lovable, Bolt, most React appsPaste the tag inside <head> of index.html at the project root (Lovable, Bolt and most React/Vite apps).
Next.js (app router)In app/layout.tsx, render <Script src="…" data-key="…" strategy="afterInteractive" /> (from next/script) inside <body>, keeping every data- attribute the tag has. vigilia.js finds its own tag even when injected after load.
Next.js (pages router)In pages/_document.tsx, add the tag inside <Head> (from next/document), keeping every data- attribute it has.
SvelteKitPaste the tag inside <head> of src/app.html, before %sveltekit.head%.
AstroPaste the tag inside <head> of your shared layout (e.g. src/layouts/Layout.astro), with is:inline so Astro leaves it alone.
RemixIn app/root.tsx, add the tag inside <head>, after <Links />.
NuxtIn nuxt.config, add it under app.head.script as { src, "data-key": …, defer: true }, plus "data-endpoint" if the tag has one.

Built with an AI builder? Don't paste it yourself: the dashboard gives you a prompt that tells Lovable, Bolt, Cursor or Claude Code exactly what to add — and what not to touch.

Or install from npm

Writing the code yourself, with a bundler? The same SDK is on npm as @getvigilia/browser. Use it instead of the tag, not both; the dashboard fills in your key.

npm install @getvigilia/browser
import { init } from '@getvigilia/browser';

// Once, as early as the app starts (main.tsx, or the root layout).
init({ key: 'vgl_pub_…', endpoint: 'https://cdn.getvigilia.com/v1/events' });

Bundled into your app, it needs only the connect-src line below.

If your site has a Content-Security-Policy

Only then: add these two addresses to the directives you already have. Keep everything else.

script-src  https://cdn.getvigilia.com
connect-src https://cdn.getvigilia.com

A blocked script is the most common reason a new install shows nothing. The app page's install check reads your live site and tells you if this is what's happening.

Optional: point at the exact line

Publish source maps and Vigilia resolves a crash in your minified bundle to your own file and line (src/pages/Checkout.tsx:42), in the incident and in the fix prompt. It fetches them only while diagnosing. In Vite, build.sourcemap: 'hidden' writes them without pointing the page at them. In Next.js it's productionBrowserSourceMaps: true.

Check it works

Publish, then open your app with ?vigilia_test=1 on the address (the setup page has the link ready). Vigilia sends one clearly-labelled test error and a first incident appears within seconds. Nothing on your page breaks.

Server errors

Errors in your backend (a Supabase edge function, a Next.js API route, a Cloudflare Worker) never reach a visitor's browser, so the script can't see them. @getvigilia/server sends them to Vigilia, where they become incidents like any other: in plain language, with a fix prompt for your builder. It's errors only (no tracing, no logs), has no dependencies, and never throws into your code. Server errors are part of Pro.

1. Two secrets

On your app's page, under Server errors, create a server key. It's shown once. Set it and the endpoint where your backend runs, never in code:

VIGILIA_SERVER_KEY=vgk_…
VIGILIA_ENDPOINT=https://cdn.getvigilia.com/v1/server/events

2. Wrap your handlers

A wrapped handler reports anything it throws, and any 5xx it returns, with the error text from a body like { "error": "…" }. The response itself is never touched.

Supabase Edge Functions

import { withVigilia, watchAI } from 'https://cdn.getvigilia.com/server.mjs';

watchAI(); // also: AI calls that fail (out of credit, key rejected)

Deno.serve(withVigilia(async (req) => {
  // …your function, unchanged
}, { route: 'checkout' })); // the function's name

In Supabase: Project Settings → Edge Functions → Secrets, or with supabase secrets set.

Next.js / Vercel

npm install @getvigilia/server
import { withVigilia, watchAI } from '@getvigilia/server';

watchAI(); // also: AI calls that fail (out of credit, key rejected)

export const POST = withVigilia(async (request: Request) => {
  // …your route, unchanged
});

In Vercel: Project → Settings → Environment Variables, for Production. Locally, in.env.local.

Cloudflare Workers

npm install @getvigilia/server
import { withVigilia, watchAI } from '@getvigilia/server';

watchAI(); // also: AI calls that fail (out of credit, key rejected)

export default withVigilia({
  async fetch(request, env, ctx) {
    // …your worker, unchanged
  },
});

As Worker secrets: run npx wrangler secret put VIGILIA_SERVER_KEY, then the same for VIGILIA_ENDPOINT.

Node / Express

npm install @getvigilia/server
import { vigiliaErrorHandler, watchAI } from '@getvigilia/server';

watchAI(); // also: AI calls that fail (out of credit, key rejected)

// after your routes:
app.use(vigiliaErrorHandler());

In your host’s environment variables (or a.env file your app loads).

Already on OpenTelemetry? (Team)

No package needed: add Vigilia as one more exporter. Your traces and logs keep going wherever they go today; Vigilia keeps only exceptions and ERROR logs, and drops the rest on arrival. OTLP over HTTP, JSON encoding.

# OpenTelemetry Collector: add Vigilia next to your existing exporters.
exporters:
  otlphttp/vigilia:
    endpoint: https://cdn.getvigilia.com/otlp
    encoding: json
    headers:
      authorization: Bearer ${env:VIGILIA_SERVER_KEY}

service:
  pipelines:
    logs:
      exporters: [otlphttp/vigilia]   # keep the ones already here
    traces:
      exporters: [otlphttp/vigilia]

# No Collector? Point the SDK at Vigilia instead (this replaces its current destination):
# OTEL_EXPORTER_OTLP_ENDPOINT=https://cdn.getvigilia.com/otlp
# OTEL_EXPORTER_OTLP_PROTOCOL=http/json
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer <your server key>

Redact at source (optional). Vigilia scrubs every record on arrival: emails, tokens, card-like numbers and bank account numbers (IBANs). Over OpenTelemetry that is the only scrub a record gets, because it leaves your network as it was logged. To redact before it leaves, add the Collector's own redaction processor. It covers log bodies, attributes and span events.

# Optional: redact at source, so these never leave your network.
# Needs the Collector's contrib distribution. A processor acts on its whole
# pipeline: every exporter in it receives the redacted record.
processors:
  redaction/vigilia:
    allow_all_keys: true
    blocked_values:
      - "[A-Z]{2}[0-9]{2}( ?[A-Z0-9]{4}){2,7}( ?[A-Z0-9]{1,3})?"   # IBANs
      - "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"        # email addresses
    summary: silent

service:
  pipelines:
    logs:
      processors: [redaction/vigilia]   # after the ones already here
    traces:
      processors: [redaction/vigilia]

On Lovable or Bolt? The app page has a prompt that has your builder wrap every edge function for you. Add the secrets yourself; never paste the key into a chat.

3. Check it works

Call sendTestError() once, from the same import. It shows up as a test incident with nothing to fix, and the app page says the server is connected. Errors caught in your own try/catch can be sent with captureException(error, { request }).

Calls to AI. The watchAI() line in each snippet watches your backend's calls to OpenAI, Anthropic, Gemini, OpenRouter, Lovable AI and others. When one fails, Vigilia says why in plain words: out of credit (fixed in a billing page, not in code), a rejected key, rate limits, or the provider being overloaded. It also counts calls and tokens per model, charted on the app page, and tells you when a day runs to three times your usual: a loop or an abused endpoint costs money before anything looks broken. Responses are never touched.

When visitors' requests fail because your backend threw, Vigilia links the two: the failed request says “caused on your server”, the server error says who it affected, and the diagnosis uses both.

Connect your AI

Vigilia is an MCP server, so the AI you build with can read your incidents and fix them without copy-paste — then mark them fixed, and Vigilia confirms the fix held with real traffic.

  1. Add Vigilia to your AI with the server address https://app.getvigilia.com/mcp.
  2. Sign in: your AI opens a Vigilia page, and you click Allow.
  3. Ask: “Use Vigilia to fix my worst incident.”

The dashboard's Connect AI page has the exact steps for each tool. The short version:

  • Lovable: Settings → Connectors → Chat connectors → add a custom MCP server, choose OAuth.
  • Bolt: Connectors → Custom MCP server, transport HTTP, choose OAuth.
  • Claude (web, desktop and mobile): Customize → Connectors → Add custom connector, then Connect. In a chat, turn Vigilia on from + → Connectors. On a Team or Enterprise plan, an Owner adds it once under Organization settings → Connectors.
  • Claude Code: the plugin, which brings the server and two commands. Or just the server: claude mcp add --transport http --scope user vigilia https://app.getvigilia.com/mcp. Then /mcp → Authenticate.
  • Cursor: one click from the Connect AI page, then Connect in Cursor Settings → MCP.
  • VS Code, Windsurf and any client that speaks streamable HTTP: add the server address; they sign in the same way.

Advanced

A client that can't sign in with OAuth can send a token instead: create one in Settings → API tokens and send it as Authorization: Bearer vgs_…. The Connect AI page writes the config for your tool under Advanced.

Claude Code asks before each Vigilia call. Vigilia's tools only read and update your incidents in Vigilia, never your files, so you may allow them in ~/.claude/settings.json. Your call.

{
  "permissions": {
    "allow": [
      "mcp__plugin_vigilia_vigilia",
      "mcp__vigilia"
    ]
  }
}

What your AI can do

  • list_open_incidents, get_incident — what's broken, worst first, with the plain explanation, the evidence and the fix prompt.
  • mark_fixed, get_fix_status — close the loop: Vigilia watches real traffic and reports whether the fix held.
  • list_apps, get_app_health, get_install_instructions, check_install — including installing Vigilia in a new project.
  • get_server_install_instructions — wiring up server errors in the project's backend (Pro).

Every tool only reads, except mark_fixed, which marks an incident fixed in Vigilia and starts the fix watch. Nothing touches your code unless your AI is a coding agent working in your project, and then it's your AI making the change, in front of you. Choose Read only on the Vigilia sign-in page and it can look but never mark anything.

Things to ask

  • “What's broken in my app right now?”: the open incidents, worst first.
  • “Explain my worst Vigilia incident and how to fix it.”: the plain explanation, the evidence and the fix prompt.
  • “Use Vigilia to fix my worst incident.”: in a coding agent with your project open, it fixes the cause and, once you've deployed, marks it fixed.
  • “Did my last fix hold?”: whether real traffic confirmed it.
  • “How is my app doing today?”: uptime, open incidents and today's traffic.
  • “Install Vigilia in this project.”: the script tag in the right place, then a check of the live site.

Disconnecting

Remove Vigilia in your AI's connector settings, and disconnect it in Vigilia under Settings → Connected AI, where every AI you connected is listed.

If something doesn't work

  • The connection or sign-in fails. Check that the address is exactly https://app.getvigilia.com/mcp, and that you're signed in to the Vigilia account you want on the page that opens. Removing the connector and adding it again starts a clean sign-in.
  • “You already have 10 Vigilia tokens”. An account holds 10 connections and API tokens together. Disconnect what you no longer use in Settings → Connected AI (or revoke a token under API tokens), then connect again.
  • “Vigilia is connected read-only”. It was allowed with Read only. Disconnect Vigilia in your AI's connector settings, connect again, and keep Read & write to let it mark incidents fixed.
  • No apps or no incidents. Nothing may be broken, or Vigilia may not be installed yet: ask “check my Vigilia install”, or open the app's page in the dashboard.
  • “Too many requests”. Free accounts have 2,000 API and MCP requests a day, reset at midnight UTC. An agent fixing a bug uses a few dozen.

Still stuck? Email support@getvigilia.com with what you asked and what you saw.

Claude Code plugin

The plugin bundles the MCP server with two commands: /vigilia:fix (fix your worst incident, add a regression test where the repo has tests, then mark it fixed) and /vigilia:install (add Vigilia to a project, CSP included). In Claude Code:

/plugin marketplace add https://getvigilia.com/claude/marketplace.json
/plugin install vigilia@vigilia

Then sign in once: type /mcp, choose plugin:vigilia:vigilia and Authenticate (if it says disabled, choose Enable first), then click Allow on the Vigilia page that opens.

The plugin brings the Vigilia server with it: don't also add it with claude mcp add, or Claude sees every tool twice.

REST API

The same data your AI sees, for your own scripts. Base URL https://app.getvigilia.com/v1; send your token as Authorization: Bearer vgs_…. The full contract is in openapi.json.

curl -H "Authorization: Bearer vgs_…" "https://app.getvigilia.com/v1/incidents?status=open"
  • Incidents — list, read (with evidence), resolve, ignore, reopen.
  • Apps — list, health, run the install check.
  • Deploys — POST /v1/apps/{appId}/deploys with a label (a version or commit) from CI. Vigilia also notices deploys on its own; a marker adds your label and exact timing.

Rate limits are per token and per plan: 60 requests a minute on Free, 300 on Pro, 600 on Team. Free accounts also have 2,000 API and MCP requests a day, all tokens together, reset at midnight UTC. An agent fixing a bug uses a few dozen.

Deploy markers from CI

Vigilia notices new versions on its own. A marker adds the commit, so an incident says “It started 12 minutes after your deploy (3f9c2a1)”. Save a read & write token as the CI secret VIGILIA_TOKEN, then add a step after your deploy. In GitHub Actions:

- name: Tell Vigilia about this deploy
  continue-on-error: true
  env:
    VIGILIA_TOKEN: ${{ secrets.VIGILIA_TOKEN }}
  run: |
    curl -fsS -X POST "https://app.getvigilia.com/v1/apps/YOUR_APP_ID/deploys" \
      -H "Authorization: Bearer $VIGILIA_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{\"label\": \"${GITHUB_SHA::7}\"}"

Anywhere else, run this after deploying:

curl -fsS -X POST "https://app.getvigilia.com/v1/apps/YOUR_APP_ID/deploys" \
  -H "Authorization: Bearer $VIGILIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"label\": \"$(git rev-parse --short HEAD)\"}" || true

Each app's page in the dashboard has these steps under Mark deploys from CI, with its ID filled in. A failed post never fails the deploy.

Webhooks

On paid plans, Vigilia can POST every alert to your own endpoint — for PagerDuty, Zapier, Make, n8n or your own code. Add one in Settings → Alert channels; you get a signing secret (whsec_…) once.

Events: incident.opened, app.down, app.recovered, fix.verified, and test from the “Send test” button. Every body has id, type, createdAt and app; incidents carry the plain explanation, the fix prompt and a link. When one of the app's extra pages goes down rather than the whole app, the incident's endpoint names it, and app.recovered carries it in recovery.endpoint.

Verify the signature

Vigilia-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>, computed over t.rawBody with your secret. Use the raw body, and reject anything more than 5 minutes old:

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyVigilia(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(\`${t}.${rawBody}\`).digest('hex');
  return v1?.length === expected.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Retries

Answer with any 2xx within 8 seconds. On a 5xx, 429 or timeout Vigilia retries after 2 minutes, 10 minutes, 30 minutes and 2 hours; another 4xx means the URL is wrong and it stops. After 10 failures in a row the channel turns itself off, and says so in Settings.

Issue trackers

On the Team plan, Vigilia opens an issue for an incident in GitHub Issues, Linear or Jira Cloud. The issue carries the plain explanation, who's affected, the evidence and stack, what the visitor did, the fix prompt, and a link back. Open one from the incident page with Create issue, or let Vigilia open them for critical (or high and critical) incidents on its own. An incident gets one issue at most.

Connect a tracker in Settings → Issue trackers with a token you create:

  • GitHub: a fine-grained token with access to the one repository, and Issues: Read and write.
  • Linear: a personal API key, and the team's key (ENG in ENG-123).
  • Jira Cloud: an Atlassian API token, the account's email, your …atlassian.net site and the project key. Issues open as a Bug, or a Task where the project has no Bug.

Vigilia checks it can reach the tracker before saving it, and Test checks again without opening an issue. The token is kept server-side and never shown again.

Sentry

Already on Sentry? Keep it. On the Team plan, Vigilia reads each new Sentry issue at error or fatal, explains it in plain language with a fix prompt for your builder, and posts that back on the Sentry issue as a comment, and into your Vigilia alerts. Sentry's grouping is kept: one Sentry issue, one Vigilia incident, diagnosed once.

  1. In Vigilia, Settings → Sentry → Connect Sentry: pick the app and your Sentry organization. You get a webhook URL.
  2. In Sentry, Settings → Developer Settings → Custom Integrations → Create New Integration → Internal Integration. Paste the webhook URL, give it Issue & Event: Read & Write, and tick the issue webhook.
  3. Paste the integration's token and client secret back into Vigilia. Vigilia checks the token before saving, and checks every webhook's signature.

Up to 300 Sentry issues a month per connection. The incident links to the Sentry issue, and the Sentry comment links back.

Status pages and the badge

From any app's page, publish a public status page at getvigilia.com/status/your-app: whether it's up right now, 90 days of uptime and recent downtime. It shows uptime only — never errors or anything about your visitors.

The page comes with a badge for your site or README, which links to it:

[![Monitored by Vigilia](https://getvigilia.com/badge/your-app.svg)](https://getvigilia.com/status/your-app)

What Vigilia watches

SignalWhat becomes an incident
ErrorsCrashes, unhandled promise rejections and console errors from real visitors — grouped, so a thousand of the same error is one incident.
Failed requestsRequests to your API or services that fail. Several hosts failing at once is a visitor's connection, not your app, and is ignored. A "not found" (404) waits until 3 different visitors have hit it: one is usually a page asking for something that isn't there, and saying so.
Slow requestsRequests that take over 5 seconds, once 3 different visitors have hit them.
Rage clicksSomeone clicking the same thing 3 times in a second — a button that seems dead — once 3 different visitors have done it.
UptimeYour app not answering, checked every minute on paid plans and every 5 on Free. On paid plans, also the key pages you add (checkout, sign-in, an API), each with text it must show, so a page that loads but is broken counts too.
Server errorsErrors your backend throws or answers with (5xx), from @getvigilia/server. Pro.
SlowdownsYour app suddenly answering several times slower than it usually does, judged against its own usual speed.
Health checksSecret keys visible in your public code (weekly), your security certificate (daily) and your domain's registration running out (weekly).
Page speedLoading speed, responsiveness and visual stability (LCP, INP, CLS), on the app's Performance tab.

And context on every incident: when a problem started right after a deploy, when a service your app depends on — Supabase, Vercel, OpenAI and others — was having an outage of its own at the time, and, with server errors on, when a request failing in visitors' browsers was caused by an error your backend threw. Those two incidents point at each other.

Errors from your own laptop and from preview links are recorded but never alerted. The ones apps hit most are explained in the error library.

How a fix is confirmed

When you mark an incident fixed, Vigilia keeps watching. An error that stays away only proves something if it was due to show up again, so a fix is confirmed by traffic, not by time alone.

  • The rate before. In the 7 days before you marked it fixed, how many visits did the error hit, out of how many visits to your app?
  • The visits since. At that rate, how many times would the error have shown up by now if the fix had done nothing?
  • Confirmed at 3. Once at least 3 were due and none came, and 24 hours have passed, you get “Your fix worked”. With 3 due, a fix that did nothing would stay quiet about 1 time in 20.

An error that hit 1 visit in 10 needs about 30 visits to confirm; one that hit every visit needs 3. Visits from your own machine or a preview link don't count.

Not enough traffic

If too few people use the app, Vigilia says “Not enough traffic to confirm this fix yet” and shows how many visits it has seen and about how many more it needs. It keeps watching for 7 days. If the traffic never comes it closes the watch as not enough traffic and tells you once by email. That is not a failed fix. It means Vigilia couldn't tell a fix from a quiet week, and won't claim otherwise.

If it comes back

The incident reopens at once, marked as a regression, whatever the watch had concluded.

Errors from your backend (server errors and OpenTelemetry) can't be confirmed this way yet: Vigilia sees the requests that fail, not the ones that succeed, so it has no traffic to count. Their fixes read “not enough traffic” after 7 days unless the error returns. Over the API and MCP the same verdict is fix.watch, with the numbers in fix.evidence.