00

Setup

The first hour, written down. Three things make ChangeBox work on your product: the widget on the page, your errors arriving, and a login the Verifier can use. Everything else is a switch.

01Create the app and connect Cursor

5 minutes

An app is one site or product. Apps → Add an app: give it a name and its production URL. Then Settings → Cursor and paste a Cursor API key from an account that can run Cloud Agents on your repositories; ChangeBox stores it encrypted and only ever hands it to Cursor. Under the app's Setup, add the repository agents should work in and confirm the Production environment points at your real origin with the right base branch.

CheckThe app card on Apps shows Agents on once the widget has been seen on the live site and a Cursor key is connected.

02Put Δ on the page

10 minutes

The widget key is public; reporters never need a ChangeBox account. Paste the tag before </body> on every page you want people to report from. Your key is on the app's Setup page.

<script src="https://changebox.ai/widget.js" data-app="cb_live_xxxxxxxxxxxx"></script>

React / Next.js

npm i @changebox/react

// app/layout.tsx (or any client component that is always mounted)
import { ChangeBox } from "@changebox/react";

<ChangeBox
  appKey="cb_live_xxxxxxxxxxxx"
  context={{ release: process.env.NEXT_PUBLIC_GIT_SHA, plan: user?.plan }}
/>

Tell ChangeBox who is reporting

Your app knows who is signed in; ChangeBox does not. Sign a short statement on your server with the app's identity secret (Setup → Identity signing) and hand it to the widget. Reports carry a real person, Who can report is enforced, and the people you mark as approvers can approve from inside your product. Skip this only for fully public sites.

// Server only. npm i @changebox/shared
import { signHostIdentity } from "@changebox/shared";

const identity = await signHostIdentity({
  secret: process.env.CHANGEBOX_IDENTITY_SECRET,
  appKey: "cb_live_xxxxxxxxxxxx",
  id: user.id,
  email: user.email,
  name: user.name,
  permitted: true,                              // may report here
  trustedForAutoBuild: user.isStaff,            // builds may start without a person
  roles: user.isAdmin ? ["approver"] : [],
  ttlSeconds: 3600,
});

// Browser
ChangeBox.identify(identity);   // or <ChangeBox identify={identity} />

Origins and environments

The widget is accepted from the production URL, every environment origin, and the extra origins you list; anything else gets a 403. Reports are matched to an environment by page origin, so a staging report lands in Staging and never starts a production agent. localhost always works.

CheckOpen your site, press Δ, send “Testing the widget” as polish. It appears in the app within a few seconds with the page, browser and release attached, and the app's Settings → Install says Δ is live.

03Send your errors

10–20 minutes

ChangeBox is a defect intake, not a log store. It takes error and fatal only, groups them by what broke, and files one change when a group repeats. Create the ingest key on the app's Signals page; it is shown once and can only write signals.

Already on OpenTelemetry

Point the OTLP/HTTP JSON logs exporter at ChangeBox. Change nothing else; traces and metrics keep going wherever they go today.

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://changebox.ai/api/ingest/otlp/v1/logs
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-changebox-ingest-key=cbi_xxxxxxxxxxxx
OTEL_RESOURCE_ATTRIBUTES=service.name=api,deployment.environment=production

With the OpenTelemetry Collector, add a second logs exporter and keep your existing pipeline:

exporters:
  otlphttp/changebox:
    logs_endpoint: https://changebox.ai/api/ingest/otlp/v1/logs
    encoding: json
    headers:
      x-changebox-ingest-key: ${env:CHANGEBOX_INGEST_KEY}
processors:
  filter/errors:
    logs:
      log_record:
        - 'severity_number < 17'      # keep ERROR (17) and FATAL (21) only
service:
  pipelines:
    logs/changebox:
      receivers: [otlp]
      processors: [filter/errors, batch]
      exporters: [otlphttp/changebox]

Node, Bun, Workers (no OpenTelemetry)

npm i @changebox/sdk

import { createTelemetry } from "@changebox/sdk";
const telemetry = createTelemetry({
  apiUrl: "https://changebox.ai",
  ingestKey: process.env.CHANGEBOX_INGEST_KEY,
  service: "api",
  environment: process.env.NODE_ENV === "production" ? "production" : "staging",
});

// Anywhere you catch something you would page on:
telemetry.capture(error, { url: request.url, attributes: { route: "/v1/orders", requestId } });

// Express / Fastify error handler, Next.js onRequestError, Workers ctx.waitUntil(telemetry.flush()) …

Next.js (App Router)

// instrumentation.ts
import type { Instrumentation } from "next";
import { createTelemetry } from "@changebox/sdk";

const telemetry = createTelemetry({ apiUrl: "https://changebox.ai", ingestKey: process.env.CHANGEBOX_INGEST_KEY!, service: "web", environment: process.env.VERCEL_ENV ?? "production" });

export const onRequestError: Instrumentation.onRequestError = async (error, request, context) => {
  telemetry.capture(error, { url: request.path, attributes: { method: request.method, route: context.routePath } });
  await telemetry.flush();
};

Cloudflare Workers

Workers Logs can export natively: Observability → Logs → Add destination → OTLP, URL https://changebox.ai/api/ingest/otlp/v1/logs, header x-changebox-ingest-key. Add a second header x-changebox-service: your-worker and x-changebox-environment: production because Cloudflare does not let you set resource attributes per destination.

Browser errors

The widget already captures console errors and attaches them to reports. If you want unhandled browser errors filed on their own, send them through the same SDK from your error boundary with service: "web-client"; ChangeBox groups by message and top frames, so minified stacks still group correctly.

CheckThrow one error on purpose in staging. Within a minute the app's Signals page shows the group with 1 seen. Trip it three more times and a change is filed by the Telemetry agent (threshold and window are on the Signals page).

04Give the Verifier a login

15 minutes

When a fix goes live, the Verifier opens the real page in a real browser and checks the acceptance criteria before a person is asked to. For that it needs an account in your product with the least access that still shows the pages people report on. Backend-only changes (an API returns 200, a job runs, a log line stops) are not browser-checkable and go straight to a person; everything with a visible outcome is checked.

1. Create its address

App → Setup → Verification → Create the Verifier's address. You get something like verifier-yourapp-k3f2@changebox.ai. Mail to that address is received by ChangeBox and handed only to the running Verifier, through a token that lives as long as that run.

2. Create the user in your product

Invite that address the way you would a new teammate. Give it a plain user role, seed it with representative data (a project, an order, a workspace) so screens are not empty, and put it in a team or tenant of its own so it never touches a customer's data. Do not make it an admin.

3. Make sure it can sign in by email

Magic link and one-time code both work: your product emails the code or link, ChangeBox receives it, the Verifier finishes the sign-in. If your product is password-only, create a password for the account, store it as a secret named VERIFIER_PASSWORD in the Cursor environment, and write the sign-in steps using $VERIFIER_PASSWORD; the value never passes through ChangeBox. Google-only or SSO-only sign-in is not supported yet; add an email sign-in for this one account.

App → Setup → Environments → Production → Verifier sign-in

URL      https://app.example.com/login
Steps    Enter verifier-yourapp-k3f2@changebox.ai, press "Email me a code",
         enter the code, you are signed in when the sidebar shows "Verifier".

4. Write criteria the Verifier can see

The Verifier checks the Change Spec's acceptance criteria. “The Save button stores the plan and shows a confirmation” is checkable; “the bug is fixed” is not. The intake assistant already asks testers for criteria in this shape; you can edit them on any change before it ships.

CheckPick any live change with a visible outcome and press Ask the Verifier on its page. Within a few minutes the timeline shows Verifier checked the live page with a verdict per criterion. If it says couldn't tell, the reason is usually the sign-in; fix the steps and ask again.

05Tell ChangeBox when you deploy

5 minutes

A change becomes live when its PR is deployed. With the GitHub App installed, merges and deployments flow in on their own. Without it, call the deploy webhook from your pipeline after a production deploy finishes; ChangeBox marks every change whose PR is in that deploy, starts the Verifier, and tells the reporter.

# After the production deploy succeeds (GitHub Actions, Vercel deploy hook, Cloudflare, anything)
curl -X POST https://changebox.ai/api/deployments/$CHANGEBOX_DEPLOY_SECRET \
  -H 'content-type: application/json' \
  -d '{"environment":"production","sha":"'"$GITHUB_SHA"'","url":"https://app.example.com"}'

CheckMerge any agent PR and deploy. The change moves to Ready to check, the reporter sees it in the widget, and the Verifier run starts within the quiet window you set on the app.

06Slack, people, policy

10 minutes

Settings → Slack: connect the workspace and press Create the standard channels; ChangeBox makes and joins #cb-changes, #cb-signals, #cb-releases and #cb-accepted, or point each stream at channels you already have. Settings → Team: invite the people who approve and the testers who report. App → Setup → Who approves builds: start on Manual, move to Trusted once you have watched a dozen PRs.

Then report one real bug. Not a test: a thing a customer would notice. Watch it go Spec → build → PR → review → live → verified. That is the loop; everything after this is tuning it. The quickstart walks that first change step by step.