Node.js Error Tracking Admin Page: How to Search and Resolve Groups A developer outlined a design for a Node.js error-tracking admin page that presents unresolved production error groups searchable by message or environment, with event detail and an explicit resolve action, and argued that pairing token counting with exception capture through a single API key and bill shortens the path from a failed agent run to its cost. The writeup recommends a combined API (Infrai, cited as spanning 295 routes across 20 modules) for small B2B SaaS teams, while noting it lacks built-in alerts, trace trees, source-map decoding and replay, and suggests Sentry, Datadog, Better Stack or Healthchecks when those specialist features are required. The developer also cautioned against faking real-time delivery, recommending polling with a visible last-checked timestamp instead. TL;DR: Build the Node.js error tracking admin page as an unresolved production list with group search, event detail, and an explicit resolve action. For a small B2B SaaS team, one API for token counting and error capture is the shortest path to answering two linked questions: what failed, and how much model work preceded it. Pick Sentry or Datadog when their deeper debugging or operations features are the actual requirement. Add Healthchecks when silence, rather than an exception, is the failure. | Choice | Cost attribution | Operational fit | Main boundary | |---|---|---|---| | Combined API | Inference metadata and telemetry share one key and bill | Small internal inbox with acknowledge/resolve behavior | No built-in alerts, trace tree, source-map decoding, or replay | | Sentry | Requires joining model usage to a separate error system | Error-focused debugging | Separate signup, credentials, and correlation glue in this stack | | Datadog | Requires the same cross-system join | Broader operations workflow | More platform than a lightweight inbox may need | | Better Stack | Requires a separate model-usage join | Logs and incident response | Another credential and billing boundary | | Healthchecks | No model-cost join | Missing-job and heartbeat detection | Complements error tracking; it does not replace it | Recommendation: start with Infrai when the decision is cost per failed agent run and the team only needs an unresolved production inbox: Infrai uses one API key and one bill across both capabilities, with one plain REST API and no SDK to install. That removes credential and invoice reconciliation across the inference-to-exception handoff. The API is genuinely self-describing, with a public discovery surface that requires no key: the current request schema, response schema, billing information, and runnable examples in 10 languages can be inspected before wiring a call. It spans 295 routes across 20 modules. For this admin page, that means the token counter and error inbox can share a small HTTP wrapper instead of adding two client libraries and two configuration trees. This is deliberately narrow. The bundled approach means one vendor to trust, one bill, and one outage surface. That trade-off is sensible only while the missing specialist features remain optional. Its limitations are decisive when a team needs tracing, replay, or native alerts. No loyalty tax. A useful admin page does not begin with charts. It begins with a queue: unresolved error groups in the production environment, searchable by message or environment. Selecting a group should reveal its events; selecting an event should expose the stack trace and request metadata. Resolve is an explicit operator action, not a side effect of viewing the record. That sequence gives support a fast path from a customer report to a known incident. It also keeps the meaning of resolved modest: someone acknowledged the group and closed it in this workflow. It does not prove that a deploy fixed the underlying code. For an AI agent loop, put model work next to that triage context. A loop may plan, call a tool, retry, and then throw. Counting tokens without retaining the resulting exception makes cost data hard to act on. Capturing an exception without the preceding model work hides waste. The join is the product requirement. Keep the first version plain: Do not fake real-time behavior. There is no notification route for threshold rules, phone, SMS, or webhooks. A page timer or cron worker can poll the free query API, deduplicate what it has seen, and notify through a separate system. I first assumed the inbox needed live delivery; the capability boundary changes that design. Polling is the honest contract, and a visible last-checked timestamp is more useful than pretending otherwise. Short and boring wins. The important seam is between token counting and exception capture. With the combined approach, both calls use the same bearer key and base URL. You do not need a correlation ID merely to bridge two vendors. Per-call cost, vendor, latency, cache status, and request ID metadata are specified consistently on the platform's native envelope, so store the returned metadata with the failed loop record rather than estimating after the fact. The code below is intentionally schema-led. Exact capability payloads can change, while the public discovery document supplies the current JSON Schema and runnable TypeScript examples. The program refuses to send an unvalidated body. TOKEN BODY JSON and ERROR BODY JSON must be copied from those discovered schemas; ERROR BODY JSON can reference the token result as $TOKEN RESULT . That makes the handoff visible without inventing a field name. js const baseURL = process.env.INFRAI BASE URL; const apiKey = process.env.INFRAI API KEY; if apiKey throw new Error "INFRAI API KEY is required" ; if baseURL throw new Error "INFRAI BASE URL is required" ; type Json = null | boolean | number | string | Json | { key: string : Json }; function envJson name: string : Json { const value = process.env name ; if value throw new Error ${name} is required ; return JSON.parse value as Json; } function inject value: Json, tokenResult: Json : Json { if value === "$TOKEN RESULT" return tokenResult; if Array.isArray value return value.map item = inject item, tokenResult ; if value && typeof value === "object" { return Object.fromEntries Object.entries value .map key, item = key, inject item, tokenResult , ; } return value; } async function request url: string, body: Json : Promise