insightrecorder/docs SDKs → GitHub

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.

Base URL   https://api.insightrecorder.example.com

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.

400 Invalid request (validation failed)
401 Missing or invalid token
403 Authenticated but lacking the required permission
404 Resource does not exist
409 Conflict (e.g. email already registered)
429 Rate limited — retry after the Retry-After header
503 Dependency unavailable or not configured

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.

HTML /static/js/capture.js Embed the capture SDK
scope  public capture token · no JWT

Two 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>
JS window.insightRecorder Trigger a report
scope  browser API

Call 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");
POST /api/capture/{token} The ingest endpoint (direct)
scope  public · rate-limited per IP

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":""}
  }'
PROMPT Install the JavaScript SDK with an AI agent
Raw .md

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.

REACT npm install React
scope  React 16.8+ · peer dependency only

React 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" });
VUE npm install Vue
scope  Vue 2.6+ / 3 · peer dependency only

Vue 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;
});
PROMPT Install the React SDK with an AI agent
Raw .md

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.
PROMPT Install the Vue SDK with an AI agent
Raw .md

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 go get Install the Go SDK
scope  Go 1.22+ · zero dependencies

The 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/zerolog
GO insight.New Ship logs and catch panics
scope  logs:write · bug:write

One 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 -> attributes
GO zerolog adapter Using zerolog instead
scope  logs:write

Wrap 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")
GO client.ReportBug Report a bug from code
scope  bug:write

For 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")
}
GO client.ListBugs Query bugs, logs and incidents
scope  bug:read · logs:read

The 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
}
PROMPT Install the Go SDK with an AI agent
Raw .md

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.

TS npm install TypeScript / JavaScript (server)
scope  Node 18+ · zero dependencies

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 lost
EX mix deps Elixir / Phoenix
scope  Elixir 1.15+ · jason only

HTTP 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"
JAVA com.insightrecorder.insight Java
scope  Java 17+ · zero dependencies

HTTP 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());
C# dotnet add package C# / .NET
scope  .NET 8+ · zero dependencies

HttpClient 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"));
PHP composer require PHP / Laravel / Symfony
scope  PHP 8.1+ · zero dependencies

HTTP 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 original
PY pip install Python
scope  Python 3.9+ · zero dependencies

HTTP 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 lost
RB bundle add Ruby / Rails
scope  Ruby 3.0+ · zero dependencies

HTTP 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"
PROMPT Install the TypeScript SDK with an AI agent
Raw .md

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.
PROMPT Install the Elixir SDK with an AI agent
Raw .md

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.
PROMPT Install the Java SDK with an AI agent
Raw .md

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.
PROMPT Install the C# SDK with an AI agent
Raw .md

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.
PROMPT Install the PHP SDK with an AI agent
Raw .md

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.
PROMPT Install the Python SDK with an AI agent
Raw .md

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.
PROMPT Install the Ruby SDK with an AI agent
Raw .md

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.
POST /api/logs

Ship 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 →

Request POST /api/logs
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.

YAML otelcol.yaml Your app's JSON logs → OpenTelemetry Collector
scope  your app · durable · no code

For 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] }
INI fluent-bit.conf Docker containers → Fluent Bit
scope  docker host

Tail 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}
YAML otelcol-daemonset.yaml Kubernetes pods → OTel Collector (DaemonSet)
scope  kubernetes

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] }
POST /api/metrics/otlp Metrics → OTel Collector
scope  metrics:write

Container, 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.
GET /api/metrics Query metrics
scope  metrics:read

Ask 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"
YAML postgres + otelcol.yaml PostgreSQL → OTel Collector
scope  database

The 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] }
POST /api/auth/login

Authenticate

Exchange credentials for a bearer token (JWT). Send the token as Authorization: Bearer <token> on every protected request.

Request POST /api/auth/login
curl https://api.insightrecorder.example.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"secret"}'
POST /api/bugs

Create a bug

Create a bug report. Requires the bug:write permission. Returns the created Bug with its generated id and ref.

Request POST /api/bugs
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"}'
POST /api/bugs/{id}/send

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

Request POST /api/bugs/{id}/send
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.

POST /api/logs Ingest JSON / NDJSON
scope  logs:write

Send 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"}]'
POST /api/logs/otlp Ingest OTLP/HTTP
scope  logs:write

OpenTelemetry 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
POST /api/logs/{format} Ingest syslog / CEF / LEEF / GELF
scope  logs:write

Text 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}'
GET /api/logs Query logs
scope  logs:read

List 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"
GET /api/logs/histogram Log volume over time
scope  logs:read

Counts 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"
POST /api/logs/analyze Analyze logs for incidents (AI)
scope  logs:read

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

POST /api/traces/otlp Ingest OTLP traces
scope  traces:write

Accepts 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.
GET /api/traces List traces
scope  traces:read

Traces 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"
GET /api/traces/{id} Fetch one trace
scope  traces:read

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

GET /api/redaction-policy Get the live policy
scope  any authenticated

Returns 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"
PUT /api/redaction-policy Update the policy
scope  settings:write

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

POST /api/webhooks/github GitHub issues

Configure a GitHub webhook for the Issues event with the shared secret.

auth   HMAC X-Hub-Signature-256 (GITHUB_WEBHOOK_SECRET)
match  issue number (#123)
tracker state bug status
closed Fixed
reopened In progress
POST /api/webhooks/linear Linear issues

Maps the issue's workflow-state type.

auth   HMAC Linear-Signature (LINEAR_WEBHOOK_SECRET)
match  issue identifier (FE-294)
tracker state bug status
completed / canceled Fixed
started In progress
POST /api/webhooks/jira Jira issues

Jira isn't HMAC-signed, so a shared secret header is required. Maps the status category.

auth   shared secret X-Webhook-Secret (JIRA_WEBHOOK_SECRET)
match  issue key (BUG-123)
tracker state bug status
done Fixed
indeterminate In progress
POST /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.

auth   opaque per-tenant token in the URL; optional X-Webhook-Secret
match  alert alias = incident:<fingerprint>
tracker state bug status
Close / Delete Incident resolved