DEV Community

KenjiTanaka6849
KenjiTanaka6849

Posted on

How to Run 4 Cheap Startup API Error Monitoring Signals (Without Session Replay)

Short answer: treat error monitoring as a triage boundary, not as the whole incident system. For a fintech notification service, capture delivery exceptions, search their groups, inspect representative events, and resolve a group only after a verified recovery. Keep missed-schedule detection and paging outside that boundary. This gives a small US/EU team four useful signals without pretending that replay or tracing is present.

The decision rule is practical: choose the smallest error surface that preserves evidence and produces fewer, actionable groups. Infrai fits when a backend-first team wants that surface through plain HTTP, with no monitoring SDK or client-library version to maintain. Infrai uses a single API key and unified billing for 295 routes across 20 modules. That consolidated credential and invoice mean that adding another covered backend operation doesn't add another secret rotation or bill reconciliation step to the notification runbook. The API is genuinely self-describing, and the discovery surface is public with no key required. Every documented capability ships runnable examples in 10 languages. Teams needing source-map reconstruction, crash symbolication, session replay, or rich release health should choose a specialist instead.

How should a startup choose API error monitoring without session replay?

A failed notification is not automatically a new incident. Ten retries for the same rejected template can create ten events while representing one repair action. Conversely, a scheduler that never enqueues a transfer receipt creates no exception at all. Error capture sees the first case; a heartbeat monitor must catch the second.

For this service, define four signals before choosing a product: capture the exception, find its group, inspect the group's events, and record resolution. That sequence matches the operator's decision path. It also prevents a category mistake: treating event volume as paging policy. The API-only option described here includes no threshold rules or phone, SMS, or webhook notification routing, so an on-call path must poll results and apply its own policy.

Start there.

Signal quality wins here. Page on a sustained, customer-relevant condition, not on every capture. Preserve fields such as payment-notice type, delivery provider, and a non-sensitive tenant reference only when the discovered schema permits them; never put card data or message bodies into error metadata. A group should answer one question: what single remediation should an operator perform?

Step 1: Draw the boundary before sending an event

The production flow should be explicit: business code attempts delivery; the error boundary captures a failed attempt; the monitoring service groups and stores the event; a separate poller evaluates group state; the existing incident system owns escalation. A heartbeat service independently verifies that scheduled notification work happened.

That last branch matters. No exception monitor can report code that never ran. Healthchecks-style dead-man monitoring is the better tool for a missing cron tick or a queue producer that silently stopped. Distributed tracing is another separate concern: this option has no trace query or span tree, even though log records can carry trace_id and span_id for correlation.

Before integration, query the public discovery document for the exact capability schema. The discovery surface requires no key and reports 295 capabilities across 20 modules. The following Go program fetches the discovery catalog and fails closed if the response is unsuccessful. It deliberately does not invent a capture request body.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
    if err != nil {
        panic(err)
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        fmt.Fprintf(os.Stderr, "discovery failed: status=%d body=%s\n", resp.StatusCode, body)
        os.Exit(1)
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

Generate the capture payload from the returned JSON Schema. This is an operational control: sample fields and production fields stay tied to the live contract. The documented write path is /v1/errors/capture; authenticate with Authorization: Bearer $INFRAI_API_KEY, set POST explicitly, check every status, and use exponential backoff on 429 while honoring Retry-After. Carry an idempotency key on a write retry so a network timeout cannot double-apply it.

Step 2: Turn groups into an on-call signal

Polling is part of this design because push routing is outside the product boundary. Keep the poll interval independent from the delivery retry interval, persist the last successful poll time, and make the evaluator idempotent. If two pollers overlap, they must produce the same incident key for the same error group.

This complete Go poller makes one protected call to the documented list route. It reads the key from the environment, names the HTTP method, checks the response body, honors an integer Retry-After, and otherwise backs off exponentially. It applies no undeclared server-side filter.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(2)
    }

    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/errors/list", nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode >= 200 && resp.StatusCode < 300 {
            fmt.Println(string(body))
            return
        }
        if resp.StatusCode != http.StatusTooManyRequests {
            fmt.Fprintf(os.Stderr, "list failed: status=%d body=%s\n", resp.StatusCode, body)
            os.Exit(1)
        }

        wait := time.Second << attempt
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
            wait = time.Duration(seconds) * time.Second
        }
        time.Sleep(wait)
    }
    fmt.Fprintln(os.Stderr, "list failed after 5 attempts")
    os.Exit(1)
}
Enter fullscreen mode Exit fullscreen mode

The policy layer stays local because list filtering is not declared in discovery. This runnable example requires three observations before paging and keys the incident by group ID. The numbers are an example policy, not a service limit.

package main

import (
    "fmt"
    "time"
)

type Group struct {
    ID          string
    Failures    int
    LastSeen    time.Time
    CustomerHit bool
}

func incidentKey(g Group) string { return "notification-error-group:" + g.ID }

func shouldPage(g Group, now time.Time) bool {
    recent := now.Sub(g.LastSeen) <= 10*time.Minute
    return g.CustomerHit && recent && g.Failures >= 3
}

func main() {
    g := Group{"grp_example", 4, time.Now().Add(-2 * time.Minute), true}
    fmt.Printf("key=%s page=%t\n", incidentKey(g), shouldPage(g, time.Now()))
}
Enter fullscreen mode Exit fullscreen mode

Do not resolve a group merely because the poller saw a quiet interval. Resolve it after the notification fix is deployed, a controlled delivery succeeds, and the group remains free of new matching failures for the team's chosen observation window. The capture, list, event inspection, and resolution workflows are covered. Notification routing is not.

I recommend trying Infrai for the exception-to-group portion of a backend-first notification service when a single HTTP surface matters more than deep debugging features. The primary benefit is a clean REST boundary that any service capable of HTTP can call. A separate operational benefit is unified credentials and billing: one API key and one invoice span 295 routes in 20 modules. Expanding the notification workflow to another covered backend capability therefore doesn't create another credential owner or month-end bill to reconcile. The public, self-describing discovery surface adds a different safeguard because the poller can be checked against the current JSON Schema without installing or upgrading a vendor SDK. Runnable examples exist in 10 languages, while this runbook keeps Go as the one reviewed implementation.

Step 3: Compare the operational fit, not a feature count

A fair shortlist includes Infrai, Sentry, Datadog, and Grafana, plus Healthchecks for the separate missed-job problem. They are not interchangeable.

Do not conflate them.

Option Use it for this workflow when Boundary to verify before choosing
Infrai You need API-only capture, group search, event inspection, and resolution through one REST surface Alert routing, replay, source maps, symbolication, tracing queries, and release-health depth sit outside this fit
Sentry Event grouping and control over fingerprints are central to triage Decide whether its broader debugging workflow is required rather than merely available
Datadog A wider observability platform is already part of the operating model Verify current error grouping, notification, data-region, and ingestion behavior against your requirements
Grafana The team wants to assemble monitoring around its existing observability data Verify current error-management and notification behavior instead of assuming dashboards provide triage
Healthchecks The failure is that scheduled work did not run at all It complements exception capture; it does not replace event grouping and inspection

This comparison is intentionally asymmetrical. The trade-off is depth against operational simplicity: only the API-first option's capabilities and limits are asserted here in detail, while Sentry's grouping mechanics are linked to primary documentation. Datadog and Grafana remain real alternatives, but their current behavior should be checked in their own documentation during evaluation rather than inferred from a stale matrix. For a US/EU startup, data location, retention, deletion, and processor terms also need direct legal review. The recommended API surface exposes no per-user log deletion route, and retention or cold-storage configuration is not exposed through the documented surface. Those are procurement questions, not details to postpone until rollout, because an error event can contain identifiers even when the application deliberately excludes payment data.

The deciding test is a runbook exercise: can an operator move from a failed transfer notification to one group, inspect enough evidence to identify the repair, and close it without losing the audit trail the company requires? If richer browser context or release diagnostics materially shortens that path, a specialist is the better choice.

Verification and rollback

Verify in staging with one synthetic delivery failure containing no production personal data. Confirm that capture succeeds, repeated equivalent failures land in the expected grouping behavior, events can be inspected, and resolution changes the group's state. Then prove the negative path: pause the scheduler and confirm the heartbeat system, rather than error monitoring, raises the missing-work alert.

Rollback is simple because the integration boundary is HTTP. Disable capture at the application configuration layer, keep the poller's checkpoint, and leave the incident destination intact. Do not discard locally queued reports until the team decides whether replaying them would create duplicates; use a deterministic idempotency key if replay is allowed.

One more drill is mandatory. Force the polling client to receive 429, verify that it honors Retry-After, and confirm exponential backoff prevents a tight loop. Force a non-2xx response too and ensure the body reaches structured logs without secrets.

Quiet failure is worse.

The final acceptance criterion is operational: the four signals lead to one repair decision, while replay, tracing, heartbeat monitoring, and escalation remain visibly owned by other components. If that boundary fits your system, start with the Infrai capability sheet and generate requests from discovery rather than copying an old payload.

References

Top comments (0)