Start watching free

Docs

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_…" data-endpoint="https://vigilia-ingest-66g3co4wzq-ew.a.run.app/v1/events" 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="…" data-endpoint="…" strategy="afterInteractive" /> (from next/script) inside <body>, keeping both data- attributes. 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 both data- attributes.
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": …, "data-endpoint": …, defer: true }.

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.

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://vigilia-ingest-66g3co4wzq-ew.a.run.app

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://vigilia-ingest-66g3co4wzq-ew.a.run.app/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

curl -o vigilia-server.mjs https://cdn.getvigilia.com/server.mjs   # next to the file that imports it
import { withVigilia, watchAI } from './vigilia-server.mjs';

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

curl -o vigilia-server.mjs https://cdn.getvigilia.com/server.mjs   # next to the file that imports it
import { withVigilia, watchAI } from './vigilia-server.mjs';

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

curl -o vigilia-server.mjs https://cdn.getvigilia.com/server.mjs   # next to the file that imports it
import { vigiliaErrorHandler, watchAI } from './vigilia-server.mjs';

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://vigilia-ingest-66g3co4wzq-ew.a.run.app/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://vigilia-ingest-66g3co4wzq-ew.a.run.app/otlp
# OTEL_EXPORTER_OTLP_PROTOCOL=http/json
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer <your server key>

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: clients that support OAuth — Lovable, Bolt, Claude and others — open a Vigilia page where you allow them, no token to copy. For the rest, create a token in Settings → API tokens: read lets your AI look; read & write also lets it mark incidents fixed.
  3. Ask: “Use Vigilia to fix my worst incident.”

Lovable

Settings → Connectors → Chat connectors → add a custom MCP server. Server URL https://app.getvigilia.com/mcp, authentication: OAuth — then allow Lovable on the Vigilia page that opens.

Bolt

Connectors → Custom MCP server. Transport HTTP, server URL https://app.getvigilia.com/mcp, authentication: OAuth — then allow Bolt on the Vigilia page that opens.

Cursor

The dashboard's Connect AI page has a one-click “Add to Cursor” button. Or add it to ~/.cursor/mcp.json:

{
  "mcpServers": {
    "vigilia": {
      "url": "https://app.getvigilia.com/mcp",
      "headers": {
        "Authorization": "Bearer vgs_…"
      }
    }
  }
}

Claude Code, from the terminal

claude mcp add --transport http --scope user vigilia https://app.getvigilia.com/mcp --header "Authorization: Bearer vgs_…"

VS Code, Windsurf and Claude Desktop are on the Connect AI page too, with your token filled in.

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).

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

It asks for your token once and keeps it in your system keychain.

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.
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.

Privacy

The script scrubs emails, tokens, card-like numbers and query strings in the browser, and the server scrubs again. It sets no cookies and doesn't track anyone across sites. Data is hosted in the EU. The details are in the privacy policy.

Ready when you are

One app is free. One script tag, about two minutes.