Guide for AI agents
Machine-readable index, a Markdown copy of every page and setup prompts for coding agents such as Lovable, Cursor and Claude Code.
On this page
This page is for coding agents — Lovable, Cursor, Claude Code, Copilot and others — and for people who hand them a setup task. Everything here is also available as plain Markdown, so an agent can read it without a browser.
Machine-readable docs#
| URL | Contents |
|---|---|
https://belvador.dev/llms.txt | Index of every page in English and Vietnamese, with one-line summaries (llmstxt.org layout) |
https://belvador.dev/llms-full.txt | Every page as Markdown in one file, English then Vietnamese |
https://belvador.dev/api/public/docs/<locale>/<slug> | One page as Markdown, for example /api/public/docs/en/sdk/tanstack-start |
https://belvador.dev/api/public/docs/<locale>/index | The index of one language (en or vi) |
https://belvador.dev/api/public/docs/<locale>/full | Every page of one language in one file |
Each docs page also has Copy Markdown and a Markdown link. The Markdown is generated from the same source as the page, including the setup code, so it never differs from what people read. These URLs are public, need no sign-in and return no account data.
Rules for setting up Belvador in an app#
Get the DSN from the user. It is on the project page in Belvador. Never invent one, never reuse a DSN from another project. One app = one project = one DSN, shared by its browser and server parts.
Install the exact package version:
@belvador/<package>@0.6.1. Do not add@sentry/*SDK packages to JavaScript apps — the Belvador packages bring their own audited engine. (@sentry/cliin CI for source maps is fine.)Keep what the app already does. Add to existing
src/server.ts,src/start.tsand router files; do not replace them. Keep existing middleware in order, and add Belvador's middleware last.Never weaken security. Merge
https://ingest.belvador.devinto the existingconnect-src; do not remove or replace a Content-Security-Policy, CSRF protection, authentication or security headers.Remove old Sentry setup (
Sentry.init,@sentry/*) so errors are not reported twice.No secrets. Do not read, print, log or commit secrets. Do not put tokens, passwords or user emails into tags, context or messages. The DSN is not a secret; upload tokens are — they belong in CI secrets.
Do not change business logic. Only add error tracking.
Verify every runtime with the connection check from the setup prompt (see Verify the connection). Report done only when each result is
verified— or the read-back lists every check. If a result isproblemorunconfirmed, show the user the exact[belvador] verify …line or read-back answer. Remove temporary check code afterwards.
Setup prompts#
These are the prompts the Belvador app shows under Paste into your AI agent. Replace the DSN placeholder with the project's DSN — the app fills it in for you.
TanStack Start / Lovable#
@belvador/tanstack-startRuntime: browser+workersreact: ^18.0.0 || ^19.0.0@tanstack/react-router: ^1.168.0@tanstack/react-start: ^1.168.0Cloudflare Workers: nodejs_compatInstallAdd the package to your app.```bashnpm install @belvador/tanstack-start@0.6.1```InitializeInitialize it as early as possible. Personal data and credentials are removed before anything is sent.src/instrument.ts```typescriptimport { init } from "@belvador/tanstack-start/client";// Imported first by src/start.ts and src/router.tsx. Runs only in the browser.if (typeof window !== "undefined") { init({ dsn: "https://<public-key>@ingest.belvador.dev/<project-id>", release: import.meta.env.VITE_APP_VERSION, // your build or commit id environment: import.meta.env.MODE, });}```BrowserImport ./instrument on the first line and report route error pages with defaultOnCatch. It runs only where an error component exists.src/router.tsx```typescriptimport "./instrument"; // first line, before the route treeimport { withBelvadorRouter } from "@belvador/tanstack-start/client";// In getRouter(): wrap the options you already pass to createRouter.// createRouter(withBelvadorRouter({ routeTree, ...yourOptions }))// It reports route errors, calls your own defaultOnCatch after that, and adds// TanStack's error component only if you set no defaultErrorComponent.```Server (Cloudflare Workers)Wrap the server entry: each request gets its own scope, errors are re-thrown and responses stay unchanged. Needs the nodejs_compat flag (Lovable apps have it).src/server.ts```typescriptimport handler from "@tanstack/react-start/server-entry";import { withBelvador } from "@belvador/tanstack-start/server";// Requires Cloudflare Workers with nodejs_compat enabled.// If src/server.ts already exists, wrap its default export instead of `handler`.// vite.config.ts needs tanstackStart: { server: { entry: "server" } } (Lovable apps have it).export default withBelvador( (env: { BELVADOR_DSN?: string; BELVADOR_RELEASE?: string }) => ({ dsn: env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>", release: env.BELVADOR_RELEASE, }), handler,);```MiddlewarePut them last in each list so server-function and server-route errors are seen before an error-page middleware handles them.src/start.ts```typescriptimport "./instrument"; // first line: the browser loads start.ts before the routerimport { createStart } from "@tanstack/react-start";import { belvadorFunctionMiddleware, belvadorRequestMiddleware } from "@belvador/tanstack-start";export const startInstance = createStart(() => ({ // Last in each list, after your own middleware. requestMiddleware: [/* …your middleware, */ belvadorRequestMiddleware], functionMiddleware: [/* …your middleware, */ belvadorFunctionMiddleware],}));```Content Security PolicyIf your app sends a CSP header, allow the ingest address in connect-src.```textconnect-src 'self' https://ingest.belvador.dev;```Verify the browserProves that captured and uncaught browser errors reach this project, then reads the result back from Belvador.```typescriptimport { verifyIntegration } from "@belvador/tanstack-start/client";// Run once after setup or deploy (for example behind a debug flag), never on every page load.// The console shows one expected "Belvador verification (uncaught)" error — that is the test.const result = await verifyIntegration();// result.outcome: "verified" | "problem" | "unconfirmed"; the console prints one summary line.```Verify the serverProves that server code runs inside Belvador's middleware and that its errors reach this project.src/routes/api/verify-belvador.ts```typescriptimport { createFileRoute } from "@tanstack/react-router";import { verifyIntegration } from "@belvador/tanstack-start/server";// Temporary: open /api/verify-belvador once after deploying, then delete this file.// It must run inside belvadorRequestMiddleware (src/start.ts) — that is part of the check.export const Route = createFileRoute("/api/verify-belvador")({ server: { handlers: { GET: async () => Response.json(await verifyIntegration()), }, },});```Read the result backUse the id the app printed. The answer lists which checks reached your node.```bashcurl -s "https://ingest.belvador.dev/api/<project-id>/verification/bv1_<id printed by your app>/?sentry_key=<public-key>"```Before you report doneRun the check for every runtime listed above. Report done only when each result is verified (or the read-back lists every check). If a result is problem or unconfirmed, show the user the exact "[belvador] verify …" line or read-back answer. Never remove existing middleware, error pages or security headers to make a check pass.Monitor background workAsk the user which scheduled jobs, queues or workers to monitor — don't guess. Use withMonitor (Belvador SDK), the Sentry SDK's crons API, or HTTP check-ins. Run each job once and finish only when the Belvador Monitors page shows that run. Browsers never send check-ins.React#
@belvador/reactRuntime: browserreact: ^18.0.0 || ^19.0.0InstallAdd the package to your app.```bashnpm install @belvador/react@0.6.1```InitializeInitialize it as early as possible. Personal data and credentials are removed before anything is sent.src/instrument.ts```typescriptimport { init } from "@belvador/react";init({ dsn: "https://<public-key>@ingest.belvador.dev/<project-id>", release: import.meta.env.VITE_APP_VERSION, // your build or commit id environment: import.meta.env.MODE,});```Import it firstMake it the first import of your entry file. Imports run before the rest of the file, so an error while your app's modules load is reported only if Belvador started before them.src/main.tsx```tsximport "./instrument"; // first line: starts Belvador before App's modules loadimport { createRoot } from "react-dom/client";import { ErrorBoundary } from "@belvador/react";import App from "./App";const root = document.getElementById("root");if (!root) throw new Error("Root element missing");createRoot(root).render( <ErrorBoundary fallback={<p>Something went wrong.</p>}> <App /> </ErrorBoundary>,);```Content Security PolicyIf your app sends a CSP header, allow the ingest address in connect-src.```textconnect-src 'self' https://ingest.belvador.dev;```Verify the browserProves that captured and uncaught browser errors reach this project, then reads the result back from Belvador.```typescriptimport { verifyIntegration } from "@belvador/react";// Run once after setup or deploy (for example behind a debug flag), never on every page load.// The console shows one expected "Belvador verification (uncaught)" error — that is the test.const result = await verifyIntegration();// result.outcome: "verified" | "problem" | "unconfirmed"; the console prints one summary line.```Read the result backUse the id the app printed. The answer lists which checks reached your node.```bashcurl -s "https://ingest.belvador.dev/api/<project-id>/verification/bv1_<id printed by your app>/?sentry_key=<public-key>"```Before you report doneRun the check for every runtime listed above. Report done only when each result is verified (or the read-back lists every check). If a result is problem or unconfirmed, show the user the exact "[belvador] verify …" line or read-back answer. Never remove existing middleware, error pages or security headers to make a check pass.Monitor background workAsk the user which scheduled jobs, queues or workers to monitor — don't guess. Use withMonitor (Belvador SDK), the Sentry SDK's crons API, or HTTP check-ins. Run each job once and finish only when the Belvador Monitors page shows that run. Browsers never send check-ins.For the other runtimes, follow the page itself: Browser, Cloudflare Workers, Node.js, Python.
OpenTelemetry traces and logs#
The app shows one prompt per certified runtime under Project settings → Ingest keys → Set up with an AI agent, with the project's endpoint filled in and a placeholder in place of the key. The Node.js one:
OpenTelemetry traces and logs → Belvador: Node.jstraces: supported (Belvador certification run 2026-10-24)logs: supported (Belvador certification run 2026-10-24), records carry trace_id/span_idTested with: node 22.23.4, @opentelemetry/sdk-node 0.300.0, @opentelemetry/auto-instrumentations-node 0.82.0, pino 10.4.0Guide: https://belvador.dev/en/docs/otel/nodeGoalSend this service's OpenTelemetry traces and logs to Belvador over OTLP/HTTP. Keep the existing error SDK (Belvador or Sentry) exactly as it is.Before you change anythingFind the runtime and version, the module system (CommonJS or ES modules for Node.js), the framework, how the app is started in production and where it is hosted. Look for an existing OpenTelemetry, Sentry tracing or vendor bootstrap. If something already creates spans, add Belvador as an exporter to that setup; if you cannot tell who owns tracing, ask the user. Never read, print or commit secrets — use the key placeholder.RulesOne tracer provider per process; no second SDK or handler wrapper that also creates spans. Do not change authentication, middleware order or business logic. Do not turn on capture of request or response bodies, headers, cookies, query values or GenAI message content. Keep metrics off. The ingest key is a server secret: environment or secret store only, never in browser code or a public env variable. No OpenTelemetry in browsers. Keep the exporter's batching; never await an export per request.Environment variablesSet these where the app runs (secret store for the key). Use the service's real name and the deployment's environment.```bashexport OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.belvador.dev/otlp/<project-id>export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufexport OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20<your-ingest-key>export OTEL_TRACES_EXPORTER=otlpexport OTEL_LOGS_EXPORTER=otlpexport OTEL_METRICS_EXPORTER=noneexport OTEL_SERVICE_NAME=my-serviceexport OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production```Certified recipeUse these steps as written; they are the ones Belvador's certification run executes. Change only file names, the start command and the service name to fit the project, and pin package versions like the rest of the project.```bashnpm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node``````bashexport OTEL_SEMCONV_STABILITY_OPT_IN=httpexport OTEL_NODE_RESOURCE_DETECTORS=env,host,osnode --require @opentelemetry/auto-instrumentations-node/register app.js``````bashexport OTEL_SEMCONV_STABILITY_OPT_IN=httpexport OTEL_NODE_RESOURCE_DETECTORS=env,host,osnode --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import @opentelemetry/auto-instrumentations-node/register app.mjs```VerifyStart the app locally with the variables set and send one request. Confirm one trace with server and client spans in Belvador's Traces page and one log line with the same trace id in Logs. Then ask the user to run "Check the OTLP connection" on the project's Connection tab. Do not send test traffic to production unless the user says so.ReportList the files you changed, the packages added with exact versions, how to turn it off (unset OTEL_EXPORTER_OTLP_ENDPOINT or remove the start flag), the environment and signals you actually saw arrive, and anything not verified.The others have the same rules and use their runtime's recipe: Python, Go, Java, Deno, Collector.
Checks before saying “done”#
| Check | Expected |
|---|---|
| Package installed | @belvador/<package> at 0.6.1 in package.json, no @sentry/* |
| Browser init | Runs once, early; uses the user's DSN; release and environment set |
| Server (TanStack Start) | src/server.ts wraps the real entry with withBelvador; nodejs_compat on |
| Middleware (TanStack Start) | Belvador's two middleware are the last entries of their lists |
| CSP | connect-src includes https://ingest.belvador.dev; nothing else removed |
| Browser check | The connection check from the browser is verified (or its read-back lists delivery and uncaught) |
| Server check | The server check is verified — it ran inside Belvador's middleware — or its read-back lists delivery |
| Cleanup | Temporary check route or code removed; build and type check pass |
Limits to tell the user#
Errors the app catches and handles are not sent unless
captureExceptionis called.Errors in TanStack Start route loaders during SSR are reported once from the server, with the server stack, only when the router is wrapped with
withBelvadorRouter({ routeTree }); without it they arrive from the browser only.Filtering removes common sensitive data by name and pattern; it cannot recognise every secret.
A returned event ID does not prove the event was stored — check the project in Belvador.
Product facts agents get wrong#
Belvador tracks errors, background-work runs (monitors), uptime checks and SLOs, and stores traces and logs sent with OpenTelemetry (OpenTelemetry). The Belvador SDK does not create spans itself; traces come from an OpenTelemetry SDK or Collector. There is no session replay — see Capabilities by runtime.
Monitors are added only for jobs the user names. Ask which scheduled jobs, queues or workers matter; never wrap every function. A monitor is done when its run appears on the Monitors page.
Customers do not install a server: Belvador creates the node in the customer's Hetzner project from the app. No SSH or Docker steps.
The node stores error events, traces and logs; Belvador's own database stores only metadata.
Alerts use each workspace's own Telegram bot, Slack webhook or email sender, or a signed webhook to the workspace's own endpoint.
Belvador has an MCP server at
https://api.belvador.dev/mcp(OAuth or a token) and a local one,npx -y @belvador/mcp@0.4.0. It reads issues, events, traces, logs, monitors and these docs; the person sets it up under Settings → AI agents — see Connect an AI agent over MCP. Without MCP, an agent can call the REST API with a personal or service token. Keys, members, alert destinations and node changes always wait for a person to approve on belvador.dev. Never ask the user to paste a token into chat — read it from theBELVADOR_TOKENenvironment variable.
Posts on this topic
All posts- A prompt to add error tracking to a Lovable app, and how to check itThe prompt to add error tracking to a Lovable app: which files, in which order, what to keep, and how to prove it works. Old order 0 of 12, fixed 12 of 12.
- How to send production errors to an LLM safelyBefore you paste an error into ChatGPT, Claude or a coding agent: strip secrets, fence untrusted text, check retention. A 72-run injection test shows why.
- Observability MCP gives an agent access, not a diagnosisWhat AI agents do with error-tracker MCP tools: 60 runs, three models. All found the issue, fewer the cause, and most resolved a live bug when they could.
Spotted something wrong or missing? Tell us: hello@belvador.dev