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

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](https://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

1. **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.
2. **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/cli` in CI for [source maps](https://belvador.dev/en/docs/source-maps) is fine.)
3. **Keep what the app already does.** Add to existing `src/server.ts`, `src/start.ts` and router files; do not replace them. Keep existing middleware in order, and add Belvador's middleware **last**.
4. **Never weaken security.** Merge `https://ingest.belvador.dev` into the existing `connect-src`; do not remove or replace a Content-Security-Policy, CSRF protection, authentication or security headers.
5. **Remove old Sentry setup** (`Sentry.init`, `@sentry/*`) so errors are not reported twice.
6. **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.
7. **Do not change business logic.** Only add error tracking.
8. **Verify every runtime** with the connection check from the setup prompt (see [Verify the connection](https://belvador.dev/en/docs/sdk/verify)). 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. 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

````text
@belvador/tanstack-start

Runtime: browser+workers

react: ^18.0.0 || ^19.0.0

@tanstack/react-router: ^1.168.0

@tanstack/react-start: ^1.168.0

Cloudflare Workers: nodejs_compat

Install
Add the package to your app.
```bash
npm install @belvador/tanstack-start@0.6.1
```

Initialize
Initialize it as early as possible. Personal data and credentials are removed before anything is sent.
src/instrument.ts
```typescript
import { 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,
  });
}
```

Browser
Import ./instrument on the first line and report route error pages with defaultOnCatch. It runs only where an error component exists.
src/router.tsx
```typescript
import "./instrument"; // first line, before the route tree
import { 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
```typescript
import 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,
);
```

Middleware
Put them last in each list so server-function and server-route errors are seen before an error-page middleware handles them.
src/start.ts
```typescript
import "./instrument"; // first line: the browser loads start.ts before the router
import { 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 Policy
If your app sends a CSP header, allow the ingest address in connect-src.
```text
connect-src 'self' https://ingest.belvador.dev;
```

Verify the browser
Proves that captured and uncaught browser errors reach this project, then reads the result back from Belvador.
```typescript
import { 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 server
Proves that server code runs inside Belvador's middleware and that its errors reach this project.
src/routes/api/verify-belvador.ts
```typescript
import { 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 back
Use the id the app printed. The answer lists which checks reached your node.
```bash
curl -s "https://ingest.belvador.dev/api/<project-id>/verification/bv1_<id printed by your app>/?sentry_key=<public-key>"
```

Before you report done
Run 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 work
Ask 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

````text
@belvador/react

Runtime: browser

react: ^18.0.0 || ^19.0.0

Install
Add the package to your app.
```bash
npm install @belvador/react@0.6.1
```

Initialize
Initialize it as early as possible. Personal data and credentials are removed before anything is sent.
src/instrument.ts
```typescript
import { 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 first
Make 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
```tsx
import "./instrument"; // first line: starts Belvador before App's modules load
import { 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 Policy
If your app sends a CSP header, allow the ingest address in connect-src.
```text
connect-src 'self' https://ingest.belvador.dev;
```

Verify the browser
Proves that captured and uncaught browser errors reach this project, then reads the result back from Belvador.
```typescript
import { 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 back
Use the id the app printed. The answer lists which checks reached your node.
```bash
curl -s "https://ingest.belvador.dev/api/<project-id>/verification/bv1_<id printed by your app>/?sentry_key=<public-key>"
```

Before you report done
Run 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 work
Ask 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](https://belvador.dev/en/docs/sdk/browser), [Cloudflare Workers](https://belvador.dev/en/docs/sdk/cloudflare), [Node.js](https://belvador.dev/en/docs/sdk/node), [Python](https://belvador.dev/en/docs/sdk/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:

````text
OpenTelemetry traces and logs → Belvador: Node.js

traces: supported (Belvador certification run 2026-10-24)
logs: supported (Belvador certification run 2026-10-24), records carry trace_id/span_id
Tested with: node 22.23.4, @opentelemetry/sdk-node 0.300.0, @opentelemetry/auto-instrumentations-node 0.82.0, pino 10.4.0
Guide: https://belvador.dev/en/docs/otel/node

Goal
Send 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 anything
Find 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.

Rules
One 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 variables
Set these where the app runs (secret store for the key). Use the service's real name and the deployment's environment.
```bash
export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.belvador.dev/otlp/<project-id>
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20<your-ingest-key>
export OTEL_TRACES_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=none
export OTEL_SERVICE_NAME=my-service
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production
```

Certified recipe
Use 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.

```bash
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
```

```bash
export OTEL_SEMCONV_STABILITY_OPT_IN=http
export OTEL_NODE_RESOURCE_DETECTORS=env,host,os
node --require @opentelemetry/auto-instrumentations-node/register app.js
```

```bash
export OTEL_SEMCONV_STABILITY_OPT_IN=http
export OTEL_NODE_RESOURCE_DETECTORS=env,host,os
node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import @opentelemetry/auto-instrumentations-node/register app.mjs
```

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

Report
List 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](https://belvador.dev/en/docs/otel/python), [Go](https://belvador.dev/en/docs/otel/go), [Java](https://belvador.dev/en/docs/otel/java), [Deno](https://belvador.dev/en/docs/otel/deno), [Collector](https://belvador.dev/en/docs/otel/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 `captureException` is 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](https://belvador.dev/en/docs/otel/overview)). 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](https://belvador.dev/en/docs/reference/capabilities).
- 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](https://belvador.dev/en/docs/alerts/webhooks) 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.6.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](https://belvador.dev/en/docs/mcp). Without MCP, an agent can call the [REST API](https://belvador.dev/en/docs/reference/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 the `BELVADOR_TOKEN` environment variable.
