API reference
The InsightRecorder API is organized around REST: predictable, resource-oriented URLs, JSON request and response bodies, and standard HTTP verbs and status codes. Capture bugs, ingest logs in any format, govern the data (PII redaction, immutable audit, residency), and sync to your tracker. Pick a language above — the app-facing samples follow it; log ingestion and admin calls are shown as curl.
That host is your InsightRecorder deployment — the server that receives your data — not the application you are monitoring. Self-hosted, it is your own instance (e.g. https://insight.yourcompany.com); on the hosted service it is your InsightRecorder host. Everywhere below, api.insightrecorder.example.com is only a placeholder for that address.
Authentication
Authenticate with a bearer token (JWT). Obtain one from the login endpoint below, then send it as Authorization: Bearer <token> on every protected request. Auth endpoints are rate-limited per IP, and an account is temporarily locked after repeated failed logins (5 within 15 min → locked 15 min) — while locked, even a correct password returns 429 with Retry-After.
Errors
insightrecorder uses conventional HTTP status codes and returns a JSON body { "error": "…" } on failure.
Idempotency
Resource-creating POSTs (create a bug, send to a tracker) accept an Idempotency-Key header so they are safe to retry. The first response is stored, scoped to your account; a later request with the same key replays it verbatim with Idempotent-Replayed: true and runs the action only once. Reusing a key with a different body returns 409; server errors are not stored, so they can be retried.
Capture SDK (browser)
Report bugs with full context — console, network timeline, and user steps — from any web page with one script tag. Privacy is default-deny: input values are never read, network bodies and headers are never captured, and everything is PII-redacted server-side before storage.
/static/js/capture.js Embed the capture SDKTwo script tags, in this order. redact.js masks PII in the browser before anything is transmitted, under your workspace's live policy — without it the agent falls back to its built-in strict policy, never to sending raw. capture.js keeps in-memory ring buffers of console output, network calls (method/URL/status/timing only — never bodies or headers), and user steps (clicks by selector; input values are never read). Nothing transmits until a report is triggered. With data-auto="error" set, uncaught errors and unhandled rejections auto-report. Your per-workspace token is on Settings → Integrations; it is public by design (like an error-tracker DSN) and only permits submitting captures.
<script src="https://api.insightrecorder.example.com/static/js/redact.js"></script>
<script
src="https://api.insightrecorder.example.com/static/js/capture.js"
data-token="YOUR_CAPTURE_TOKEN"
data-auto="error">
</script>window.insightRecorder Trigger a reportCall report() from your own "Report a bug" button (or console). The buffered console, network, and steps are submitted with the page URL and environment metadata, PII-redacted server-side before storage, and appear in the bug list with the replay timeline. Severity defaults to P2 when omitted.
// from your "Report a bug" button:
const ack = await insightRecorder.report("Checkout breaks after clicking Buy");
// -> { id: "0192…", ref: "BUG-7F3A2C" }
// with an explicit severity:
await insightRecorder.report("Payment API down", "P0");/api/capture/{token} The ingest endpoint (direct)What the SDK posts to — usable directly from test tooling or a custom agent. The opaque token in the URL identifies the workspace; there is no JWT and CORS is open. Unknown token → 404, malformed or unknown fields → 400. Artifacts are PII-redacted before storage.
curl https://api.insightrecorder.example.com/api/capture/$CAPTURE_TOKEN \
-H "Content-Type: application/json" \
-d '{
"title": "Checkout breaks after clicking Buy",
"url": "https://shop.example/checkout",
"steps": ["Opened /checkout", "Clicked button#buy"],
"console": [{"time":"2026-09-07T12:00:00Z","level":"error","source":"console","message":"payment failed"}],
"network": [{"time":"2026-09-07T12:00:01Z","method":"POST","url":"/api/pay","status":"500","size":"","timing":120000000,"is_error":true}],
"meta": {"browser":"Firefox 143","os":"macOS","viewport":"1280x800","build":"","flags":"","user":"","locale":"en","network":"4g","session":""}
}'Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/javascript directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder capture SDK (browser)
Paste this into a coding agent (Claude Code, Cursor, …) running inside your web
application's repository. It is written to be self-contained — the agent needs
no prior knowledge of InsightRecorder.
---
You are working in a web application. Install and wire the InsightRecorder browser
capture SDK so users can report bugs that arrive with real context (console
output, network timing, and the steps that led to the failure) instead of a
screenshot and a sentence.
**Context you need:**
- The SDK is a single vanilla-JS file served by the InsightRecorder deployment at
`<INSIGHTRECORDER_URL>/static/js/capture.js`. There is **no npm package, no build
step, and no framework requirement**.
- It is configured entirely through `data-` attributes on the script tag.
- `data-token` is a **capture token** found in the InsightRecorder UI under
*Settings → Integrations*. It is public by design — like an error-tracker
DSN — and grants nothing beyond submitting a capture. It is **not** an API
key and must not be confused with one.
**Steps:**
1. Add the script tag to the application's root HTML document — the one shared
by every page (`index.html`, the base layout/template, or the framework's
document component, e.g. `app/layout.tsx`, `_document.tsx`,
`application.html.erb`, `base.html`). Add it once, not per page:
```html
<script src="https://<INSIGHTRECORDER_URL>/static/js/redact.js"></script>
<script
src="https://<INSIGHTRECORDER_URL>/static/js/capture.js"
data-token="<CAPTURE_TOKEN>"
data-auto="error"
></script>
```
- **Both tags, in this order.** `redact.js` is what masks PII in the browser
before anything is transmitted. Omitting it does not disable redaction —
the agent falls back to its strict built-in policy — but it does mean the
workspace's configured rules never reach the page.
- `data-auto="error"` makes uncaught errors and unhandled promise
rejections report themselves automatically. Omit the attribute if you want
manual reporting only.
- `data-endpoint` is optional; it defaults to the script's own origin.
2. Put `<INSIGHTRECORDER_URL>` and `<CAPTURE_TOKEN>` in this project's existing
environment/config mechanism rather than hard-coding them, and interpolate
them in the template the way this repo already does for other public
values. If the framework distinguishes public from server-only variables
(`NEXT_PUBLIC_`, `VITE_`, …), use the **public** prefix — this value is
meant to reach the browser.
3. Add a "Report a bug" affordance wherever this application already puts
secondary user actions (a footer link, a help menu, a support widget). Wire
it to:
```js
const ack = await window.insightRecorder.report("short description of the problem");
// ack -> { id, ref } e.g. ref "BUG-7F3A2C" — show it to the user
```
Optionally pass a severity as the second argument: `"P0"`, `"P1"`, `"P2"`
(default), or `"P3"`.
4. If this application has a Content-Security-Policy, add the InsightRecorder origin
to both `script-src` (to load the file) and `connect-src` (the agent POSTs
the report there). Do not add `unsafe-inline` — it is not needed.
**Rules — do not violate these:**
- Do **not** add the script tag more than once per page load.
- Do **not** attempt to capture or forward form values, passwords, tokens, or
request bodies. The agent deliberately never reads input/textarea values and
never captures network bodies or headers — preserve that. Everything
submitted is additionally PII-redacted server-side.
- Do **not** treat the capture token as a secret to be hidden, and equally do
**not** substitute an API key (`crk_…`) for it: they are different
credentials with different powers.
- Do **not** vendor a copy of `capture.js` or `redact.js` into this repository. Load them from
the InsightRecorder deployment so it stays current.
**Verify before you finish:**
1. The application builds and runs.
2. Open a page, run `window.insightRecorder.report("smoke test")` in the browser
console, and confirm it resolves with an object containing `id` and `ref`.
3. Confirm the bug appears in InsightRecorder under `/app/bugs`, and that its
console/network timeline is populated.
4. If you set `data-auto="error"`, trigger a deliberate uncaught error and
confirm a second report arrives.
5. Report what you changed and where the script tag lives.
If anything is ambiguous — which file is the shared document, how public
config is exposed, whether a CSP exists — inspect the repository and follow
what is already there.
Capture SDK — framework adapters
React and Vue intercept component errors before they reach window.onerror, so the agent alone never sees the crash that blanked the page. These adapters close that gap and attach what only the framework knows: the component stack, or the component and hook that failed. Each is one ES module with the framework as a peer dependency — no build step, nothing bundled.
npm install ReactReact error boundaries swallow render errors: componentDidCatch runs and window.onerror never fires, so the agent alone never sees the failure that broke the page. This adapter closes that gap and attaches the component stack — the part of a React crash that locates the bug. The agent script tag is still required; this talks to it, it does not replace it.
npm install @insightrecorder/capture-react
import { CaptureErrorBoundary } from "@insightrecorder/capture-react";
<CaptureErrorBoundary fallback={<SomethingBroke />}>
<App />
</CaptureErrorBoundary>
// or from an existing boundary / event handler:
reportReactError(error, { componentStack }, { severity: "P1" });npm install VueVue routes component errors to app.config.errorHandler instead of window.onerror, so the agent alone never sees a render or lifecycle failure. The plugin closes that gap and attaches the component name and the failing hook. It chains onto an existing errorHandler rather than replacing it, so an app that already reports somewhere else keeps doing so.
npm install @insightrecorder/capture-vue
import { InsightRecorderCapture } from "@insightrecorder/capture-vue";
app.use(InsightRecorderCapture, { router }); // router is optional
// or from an onErrorCaptured hook:
onErrorCaptured((error, instance, hook) => {
reportVueError(error, instance, hook);
return false;
});Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/react directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder React adapter
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
React application's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a React application. Wire it to InsightRecorder so a crash files a
bug report with the console, network and click timeline that led to it.
**Context you need:**
- InsightRecorder's browser agent (`capture.js`) records console output, network
calls and user steps in memory and transmits nothing until a report is
triggered. It is loaded with a `<script>` tag and a public capture token.
- The agent alone is not enough for React: **error boundaries swallow render
errors**, so `componentDidCatch` runs and `window.onerror` never fires. The
`@insightrecorder/capture-react` package bridges that and attaches the component
stack.
- The capture token is public by design, like an error-tracker DSN. It is
**not** an API key and carries no scopes. Find it in the InsightRecorder UI under
*Settings → Integrations*.
**Steps:**
1. Add the agent script to the HTML document that boots the app —
`index.html`, or the framework's document template (`app/root.tsx` for Remix,
`app/layout.tsx` for Next.js):
```html
<script src="https://<insightrecorder-host>/static/js/capture.js"
data-token="<capture token>"></script>
```
Read the host and token from the project's existing environment mechanism
(`import.meta.env`, `process.env`, a config file) rather than hard-coding
them. If either is missing, the tag must be omitted and the app must run
normally — telemetry is never a boot dependency.
2. Install the adapter: `npm install @insightrecorder/capture-react` (or the yarn/pnpm
equivalent this project uses).
3. Wrap the tree at the root, **inside** any provider the fallback needs (theme,
i18n) but outside the routes:
```jsx
import { CaptureErrorBoundary } from "@insightrecorder/capture-react";
<CaptureErrorBoundary fallback={<SomethingBroke />}>
<App />
</CaptureErrorBoundary>
```
If the project already has an error boundary, do **not** add a second one
around the same subtree — call `reportReactError(error, info)` from the
existing boundary's `componentDidCatch` instead.
4. Add boundaries around subtrees that should fail independently — a dashboard
widget, an embedded editor — so one broken panel does not blank the page.
Only do this where the project's layout makes it meaningful.
5. If the app uses hash routing or a memory router, call
`captureNavigation(path)` on route change. With a normal history router,
skip this: the agent already patches `history.pushState`.
**Rules — do not violate these:**
- Do **not** capture props, state, form values, headers, cookies or request
bodies in any report you write. They routinely carry credentials and personal
data, and the SDK deliberately never reads them.
- Do **not** put the capture token in a server-side secret store or an API-key
variable — it belongs in the page, and treating it as a secret will just make
the wiring wrong.
- Do **not** make the app's boot depend on the agent loading. Every adapter
entry point is already a no-op when the agent is absent; keep it that way.
- Do **not** replace the project's existing error reporting (Sentry, etc.)
unless you were asked to. Both can run.
**Verify before you finish:**
1. The project's build and test commands pass.
2. Run the app, throw a deliberate error inside a component's render, and
confirm a bug appears in InsightRecorder at `/app/bugs` with the component stack.
3. Open that bug and confirm the console and network timeline are populated.
4. Confirm the app still boots with the script tag removed.
5. Report what you changed and where each boundary was placed.
If anything is ambiguous — which HTML template boots the app, whether a boundary
already exists — inspect the repository and follow what is already there. Do not
restructure the application to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/vue directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder Vue adapter
Paste this into a coding agent (Claude Code, Cursor, …) running inside your Vue
application's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a Vue application. Wire it to InsightRecorder so a crash files a
bug report with the console, network and click timeline that led to it.
**Context you need:**
- InsightRecorder's browser agent (`capture.js`) records console output, network
calls and user steps in memory and transmits nothing until a report is
triggered. It is loaded with a `<script>` tag and a public capture token.
- The agent alone is not enough for Vue: component errors go to
**`app.config.errorHandler`** and never reach `window.onerror`. The
`@insightrecorder/capture-vue` plugin bridges that and attaches the component name
and the failing lifecycle hook.
- The capture token is public by design, like an error-tracker DSN. It is
**not** an API key and carries no scopes. Find it in the InsightRecorder UI under
*Settings → Integrations*.
**Steps:**
1. Add the agent script to the HTML document that boots the app — `index.html`
for Vite, or Nuxt's `app.head` configuration:
```html
<script src="https://<insightrecorder-host>/static/js/capture.js"
data-token="<capture token>"></script>
```
Read the host and token from the project's existing environment mechanism
(`import.meta.env`, `runtimeConfig`) rather than hard-coding them. If either
is missing, the tag must be omitted and the app must run normally —
telemetry is never a boot dependency.
2. Install the plugin: `npm install @insightrecorder/capture-vue` (or the
yarn/pnpm equivalent this project uses).
3. Install it on the app where the other plugins are registered (`main.ts`, or
a Nuxt plugin file), passing the router if the project has one:
```js
import { InsightRecorderCapture } from "@insightrecorder/capture-vue";
app.use(InsightRecorderCapture, { router });
```
The plugin chains onto an existing `app.config.errorHandler`, so if this
project already sets one, leave it in place — do not merge them by hand.
4. Where a component should fail on its own rather than taking the page with it,
add an `onErrorCaptured` hook that calls `reportVueError(error, instance,
hook)` and returns `false`. Only do this where the project's layout makes it
meaningful.
**Rules — do not violate these:**
- Do **not** capture props, state, refs, form values, headers, cookies or
request bodies in any report you write. They routinely carry credentials and
personal data, and the SDK deliberately never reads them.
- Do **not** put the capture token in a server-side secret store or an API-key
variable — it belongs in the page, and treating it as a secret will just make
the wiring wrong.
- Do **not** make the app's boot depend on the agent loading. Every plugin
entry point is already a no-op when the agent is absent; keep it that way.
- Do **not** replace the project's existing error reporting (Sentry, etc.)
unless you were asked to. Both can run.
**Verify before you finish:**
1. The project's build and test commands pass.
2. Run the app, throw a deliberate error inside a component's `setup` or
render, and confirm a bug appears in InsightRecorder at `/app/bugs` naming that
component.
3. Open that bug and confirm the console and network timeline are populated.
4. Confirm the app still boots with the script tag removed.
5. Report what you changed and where the plugin was installed.
If anything is ambiguous — which entry file registers plugins, whether an error
handler already exists — inspect the repository and follow what is already
there. Do not restructure the application to fit the SDK.
Go SDK (server)
For Go services: ship logs, turn panics into bug reports with the stack and the log trail, and query the API. Zero dependencies, Go 1.22+, and the same privacy contract as the browser agent — request bodies, headers, cookies, and query strings are never captured.
go get Install the Go SDKThe core module has no third-party dependencies. The zerolog adapter is a separate module, so a service that uses log/slog never pulls zerolog into its build. Authenticate with a long-lived API key (crk_...) from Settings -> API keys: user JWTs expire after an hour and are not meant for machines.
go get github.com/InsightRecorder/insight-recorder-go
# optional, only if your service logs with zerolog:
go get github.com/InsightRecorder/insight-recorder-go/zerologinsight.New Ship logs and catch panicsOne wiring covers both: the Shipper batches log records in the background (never blocking your request path), and the Recover middleware turns a panic into a bug report carrying the stack trace, the request's method and path, and the log lines that led up to it. Always Close the shipper so the final batch is flushed.
client, err := insight.New("https://api.insightrecorder.example.com", os.Getenv("INSIGHT_API_KEY"))
if err != nil {
return err
}
// Logs: batched in the background, flushed on shutdown.
shipper := client.NewShipper()
defer shipper.Close()
// Keep logging to stdout AND ship to InsightRecorder.
slog.SetDefault(slog.New(insight.MultiHandler(
slog.NewJSONHandler(os.Stdout, nil),
insight.NewSlogHandler(shipper, slog.LevelInfo),
)))
// Panics become bug reports instead of a crashed process.
handler := client.Recover(
insight.WithShipper(shipper),
insight.WithService("checkout-api"),
)(mux)
http.ListenAndServe(":8080", handler)
// slog.Error("checkout failed", "trace_id", traceID, "order_id", "1234")
// level -> severity · message -> body · trace_id -> correlation · rest -> attributeszerolog adapter Using zerolog insteadWrap the shipper in a zerolog writer and keep stdout in the MultiLevelWriter: shipping is best-effort, so the local log stays the source of truth for the running process.
import insightzerolog "github.com/InsightRecorder/insight-recorder-go/zerolog"
shipper := client.NewShipper()
defer shipper.Close()
log.Logger = zerolog.New(zerolog.MultiLevelWriter(
os.Stdout,
insightzerolog.NewWriter(shipper),
)).With().Timestamp().Logger()
log.Error().Str("trace_id", traceID).Str("order_id", "1234").Msg("checkout failed")client.ReportBug Report a bug from codeFor a failure you handle rather than a panic. Pass an idempotency key to make the call safe to retry, and use the typed error helpers to react: a quota rejection tells you to back off, a forbidden error tells you the key lacks a scope.
bug, err := client.ReportBug(ctx, insight.NewBug{
Title: "payment provider rejected a valid card",
Severity: insight.P1,
Steps: []string{"POST /checkout", "provider returned 502"},
}, insight.IdempotencyKey(orderID)) // safe to retry
switch {
case insight.IsQuotaExceeded(err):
time.Sleep(insight.RetryAfter(err)) // daily plan quota hit
case insight.IsForbidden(err):
log.Error().Msg("this API key lacks the bug:write scope")
case err != nil:
return err
default:
log.Info().Str("ref", bug.Ref).Msg("filed")
}client.ListBugs Query bugs, logs and incidentsThe same client reads back what was captured — useful for CI gates, internal dashboards, and on-call tooling.
bugs, page, err := client.ListBugs(ctx, insight.BugFilter{
Severity: insight.P0, Limit: 20,
})
records, _, err := client.ListLogs(ctx, insight.LogFilter{TraceID: traceID})
analysis, err := client.AnalyzeLogs(ctx) // AI incident over recent logs
if insight.IsUnavailable(err) {
// no AI provider configured for this workspace
}Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/golang directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder Go SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your Go
service's repository. It is written to be self-contained — the agent needs no
prior knowledge of InsightRecorder.
---
You are working in a Go service. Install and wire the InsightRecorder SDK
(`github.com/InsightRecorder/insight-recorder-go`) so this service ships its logs to
InsightRecorder and reports panics as bug reports.
**Context you need:**
- InsightRecorder collects application logs and bug reports. This SDK is the
supported way for a Go service to talk to it.
- The SDK requires **Go 1.22+** and has **zero third-party dependencies** in
its core module.
- Authentication uses a long-lived **API key** that looks like `crk_…`, created
in the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope
(add `bug:write` to report bugs). **Do not** use a login JWT: those expire
after an hour and are not meant for machines.
Add the matching **read** scopes for anything the SDK reads back — `bug:read` for `ListBugs/GetBug`, `logs:read` for `ListLogs`. A write-only key is the common mistake: shipping works, the first read returns 403.
- **If the application is instrumented with OpenTelemetry, wire the trace
correlation** — it is one call, and without it the log line and the trace of
the request that wrote it never join, which is the whole point of shipping
logs here:
```go
import (
insight "github.com/InsightRecorder/insight-recorder-go"
insightotel "github.com/InsightRecorder/insight-recorder-go/otel"
)
h := insight.NewSlogHandler(sh, slog.LevelInfo).
WithTraceExtractor(insightotel.TraceIDs)
slog.SetDefault(slog.New(insight.MultiHandler(stdoutHandler, h)))
```
From then on `slog.InfoContext(ctx, …)` correlates by itself. Plain
`slog.Info` (no context) cannot — there is no span to read, so prefer the
`…Context` variants in request paths. With zerolog use
`insightzerolog.Ctx(ctx, log.Logger, insightotel.TraceIDs)`; a zerolog writer
sees bytes, never a context, so the correlation has to come from the logger.
- The base URL is the InsightRecorder deployment's origin, e.g.
`https://insightrecorder.example.com`.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. Add the dependency:
```sh
go get github.com/InsightRecorder/insight-recorder-go
```
If — and only if — this service already logs with `github.com/rs/zerolog`,
also run `go get github.com/InsightRecorder/insight-recorder-go/zerolog`. Otherwise
skip it: the adapter is a separate module precisely so services that use
`log/slog` never pull zerolog into their build.
2. Read configuration from the environment, never hard-code it. Add to the
service's existing config loading (and to `.env.example` / deployment
manifests, whichever this repo uses):
- `INSIGHT_BASE_URL` — the InsightRecorder origin
- `INSIGHT_API_KEY` — the `crk_…` key
If either is empty, the service must start normally with shipping disabled.
Telemetry is never allowed to be a startup dependency.
3. In the service's composition root (wherever the logger and HTTP server are
built — do not create a new "init" package if one already exists), wire:
```go
client, err := insight.New(os.Getenv("INSIGHT_BASE_URL"), os.Getenv("INSIGHT_API_KEY"))
if err != nil {
return err
}
shipper := client.NewShipper()
defer shipper.Close() // MUST run on shutdown or the last batch is lost
// Keep the existing local logging AND ship to InsightRecorder.
slog.SetDefault(slog.New(insight.MultiHandler(
slog.NewJSONHandler(os.Stdout, nil),
insight.NewSlogHandler(shipper, slog.LevelInfo),
)))
// Panics become bug reports carrying the stack, the request's method and
// path, and the log lines that led up to them.
handler := client.Recover(
insight.WithShipper(shipper),
insight.WithService("<this service's name>"),
)(existingHandler)
```
Match the existing style: if the service uses zerolog, use
`insightzerolog.NewWriter(shipper)` inside its `zerolog.MultiLevelWriter`
instead of the slog block. If it uses a router (chi, gin, echo), apply
`client.Recover(...)` as middleware in that router's idiom rather than
wrapping the handler manually.
4. Ensure `shipper.Close()` actually runs on the service's existing graceful
shutdown path (next to `srv.Shutdown`), not only via `defer` in a function
that may not return.
**Rules — do not violate these:**
- Do **not** write a custom `io.Writer` that POSTs one HTTP request per log
line. The SDK already batches, retries with backoff, respects the server's
quota (429 + `Retry-After`), and flushes on close.
- Do **not** make logging block on the network: `shipper.Send` is
non-blocking by design and drops when its queue is full. Do not wrap it in
anything that waits.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in bug
reports. The SDK deliberately records only the method and path because those
carry credentials and personal data.
**Verify before you finish:**
1. `go build ./...` and the repo's existing test command both pass.
2. Start the service with `INSIGHT_BASE_URL`/`INSIGHT_API_KEY` set, exercise an
endpoint, then confirm the log lines appear in InsightRecorder under `/app/logs`
(filter by the service name or a `trace_id` you logged).
3. Confirm the service still starts normally with both variables **unset**.
4. Report what you changed, and state explicitly whether `shipper.Close()` is
wired into the shutdown path.
If anything is ambiguous — which logger the service uses, where the composition
root is, how shutdown is handled — inspect the repository and follow what is
already there. Do not restructure the service to fit the SDK.
Installing rather than integrating? Each SDK has its own page with the full install-and-wire sequence: /docs/sdks.
More server SDKs
The same contract in every language: batched log shipping that never blocks your request path, error reports carrying the stack and the log trail, and a typed API client. Each ships an agent-install brief — paste it into a coding agent, or point the agent at the raw Markdown link.
npm install TypeScript / JavaScript (server)For Node services. Ships logs in the background and reports unhandled errors; the Express error handler attaches the log lines that led to the failure. Browser apps use the capture agent above instead, not this package.
npm install @insightrecorder/insight-recorder
import { Client, errorHandler } from "@insightrecorder/insight-recorder";
const client = new Client(process.env.INSIGHT_BASE_URL, process.env.INSIGHT_API_KEY);
const shipper = client.shipper();
shipper.send({ level: "error", message: "checkout failed", traceId, attrs: { order_id: "1234" } });
app.use(errorHandler(client, { shipper, service: "checkout-api" })); // register LAST
await shipper.close(); // on shutdown, or the last batch is lostmix deps Elixir / PhoenixHTTP comes from OTP (:httpc), so the SDK adds no HTTP client to your tree. Put the shipper in your supervision tree — it traps exits, so shutdown flushes the last batch.
{:insight_recorder, "~> 0.1"}
client = InsightRecorder.Client.new(
System.fetch_env!("INSIGHT_BASE_URL"),
System.fetch_env!("INSIGHT_API_KEY")
)
children = [{InsightRecorder.Shipper, client: client, name: MyApp.Shipper}]
InsightRecorder.Shipper.send(MyApp.Shipper, %{
level: "error", message: "checkout failed", trace_id: trace_id
})
# Phoenix — in your endpoint (hooks Plug.ErrorHandler, which is what
# actually sees exceptions raised downstream):
use InsightRecorder.ErrorHandler,
client: {MyApp, :insight_client, []},
shipper: MyApp.Shipper,
service: "checkout-api"com.insightrecorder.insight JavaHTTP is java.net.http and JSON is internal, so the SDK cannot collide with the Jackson or HTTP-client versions your application already pins. Bridge your Logback/Log4j2 appender to the shipper.
InsightClient client = InsightClient.builder()
.baseUrl(System.getenv("INSIGHT_BASE_URL"))
.apiKey(System.getenv("INSIGHT_API_KEY"))
.build();
Shipper shipper = client.shipper();
Runtime.getRuntime().addShutdownHook(new Thread(shipper::close));
shipper.send(LogEntry.of("checkout failed")
.level("error")
.traceId(traceId)
.attr("order_id", "1234")
.build());dotnet add package C# / .NETHttpClient and System.Text.Json ship with the framework, so nothing collides with what your application pins. Register the client as a singleton and dispose the shipper from a hosted service's StopAsync.
dotnet add package InsightRecorder.Insight
var client = new InsightClient(new InsightOptions
{
BaseUrl = builder.Configuration["Insight:BaseUrl"],
ApiKey = builder.Configuration["Insight:ApiKey"],
});
await using var shipper = client.CreateShipper();
shipper.Send(new LogEntry("checkout failed")
{
Level = "error",
TraceId = Activity.Current?.TraceId.ToString(),
}.With("order_id", "1234"));composer require PHP / Laravel / SymfonyHTTP is ext-curl and JSON is ext-json, so the SDK cannot collide with the Guzzle version your application pins. PHP has no background threads, so the shipper buffers and flushes at the end of the request — on PHP-FPM after fastcgi_finish_request(), so the user never waits. Under Octane, RoadRunner or a queue worker, flush at the end of each request or job.
composer require insightrecorder/insight-recorder
$client = new Client(getenv('INSIGHT_BASE_URL'), getenv('INSIGHT_API_KEY'));
$shipper = $client->shipper();
// Laravel and Symfony both log through Monolog — add, never replace:
$logger->pushHandler(new InsightHandler($shipper));
// Unhandled exceptions (Laravel bootstrap/app.php, or a Symfony listener):
$reporter = new ErrorReporter($client, shipper: $shipper, service: 'checkout-api');
$reporter->report($e); // never throws, never masks the originalpip install PythonHTTP is urllib.request from the standard library, so the SDK cannot collide with the requests or httpx versions your application pins. Add the logging handler alongside your existing ones — never replacing them — and wrap a WSGI app (Django, Flask) to report unhandled exceptions.
pip install insight-recorder
from insight_recorder import Client, InsightHandler, InsightMiddleware
client = Client(os.environ["INSIGHT_BASE_URL"], os.environ["INSIGHT_API_KEY"])
shipper = client.shipper()
logging.getLogger().addHandler(InsightHandler(shipper))
logging.error("checkout failed", extra={"trace_id": trace_id, "order_id": "1234"})
# Django / Flask / any WSGI app:
app.wsgi_app = InsightMiddleware(app.wsgi_app, client, shipper=shipper, service="checkout-api")
shipper.close() # on shutdown, or the last batch is lostbundle add Ruby / RailsHTTP is net/http, so the SDK cannot collide with the faraday or http versions your application pins. Broadcast your Rails logger so lines keep reaching their usual destination, and insert the Rack middleware high in the stack — it re-raises, so your error pages and rescue_from still run.
gem "insight_recorder"
client = InsightRecorder::Client.new(
ENV.fetch("INSIGHT_BASE_URL"),
ENV.fetch("INSIGHT_API_KEY")
)
shipper = client.shipper
at_exit { shipper.close } # or the last batch is lost
shipper.send(message: "checkout failed", level: "error",
trace_id: trace_id, attrs: { "order_id" => "1234" })
# Rails — keep the usual destination, add InsightRecorder alongside it:
Rails.logger.broadcast_to(
ActiveSupport::Logger.new(InsightRecorder::LogDevice.new(shipper))
)
# config/application.rb
config.middleware.insert_after ActionDispatch::ShowExceptions,
InsightRecorder::Rack, client: client, shipper: shipper, service: "checkout-api"Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/typescript directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder TypeScript/JavaScript SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
Node.js service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a Node.js service (TypeScript or JavaScript). Install and
wire the InsightRecorder SDK so this service ships its logs to InsightRecorder and reports
unhandled errors.
**Context you need:**
- InsightRecorder collects application logs and bug reports. `@insightrecorder/insight-recorder`
is the supported way for a Node **server** to talk to it. (A browser app uses
a different thing — a `<script>` capture agent — so do not install this
package in front-end-only code.)
- Requires **Node 18+**. The package has zero runtime dependencies.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add `bug:read` for anything the SDK reads back (`listBugs/getBug`), and `logs:read` if you query logs. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. `npm install @insightrecorder/insight-recorder` (or the project's package manager —
match what the repo already uses: yarn, pnpm, bun).
2. Read configuration from the environment, never hard-code it. Add
`INSIGHT_BASE_URL` and `INSIGHT_API_KEY` to the project's existing config
loading and to `.env.example` / deployment manifests. If either is empty, the
service must start normally with shipping disabled — telemetry is never a
startup dependency.
3. In the service's composition root (where the logger and HTTP server are
built — do not invent a new bootstrap module if one exists):
```ts
import { Client, errorHandler } from "@insightrecorder/insight-recorder";
const client = new Client(process.env.INSIGHT_BASE_URL!, process.env.INSIGHT_API_KEY!);
const shipper = client.shipper();
```
4. Bridge the service's existing logger to `shipper.send(...)`. Match what the
repo already uses:
- **pino**: add a custom transport/stream, or call `shipper.send` from a
`hooks.logMethod` wrapper.
- **winston**: add a small custom `Transport` whose `log()` calls
`shipper.send`.
- **console only**: wrap the call sites, or leave logging alone and only wire
error reporting (step 5).
Map fields as: `level` → level, the message → `message`, a trace id →
`traceId`, and everything else into `attrs` (string values).
5. If the service uses Express, register the error handler **last**, after all
routes:
```ts
app.use(errorHandler(client, { shipper, service: "<service name>" }));
```
For Fastify/Koa/NestJS, call `client.reportBug(...)` from that framework's
own error hook instead — the payload shape is in the package's README.
6. Flush on shutdown. Add `await shipper.close()` to the service's existing
SIGTERM/SIGINT handling, next to the server close. Without it the final batch
is lost.
**Rules — do not violate these:**
- Do **not** write a custom fetch loop that POSTs one request per log line. The
SDK batches, retries with backoff, respects the server's quota (429 +
`Retry-After`), and caps each request at `batchSize`.
- Do **not** `await shipper.send(...)` or wrap it in anything that waits — it is
deliberately synchronous and non-blocking, and drops when its queue is full.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in error
reports. The SDK records only the method and path, because those routinely
carry credentials and personal data.
**Verify before you finish:**
1. The project's build/typecheck and test commands pass.
2. Start the service with both env vars set, hit an endpoint, then confirm the
log lines appear in InsightRecorder at `/app/logs`.
3. Trigger a deliberate error and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with both variables **unset**.
5. Report what you changed, and state explicitly whether `shipper.close()` runs
on shutdown.
If anything is ambiguous — which logger, where the composition root is, how
shutdown is handled — inspect the repository and follow what is already there.
Do not restructure the service to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/elixir directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder Elixir SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
Elixir or Phoenix application's repository. It is self-contained — the agent
needs no prior knowledge of InsightRecorder.
---
You are working in an Elixir application. Install and wire the InsightRecorder SDK so
it ships its logs to InsightRecorder and reports unhandled exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. `:insight_recorder` is
the supported way for an Elixir service to talk to it.
- Requires **Elixir 1.15+**. It has one runtime dependency (`:jason`); HTTP uses
OTP's `:httpc`, so it adds no HTTP client to the application tree.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add the matching **read** scopes for anything the SDK reads back — `bug:read` for `list_bugs/get_bug`, `logs:read` for `list_logs`. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. Add `{:insight_recorder, "~> 0.1"}` to `deps/0` in `mix.exs` and run
`mix deps.get`.
2. Read configuration from the environment at **runtime**, not compile time —
put it in `config/runtime.exs`, which is where releases read config:
```elixir
config :my_app, :insight,
base_url: System.get_env("INSIGHT_BASE_URL"),
api_key: System.get_env("INSIGHT_API_KEY")
```
If either is missing, the application must boot normally with shipping
disabled — telemetry is never a startup dependency.
3. Build the client and start the shipper **in the application's supervision
tree** (`lib/my_app/application.ex`), so shutdown flushes the last batch:
```elixir
children = [
# … existing children …
{InsightRecorder.Shipper, client: insight_client(), name: MyApp.Shipper}
]
```
Starting it outside a supervisor would skip the flush on shutdown.
4. Ship logs. Prefer an explicit call at the points that matter:
```elixir
InsightRecorder.Shipper.send(MyApp.Shipper, %{
level: "error",
message: "checkout failed",
trace_id: trace_id,
attrs: %{"order_id" => order_id}
})
```
**To ship every `Logger` call rather than scattering `log/2` by hand**, add a
`:logger` handler — OTP has supported custom handlers natively since OTP 21,
so no extra dependency is needed. A handler is a module with `log/2`:
```elixir
defmodule MyApp.InsightLogHandler do
@moduledoc "Ships every Logger event to InsightRecorder, best-effort."
def log(%{level: level, msg: msg, meta: meta}, _config) do
InsightRecorder.log(MyApp.insight_client(), %{
level: Atom.to_string(level),
message: message(msg),
trace_id: meta[:trace_id],
attrs: %{}
})
:ok
end
defp message({:string, text}), do: IO.iodata_to_binary(text)
defp message({:report, report}), do: inspect(report)
defp message({format, args}), do: format |> :io_lib.format(args) |> IO.iodata_to_binary()
end
```
Register it **alongside** the default handler, never instead of it — shipping
is best-effort, so the local log stays the source of truth for the running
process:
```elixir
:logger.add_handler(:insight, MyApp.InsightLogHandler, %{level: :info})
```
The one trap: **never log from inside the handler.** A delivery failure that
logs fires the handler, which logs. `InsightRecorder.log/2` queues and returns
without blocking, and errors surface through the shipper's own callback.
If the application already has a telemetry handler pipeline, wire it there
instead and follow the existing pattern.
5. If this is a Phoenix application, report unhandled exceptions through
`Plug.ErrorHandler` — the mechanism that actually sees exceptions raised
downstream (a plug cannot: its `call/2` returns before they happen). In the
endpoint module:
```elixir
use InsightRecorder.ErrorHandler,
client: {MyApp, :insight_client, []},
shipper: MyApp.Shipper,
service: "<app name>"
```
The client is an MFA tuple because the endpoint compiles before runtime
config is loaded. If the application already defines `handle_errors/2`, call
`InsightRecorder.ErrorReporter.report(conn, error, opts)` from inside it
instead of adding a second handler.
**Rules — do not violate these:**
- Do **not** build a custom `:httpc`/Finch caller that posts one request per log
line. The SDK batches, chunks each request at `:batch_size`, retries with
backoff, and respects the server's quota.
- Do **not** wrap `Shipper.send/2` in a `GenServer.call` or otherwise make it
synchronous — it is a cast on purpose and drops when its queue is full.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports. The plug records only the method and path.
**Verify before you finish:**
1. `mix compile --warnings-as-errors` and the project's test command pass.
2. Boot the app with both env vars set, exercise a request, then confirm the log
lines appear in InsightRecorder at `/app/logs`.
3. Raise a deliberate exception in a controller and confirm a bug appears at
`/app/bugs`.
4. Confirm the app still boots with both variables **unset**.
5. Report what you changed, and state explicitly whether the shipper is under
the supervision tree.
If anything is ambiguous — where the application module is, how config is
loaded, whether Phoenix is present — inspect the repository and follow what is
already there. Do not restructure the application to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/java directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder Java SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
Java service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a Java service. Install and wire the InsightRecorder SDK so it
ships its logs to InsightRecorder and reports unhandled exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. The InsightRecorder Java SDK
(`com.insightrecorder.insight`) is the supported way for a Java service to talk to it.
- Requires **Java 17+**. It has **zero third-party dependencies** — HTTP is
`java.net.http`, JSON is internal — so it cannot conflict with the Jackson or
HTTP-client versions this project already pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add `bug:read` for anything the SDK reads back (`listBugs/getBug`), and `logs:read` if you query logs. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. Add the dependency using whatever build tool this repo uses (Maven `pom.xml`
or Gradle `build.gradle[.kts]`) — match the existing style and dependency
ordering. If the artifact is not yet published to a repository the project
can reach, add the SDK sources under `src/main/java/com/insightrecorder/insight/`
instead and say so in your summary.
2. Read configuration from the environment, never hard-code it: `INSIGHT_BASE_URL`
and `INSIGHT_API_KEY`. Wire them through the project's existing configuration
mechanism (Spring `@ConfigurationProperties`, MicroProfile Config, plain
`System.getenv`, …). If either is empty, the service must start normally with
shipping disabled — telemetry is never a startup dependency.
3. Create the client **once** (a singleton: a Spring `@Bean`, a CDI producer, or
a static holder — follow the project's pattern) and a shipper alongside it:
```java
InsightClient client = InsightClient.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.build();
Shipper shipper = client.shipper();
```
4. Bridge the service's existing logging to the shipper. Match what the repo
uses:
- **Logback**: add a small `AppenderBase<ILoggingEvent>` whose `append()`
builds a `LogEntry` and calls `shipper.send(...)`; register it in
`logback.xml` **alongside** the console appender, never replacing it.
- **Log4j2**: the equivalent custom `Appender`.
- **java.util.logging**: a custom `Handler`.
Map fields as: level name → `.level(...)`, the formatted message → the entry
message, an MDC/trace id → `.traceId(...)`, and remaining MDC entries →
`.attr(key, value)`.
5. Report unhandled exceptions with `ErrorReporter`, which builds the whole
report — stack trace, request, trace id, and the recent log trail:
```java
ErrorReporter reporter = ErrorReporter.builder(client)
.shipper(shipper)
.service("<service name>")
.build();
```
Wire it where this project already handles exceptions — a servlet filter
registered high in the chain, a Spring `@ControllerAdvice`, or a JAX-RS
`ExceptionMapper`:
```java
reporter.report(e, ErrorReporter.Request.of(
req.getMethod(), req.getRequestURI(), req.getHeader("traceparent")));
throw e; // rethrow, so existing error handling still runs
```
The SDK ships no servlet or Spring dependency on purpose — those six lines
are the adapter, and the README has the full filter. For background jobs use
`executor.submit(reporter.wrap(task))`, and add
`reporter.installUncaughtExceptionHandler()` as a JVM-wide catch-all.
6. Close the shipper on shutdown — a Spring `@PreDestroy`, a
`Runtime.getRuntime().addShutdownHook(...)`, or try-with-resources for a
short-lived process. Without it the final batch is lost.
**Rules — do not violate these:**
- Do **not** write an appender that opens an HTTP connection per log event. The
SDK batches on a daemon thread, caps each request at `batchSize`, retries with
backoff, and respects the server's quota.
- Do **not** block on `shipper.send(...)` — it is non-blocking on purpose and
drops when its queue is full.
- Do **not** swallow `InterruptedException` anywhere you touch the SDK; restore
the interrupt flag.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports: they routinely carry credentials and personal data.
**Verify before you finish:**
1. The project's build and test commands pass.
2. Start the service with both env vars set, exercise an endpoint, then confirm
the log lines appear in InsightRecorder at `/app/logs`.
3. Trigger a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with both variables **unset**.
5. Report what you changed, and state explicitly where `shipper.close()` runs.
If anything is ambiguous — which logging framework, where beans are declared,
how shutdown is handled — inspect the repository and follow what is already
there. Do not restructure the service to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/csharp directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder .NET SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
.NET service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a .NET service. Install and wire the InsightRecorder SDK
(`InsightRecorder.Insight`) so it ships its logs to InsightRecorder and reports unhandled
exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. This SDK is the supported
way for a .NET service to talk to it.
- Requires **.NET 8+**. It has **zero third-party dependencies** — `HttpClient`
and `System.Text.Json` ship with the framework — so it cannot conflict with
versions this project already pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add `bug:read` for anything the SDK reads back (`ListBugs/GetBug`), and `logs:read` if you query logs. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. `dotnet add package InsightRecorder.Insight` in the service project (not the test
project).
2. Read configuration through the project's existing `IConfiguration` — bind
`Insight:BaseUrl` and `Insight:ApiKey`, sourced from environment variables
(`Insight__BaseUrl`, `Insight__ApiKey`) or the configured secret store. Never
hard-code them, and never put the key in `appsettings.json`. If either is
empty, the service must start normally with shipping disabled — telemetry is
never a startup dependency.
3. Register the client as a **singleton** in `Program.cs` / `Startup.cs`,
following the project's DI style:
```csharp
builder.Services.AddSingleton(_ => new InsightClient(new InsightOptions
{
BaseUrl = builder.Configuration["Insight:BaseUrl"]!,
ApiKey = builder.Configuration["Insight:ApiKey"]!,
}));
builder.Services.AddSingleton(sp => sp.GetRequiredService<InsightClient>().CreateShipper());
```
4. Bridge logging. If the project uses `Microsoft.Extensions.Logging` (it almost
certainly does), add a small `ILoggerProvider`/`ILogger` implementation whose
`Log` method builds a `LogEntry` and calls `shipper.Send(...)`, and register
it **alongside** the existing providers, never replacing them. Map: log level
→ `Level`, the formatted message → the entry message,
`Activity.Current?.TraceId` → `TraceId`, and scope/state values → `.With(key, value)`.
If the project uses Serilog, write the equivalent `ILogEventSink`.
5. Report unhandled exceptions where this project already handles them — an
exception-handling middleware, an `IExceptionHandler` (ASP.NET Core 8), or an
MVC filter:
```csharp
await client.ReportBugAsync(new Dictionary<string, object?>
{
["title"] = $"{e.GetType().Name}: {e.Message}",
["severity"] = "P0",
});
```
Report and then let the exception continue, so existing handling still runs.
6. Flush on shutdown. Register an `IHostedService` whose `StopAsync` calls
`await shipper.DisposeAsync()`, or resolve and dispose it from
`IHostApplicationLifetime.ApplicationStopping`. Without it the final batch is
lost.
**Rules — do not violate these:**
- Do **not** write a logger provider that awaits an HTTP call per log event. The
SDK batches in the background, caps each request at `BatchSize`, retries with
backoff, and respects the server's quota.
- Do **not** `await` or block on `shipper.Send(...)` — it is synchronous and
non-blocking on purpose, and drops when its queue is full.
- Do **not** create a new `HttpClient` per request; if the project uses
`IHttpClientFactory`, pass the client through `InsightOptions.HttpClient` (the
SDK will not dispose one it did not create).
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports: they routinely carry credentials and personal data.
**Verify before you finish:**
1. `dotnet build` and `dotnet test` pass.
2. Run the service with the configuration set, exercise an endpoint, then
confirm the log lines appear in InsightRecorder at `/app/logs`.
3. Trigger a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with the configuration **absent**.
5. Report what you changed, and state explicitly where the shipper is disposed.
If anything is ambiguous — which logging stack, how DI is wired, how shutdown is
handled — inspect the repository and follow what is already there. Do not
restructure the service to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/php directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder PHP SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your PHP
application's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a PHP application (most likely Laravel or Symfony). Install
and wire the InsightRecorder SDK so it ships its logs to InsightRecorder and reports
unhandled exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. The
`insightrecorder/insight-recorder` package is the supported way for a PHP
application to talk to it.
- Requires **PHP 8.1+** and has **zero runtime dependencies** — HTTP is
`ext-curl`, JSON is `ext-json` — so it cannot conflict with the Guzzle
version this project pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add `bug:read` for anything the SDK reads back (`listBugs/getBug`), and `logs:read` if you query logs. A write-only key is the common mistake: shipping works, the first read returns 403.
- **PHP has no background threads.** The SDK buffers in memory and flushes at
the end of the request, after `fastcgi_finish_request()` on PHP-FPM, so the
user never waits. Do not try to make it asynchronous some other way.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. `composer require insightrecorder/insight-recorder`.
2. Read configuration from the environment, never hard-code it:
`INSIGHT_BASE_URL` and `INSIGHT_API_KEY`. Wire them through whatever this
project already uses — a Laravel `config/` file reading `env()`, Symfony
parameters, or plain `getenv()`. If either is empty, the application must
boot normally with shipping disabled: telemetry is never a boot dependency.
3. Register the client and shipper as **singletons** in the container, where
this project registers its services (a Laravel `ServiceProvider`, Symfony
`services.yaml`):
```php
$client = new Client(config('insight.base_url'), config('insight.api_key'));
$shipper = $client->shipper();
```
One shipper per request; do not construct one per log line.
4. Bridge logging through Monolog — which is how both Laravel and Symfony log —
adding the handler **alongside** the existing ones, never replacing them:
```php
$logger->pushHandler(new InsightHandler($shipper));
```
In Laravel, add it as a channel in `config/logging.php` and include that
channel in the `stack`. In Symfony, register it as a Monolog handler in
`config/packages/monolog.yaml`.
5. Report unhandled exceptions with `ErrorReporter`:
```php
$reporter = new ErrorReporter($client, shipper: $shipper, service: '<service name>');
```
Wire it where this project already handles exceptions — Laravel's
`withExceptions(...)` in `bootstrap/app.php` (or `Handler::report()` on
Laravel 10 and earlier), or a Symfony `kernel.exception` listener. Report and
let the exception continue, so the framework's error page still renders.
6. Flushing is automatic at the end of the request. If this project runs
**Octane, RoadRunner, Swoole, or queue workers**, call `$shipper->flush()`
at the end of each request or job as well — those processes outlive a single
request, so the shutdown hook fires far too late.
**Rules — do not violate these:**
- Do **not** write a Monolog handler that makes an HTTP call per log record.
The SDK batches, caps each request at `batchSize`, retries with backoff, and
respects the server's quota (429 + `Retry-After`).
- Do **not** call `flush()` inside the request path for every line — that
defeats the buffering and puts a network round trip on the user's latency.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports: they routinely carry credentials and personal data.
**Verify before you finish:**
1. The project's test command passes (`vendor/bin/phpunit`, `php artisan test`).
2. With both env vars set, exercise a route and confirm the log lines appear in
InsightRecorder at `/app/logs`.
3. Throw a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the app still boots with both variables **unset**.
5. Report what you changed, and state explicitly where the shipper is flushed.
If anything is ambiguous — which framework version, how logging is configured,
whether the app runs under Octane — inspect the repository and follow what is
already there. Do not restructure the application to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/python directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder Python SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
Python service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a Python service. Install and wire the InsightRecorder SDK so it
ships its logs to InsightRecorder and reports unhandled exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. `insight-recorder` is the
supported way for a Python service to talk to it.
- Requires **Python 3.9+** and has **zero runtime dependencies** — HTTP is
`urllib.request` — so it cannot conflict with the `requests`/`httpx` versions
this project pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add the matching **read** scopes for anything the SDK reads back — `bug:read` for `list_bugs/get_bug`, `logs:read` for `list_logs`. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. Add `insight-recorder` using whatever this project already uses —
`pyproject.toml` dependencies, `requirements.txt`, Poetry, uv, or pipenv.
Match the existing file and pinning style.
2. Read configuration from the environment, never hard-code it:
`INSIGHT_BASE_URL` and `INSIGHT_API_KEY`. Wire them through the project's
existing settings mechanism (Django `settings.py`, Pydantic `BaseSettings`,
plain `os.environ`). If either is empty, the service must start normally with
shipping disabled — telemetry is never a startup dependency.
3. Create the client and shipper **once**, where the application is composed
(`app.py`, `wsgi.py`, a Django `AppConfig.ready()`, a FastAPI lifespan):
```python
from insight_recorder import Client, InsightHandler
client = Client(os.environ["INSIGHT_BASE_URL"], os.environ["INSIGHT_API_KEY"])
shipper = client.shipper()
```
4. Bridge logging by adding the handler **alongside** the existing ones, never
replacing them:
```python
logging.getLogger().addHandler(InsightHandler(shipper))
```
If the project configures logging through `dictConfig` (Django does), add it
there as a handler entry instead of calling `addHandler` imperatively.
Anything passed via `extra=` becomes a searchable attribute; `trace_id` and
`span_id` are lifted into the record's correlation fields.
5. Report unhandled exceptions. For a WSGI app (Django, Flask, Pyramid):
```python
app.wsgi_app = InsightMiddleware(app.wsgi_app, client, shipper=shipper,
service="<service name>")
```
For an ASGI framework (FastAPI, Starlette), call
`client.report_bug({...})` from that framework's exception handler instead —
the payload shape is in the package README.
6. Flush on shutdown: call `shipper.close()` from the project's existing
shutdown path (a FastAPI lifespan's teardown, a signal handler, or `atexit`).
Without it the final batch is lost.
**Rules — do not violate these:**
- Do **not** write a logging handler that makes an HTTP call per record. The SDK
batches on a daemon thread, caps each request at `batch_size`, retries with
backoff, and respects the server's quota (429 + `Retry-After`).
- Do **not** block on `shipper.send(...)` — it is non-blocking on purpose and
drops when its queue is full.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports: they routinely carry credentials and personal data.
**Verify before you finish:**
1. The project's test command passes and the service starts.
2. With both env vars set, exercise an endpoint and confirm the log lines appear
in InsightRecorder at `/app/logs`.
3. Raise a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with both variables **unset**.
5. Report what you changed, and state explicitly where `shipper.close()` runs.
If anything is ambiguous — which web framework, how logging is configured, how
shutdown is handled — inspect the repository and follow what is already there.
Do not restructure the service to fit the SDK.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/ruby directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder Ruby SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your Ruby
or Rails application's repository. It is self-contained — the agent needs no
prior knowledge of InsightRecorder.
---
You are working in a Ruby application (most likely Rails). Install and wire the
InsightRecorder SDK so it ships its logs to InsightRecorder and reports unhandled
exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. The `insight_recorder`
gem is the supported way for a Ruby application to talk to it.
- Requires **Ruby 3.0+** and has **zero runtime dependencies** — HTTP is
`net/http` — so it cannot conflict with the `faraday`/`http` versions this
project pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add the matching **read** scopes for anything the SDK reads back — `bug:read` for `list_bugs/get_bug`, `logs:read` for `list_logs`. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. Add `gem "insight_recorder"` to the `Gemfile` (in the default group — this is
production telemetry, not a development tool) and run `bundle install`.
2. Read configuration from the environment, never hard-code it:
`INSIGHT_BASE_URL` and `INSIGHT_API_KEY`. If this project uses Rails
credentials or a settings gem, follow that instead — match what is there. If
either value is missing, the application must boot normally with shipping
disabled: telemetry is never a boot dependency.
3. Create the client and shipper once, in
`config/initializers/insight.rb` (or the project's equivalent bootstrap):
```ruby
client = InsightRecorder::Client.new(
ENV.fetch("INSIGHT_BASE_URL"),
ENV.fetch("INSIGHT_API_KEY")
)
shipper = client.shipper
Rails.application.config.insight_client = client
Rails.application.config.insight_shipper = shipper
at_exit { shipper.close }
```
4. Ship logs by broadcasting the application logger, so lines keep going to
their usual destination too:
```ruby
Rails.logger.broadcast_to(
ActiveSupport::Logger.new(InsightRecorder::LogDevice.new(shipper))
)
```
On Rails older than 7.1, use `ActiveSupport::Logger.broadcast` instead —
check which the project's Rails version supports.
Where structured fields matter, call `shipper.send(message:, level:,
trace_id:, attrs:)` directly: a `Logger` device only ever receives the
formatted string.
5. Report unhandled exceptions by inserting the Rack middleware high in the
stack, in `config/application.rb`:
```ruby
config.middleware.insert_after ActionDispatch::ShowExceptions,
InsightRecorder::Rack,
client: Rails.application.config.insight_client,
shipper: Rails.application.config.insight_shipper,
service: "<app name>"
```
It re-raises, so `ActionDispatch` error pages and any `rescue_from` still run.
For a non-Rails Rack app, `use InsightRecorder::Rack, client: …` in
`config.ru`.
6. Make sure `shipper.close` runs on shutdown — the `at_exit` above, or the
process manager's hook if this app uses one (Puma's `on_worker_shutdown`).
**Rules — do not violate these:**
- Do **not** write a logger or subscriber that makes an HTTP call per line. The
SDK batches on a background thread, caps each request at `batch_size`, retries
with backoff, and respects the server's quota (429 + `Retry-After`).
- Do **not** block on `shipper.send(...)` — it is non-blocking on purpose and
drops when its queue is full.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports: they routinely carry credentials and personal data.
**Verify before you finish:**
1. `bundle exec rspec` / `rails test` (whichever this project uses) passes.
2. Boot the app with both env vars set, exercise a request, then confirm the log
lines appear in InsightRecorder at `/app/logs`.
3. Raise a deliberate exception in a controller and confirm a bug appears at
`/app/bugs`.
4. Confirm the app still boots with both variables **unset**.
5. Report what you changed, and state explicitly where `shipper.close` runs.
If anything is ambiguous — the Rails version, how logging is configured, where
initializers live — inspect the repository and follow what is already there. Do
not restructure the application to fit the SDK.
/api/logsShip your app's logs
InsightRecorder ingests structured logs over HTTP — no agent, push what you already log. (1) Get a token with logs:write. (2) POST a JSON array or NDJSON of log objects. (3) Recognized keys map automatically — level→severity, message/msg→body, time/ts→timestamp, trace_id→correlation; everything else becomes searchable attributes. PII is redacted server-side before storage; malformed lines are dead-lettered, never dropped. Verify at /app/logs. Pick your language →
curl https://api.insightrecorder.example.com/api/logs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '[{"level":"error","message":"checkout failed","trace_id":"abc123","order_id":"1234"}]'Collector recipes (Docker · Kubernetes · databases)
InsightRecorder ingests logs (push) — it never scrapes. Point a standard shipper (OpenTelemetry Collector or Fluent Bit) at the source and export to InsightRecorder. One agent per host covers containers and the DB's log files.
otelcol.yaml Your app's JSON logs → OpenTelemetry CollectorFor durability (retry/back-pressure) and zero app coupling, write JSON logs to a file/stdout and let the OTel Collector ship them to InsightRecorder's OTLP endpoint (same redaction/tenant/dead-letter pipeline).
# otelcol.yaml
receivers:
filelog:
include: [/var/log/myapp/*.json]
operators: [{ type: json_parser }]
exporters:
otlphttp:
logs_endpoint: https://api.insightrecorder.example.com/api/logs/otlp
headers: { authorization: "Bearer ${env:INSIGHTRECORDER_TOKEN}" }
service:
pipelines:
logs: { receivers: [filelog], exporters: [otlphttp] }fluent-bit.conf Docker containers → Fluent BitTail every container's stdout/stderr (Docker's JSON log files) and POST to InsightRecorder. Renames Fluent Bit's `log` field to `message` so the body maps. (Docker's native gelf/syslog drivers use UDP/TCP, so bridge with a collector instead.)
# fluent-bit.conf
[INPUT]
Name tail
Path /var/lib/docker/containers/*/*-json.log
Parser docker
Tag docker.*
[FILTER]
Name modify
Match *
Rename log message
[OUTPUT]
Name http
Match *
Host api.insightrecorder.example.com
Port 443
TLS On
URI /api/logs
Format json
Header Authorization Bearer ${INSIGHTRECORDER_TOKEN}otelcol-daemonset.yaml Kubernetes pods → OTel Collector (DaemonSet)Run the Collector as a DaemonSet; the filelog receiver tails every pod's logs (the `container` operator parses the CRI/Docker wrapper) and exports over OTLP.
# otelcol.yaml (Collector DaemonSet)
receivers:
filelog:
include: [/var/log/pods/*/*/*.log]
operators: [{ type: container }] # parses CRI/Docker log format
exporters:
otlphttp:
logs_endpoint: https://api.insightrecorder.example.com/api/logs/otlp
headers: { authorization: "Bearer ${env:INSIGHTRECORDER_TOKEN}" }
service:
pipelines:
logs: { receivers: [filelog], exporters: [otlphttp] }/api/metrics/otlp Metrics → OTel CollectorContainer, PostgreSQL and MySQL metrics over OTLP (Protobuf or JSON on HTTP, or gRPC on :4317). Collection is receiver config, not code. Metric labels are PII-redacted server-side before storage, exactly like log bodies. Raw samples are kept for METRICS_RAW_RETENTION_DAYS (default 7) and live on as five-minute rollups for 90; a query reaching past raw retention is answered from rollups and says so.
# otelcol.yaml — one exporter, two pipelines
exporters:
otlphttp/insightrecorder:
logs_endpoint: ${env:INSIGHTRECORDER_URL}/api/logs/otlp
metrics_endpoint: ${env:INSIGHTRECORDER_URL}/api/metrics/otlp
headers: { authorization: "Bearer ${env:INSIGHTRECORDER_TOKEN}" }
receivers:
docker_stats: # per-container CPU / memory / net / block I/O
endpoint: unix:///var/run/docker.sock
postgresql: # backends, cache hit ratio, replication lag
endpoint: db:5432
username: ${env:PG_USER}
password: ${env:PG_PASSWORD}
mysql:
endpoint: db:3306
username: ${env:MYSQL_USER}
password: ${env:MYSQL_PASSWORD}
service:
pipelines:
metrics:
receivers: [docker_stats, postgresql, mysql]
exporters: [otlphttp/insightrecorder]
# Bloat has no receiver — it is a query, not an exposed metric:
# sqlquery receiver + pgstattuple (or an estimate query) on a schedule./api/metrics Query metricsAsk for one metric by name over a window, optionally filtered by label and downsampled. A name is required: without it the query is "every series you have", which at metric cardinality is a table scan nobody meant to request. The response says which resolution answered it — raw samples, or five-minute rollup averages.
curl "https://api.insightrecorder.example.com/api/metrics?\
name=postgresql.backends&\
since=2026-09-08T00:00:00Z&\
step=5m&\
label=database%3Dapp" \
-H "Authorization: Bearer $TOKEN"
# -> { "data": [ { "name": "postgresql.backends", "unit": "1",
# "attributes": {"database":"app"},
# "points": [{"timestamp":"…","value":42}] } ],
# "resolution": "raw", "step": "5m0s" }
# What is being collected at all:
curl https://api.insightrecorder.example.com/api/metrics/names?prefix=postgresql. \
-H "Authorization: Bearer $TOKEN"postgres + otelcol.yaml PostgreSQL → OTel CollectorThe clean path: make Postgres emit structured JSON (PG 15+), then tail it. (For MySQL/Oracle the logs are plain text — add a parser in the collector, or point log_destination at syslog → POST /api/logs/syslog.)
# 1) postgresql.conf — structured JSON logs (PG 15+)
log_destination = 'jsonlog'
logging_collector = on
# 2) otelcol.yaml — tail the JSON log and ship it
receivers:
filelog:
include: [/var/lib/postgresql/data/log/*.json]
operators: [{ type: json_parser }]
exporters:
otlphttp:
logs_endpoint: https://api.insightrecorder.example.com/api/logs/otlp
headers: { authorization: "Bearer ${env:INSIGHTRECORDER_TOKEN}" }
service:
pipelines:
logs: { receivers: [filelog], exporters: [otlphttp] }/api/auth/loginAuthenticate
Exchange credentials for a bearer token (JWT). Send the token as Authorization: Bearer <token> on every protected request.
curl https://api.insightrecorder.example.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"secret"}'/api/bugsCreate a bug
Create a bug report. Requires the bug:write permission. Returns the created Bug with its generated id and ref.
curl https://api.insightrecorder.example.com/api/bugs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Checkout unresponsive","severity":"P1","url":"app.acme.io/checkout"}'/api/bugs/{id}/sendSend to a tracker
Create an issue in GitHub, Linear, or Jira and store its reference on the bug. Requires bug:send. dest is one of github, linear, jira.
curl https://api.insightrecorder.example.com/api/bugs/$ID/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"dest":"github"}'Log ingestion
Ingest logs in any format — normalized to the OpenTelemetry Logs model, correlated by trace_id, PII-redacted server-side before storage, and dead-lettered when unparseable.
/api/logs Ingest JSON / NDJSONSend a JSON array or newline-delimited objects. Recognized keys (level, msg, ts, trace_id) map to the canonical model; the rest become attributes. Malformed lines are dead-lettered, not dropped. Returns {accepted, rejected}.
curl https://api.insightrecorder.example.com/api/logs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '[{"level":"error","msg":"checkout failed","trace_id":"abc123"}]'/api/logs/otlp Ingest OTLP/HTTPOpenTelemetry Protocol over HTTP, in either encoding — binary Protobuf (application/x-protobuf, the SDK/Collector default) or JSON. Point an OTLP log exporter here with no extra config. OTLP/gRPC on :4317 accepts the same payload — the highest-throughput path.
# Protobuf (default) — e.g. from the OpenTelemetry Collector's otlphttp exporter
curl https://api.insightrecorder.example.com/api/logs/otlp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/x-protobuf" \
--data-binary @logs.pb
# or JSON encoding
curl https://api.insightrecorder.example.com/api/logs/otlp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data-binary @logs.json/api/logs/{format} Ingest syslog / CEF / LEEF / GELFText log formats, one record per line (GELF is JSON). {format} is one of syslog, cef, leef, gelf. Each is normalized to the canonical model; unparseable lines are dead-lettered.
# syslog (RFC 5424 or 3164)
curl https://api.insightrecorder.example.com/api/logs/syslog \
-H "Authorization: Bearer $TOKEN" \
--data-binary @app.log
# CEF / LEEF / GELF work the same way — just change the path segment
curl https://api.insightrecorder.example.com/api/logs/gelf \
-H "Authorization: Bearer $TOKEN" \
--data-binary '{"version":"1.1","host":"web-1","short_message":"boom","level":3}'/api/logs Query logsList the tenant's records (newest first), filtered by trace_id, severity (repeat the param for an any-of match), source format, since (a duration like 15m), and q — a query language, not a substring match. Write what you would say: service:payments level:error "connection refused" http.status_code:>=500 NOT k8s.namespace:staging. Unknown names are read as attribute or resource paths, which is how http.status_code works without being enumerated; level:(error,fatal) is an any-of; EXISTS user.id matches records carrying the field at all. OR between values of one field works, OR across different fields is rejected with a message rather than quietly read as AND — a narrower result set than you asked for is the failure nobody notices. Matching is by word and phrase, so a partial token does not match: conn will not find connection. Search an id with trace_id, which is exact.
curl -G "https://api.insightrecorder.example.com/api/logs" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode 'q=service:payments level:error "connection refused"' \
--data-urlencode "since=15m"/api/logs/histogram Log volume over timeCounts per time bucket for the same filters as the query endpoint — the shape of an incident before you read a single line. The bucket width is derived from the window (about sixty bars), and the histogram shares its predicate with the list, so the bars and the rows can never describe different result sets.
curl -G "https://api.insightrecorder.example.com/api/logs/histogram" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "q=level:error" --data-urlencode "since=6h"/api/logs/analyze Analyze logs for incidents (AI)Runs your configured LLM over a window of the tenant's logs (same filters as the query endpoint) and returns the single most significant incident — DDoS, DB pool exhaustion, auth abuse, error spikes — as {incident, severity, confidence, summary, evidence[], recommended_action, analyzed}. Returns 503 when no AI provider is configured. A background scanner uses the same path to persist and Slack-alert incidents (see /app/incidents).
curl -X POST "https://api.insightrecorder.example.com/api/logs/analyze?q=too+many+clients" \
-H "Authorization: Bearer $TOKEN"Traces
The third OTLP signal. Spans arrive through the same collector and the same tenant scoping as logs and metrics, and join them by trace id — which is what turns "a log line" into "the request that wrote it".
/api/traces/otlp Ingest OTLP tracesAccepts OTLP spans as Protobuf (the Collector and SDK default, auto-detected) or JSON. Both encodings converge on the same stored span. Span names, status messages and attributes are PII-redacted before storage — unlike a metric name, a span name is a label rather than an identity, and instrumentation routinely puts the raw path in it. Span links are deliberately not stored. OTLP/gRPC on :4317 accepts the same payload.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.insightrecorder.example.com/api/traces/otlp \
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer\ $TOKEN \
./your-app
# Use the per-signal variable: OTEL_EXPORTER_OTLP_ENDPOINT has the signal path
# appended (/v1/traces), which this API does not serve./api/traces List tracesTraces in the window, newest first, filtered by trace_id, service, q (matches the span name) and errors=true, which keeps only traces containing a failed span — the one filter an investigation actually starts from. Each row carries the root span, duration, span count and whether anything failed.
curl -G "https://api.insightrecorder.example.com/api/traces" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "service=payments" --data-urlencode "errors=true"/api/traces/{id} Fetch one traceEvery span of one trace, with parents, durations, status and events. A span whose parent never arrived is returned as a root rather than dropped: an incomplete trace is the normal state while something is on fire, and refusing to render it would hide exactly the case you are looking at.
curl "https://api.insightrecorder.example.com/api/traces/4bf92f3577b34da6a3ce929d0e0e4736" \
-H "Authorization: Bearer $TOKEN"Redaction policy
Configure the per-tenant, versioned PII redaction policy enforced on every ingested log and bug capture.
/api/redaction-policy Get the live policyReturns the tenant's active (highest-version) redaction policy, or the strict default when none has been saved.
curl https://api.insightrecorder.example.com/api/redaction-policy \
-H "Authorization: Bearer $TOKEN"/api/redaction-policy Update the policySaves a new immutable version. strictness is strict (default-deny every PII kind) or balanced (skips phone). redact_body_keys are always masked; custom_patterns are validated regexes. Applies to logs and bug captures on ingest.
curl -X PUT https://api.insightrecorder.example.com/api/redaction-policy \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"strictness": "strict",
"redact_body_keys": ["password", "token", "ssn"],
"custom_patterns": [{"name": "employee-id", "regex": "EMP-\\d{5}"}]
}'Webhooks (two-way sync)
Point each tracker's webhook at the matching endpoint to sync issue status back onto bugs. Each is authenticated by its own secret (not a JWT) and matches the bug by its sync_ref. Unmatched issues or unmapped states are acknowledged with 200 (no-op); a bad signature returns 401; an unconfigured secret returns 503.
/api/webhooks/github GitHub issuesConfigure a GitHub webhook for the Issues event with the shared secret.
/api/webhooks/linear Linear issuesMaps the issue's workflow-state type.
/api/webhooks/jira Jira issuesJira isn't HMAC-signed, so a shared secret header is required. Maps the status category.
/api/webhooks/opsgenie Opsgenie (on-call resolve)Closes the on-call loop: when an Opsgenie alert opened by InsightRecorder is closed, the matching incident resolves. PagerDuty inbound is deferred — its incident webhook does not carry the Events-API dedup key.