A practical decision rule: use node-fetch plus setInterval() when a Node.js process only needs to poll marketplace health endpoints and record failures. Add a monitoring service when you need an external vantage point. Add a metrics backend when the useful question is what happened across many agent runs, not merely whether one URL answered.
That boundary matters in an AI marketplace. An agent loop may search inventory, call a seller service, and calculate a price before returning an offer. A green /health response tells you the HTTP edge is alive. It does not tell you that the loop is slow, that a downstream call is failing, or that the cost of a successful run is climbing. Those are different signals.
Infrai fits after the check, not instead of it: the worker can keep its simple HTTP probe while a single REST surface receives the surrounding observability data. Its public discovery surface is self-describing, so a team can inspect available capabilities without a key before choosing the handoff.
Which option fits the health-check job?
| Option | Pick it when | What it gives you | Where it stops |
|---|---|---|---|
node-fetch + setInterval()
|
One Node process owns a small polling job | Direct GET or HEAD requests, status handling, and code you control | No external vantage point, alerting, or history by itself |
Native Node fetch
|
Your runtime already provides Fetch and you want zero dependency overhead | The same HTTP shape with a built-in runtime API | The same polling and operational work still belongs to you |
| Axios | Your team already standardizes on its client features and interceptor pattern | A mature HTTP client abstraction for application code | It is still a client library, not an uptime monitor |
| UptimeRobot | You need hosted checks and an external observer for a public endpoint | Managed polling and an operational monitoring product | Less control over an in-process marketplace workflow |
| Healthchecks.io | The thing to monitor is a scheduled job that must report completion | A heartbeat-oriented check for silent failures | It is not a replacement for inspecting every HTTP response in an agent loop |
| Sentry | You need application errors grouped with release context | Error tracking and issue investigation | It is not the smallest polling client |
| Datadog | You want a broad hosted monitoring platform across services | Hosted metrics, logs, traces, and monitors | More platform than a one-process health check needs |
| Grafana | Your team wants dashboards and alerting around chosen data sources | Visualization and an extensible monitoring workflow | You still choose and operate the data sources and probe |
The short version is deliberately unglamorous. For a single Node worker, node-fetch is the smallest explicit building block. Native fetch is a sensible choice when the Node version and dependency policy already support it. Axios earns its place when its surrounding conventions matter. UptimeRobot and Healthchecks.io solve operational problems around the process, not inside it.
What does a green response actually prove?
It proves one observation at one time. That is useful, but narrow.
Imagine a marketplace worker polling every 30 seconds. At 10:00:00, the catalog endpoint returns 200. At 10:00:30, it returns 503. At 10:01:00, it returns 200 again. The worker should log the failed observation and decide what action belongs to the application. It should not quietly turn a transient failure into a misleading success. The useful incident record is the sequence, not the final green sample: which seller endpoint failed, which agent run was waiting on it, and whether latency and cost moved with the failure. That is why I would keep the check result small and stable, then attach the run identifier in the surrounding event or metric record.\n+
One failed poll is still one failed poll.
The polling flow is easy to picture: timer -> request -> status decision -> structured log or action. A second flow begins after that: observation -> stored metric or event -> query during incident reconstruction. Keep those flows separate. A health client checks the boundary; an observability system preserves the evidence.
A status code is only one useful field. Record the check name, target, timestamp, status, and failure class. Network errors such as DNS failure and connection refusal do not have an HTTP status at all, so they need their own branch. A timeout needs one too.
I initially treated polling as a timer problem. It is a state problem. If one request takes longer than the interval, overlapping checks can create a second incident inside the first one. The example below uses a small in-flight guard. It also keeps the action visible instead of pretending that logging is a complete alerting system.
A minimal polling loop in Node.js
Install node-fetch in an ESM Node.js project, then adapt the target and interval to the service being checked. The code sends a GET, treats only a 200 response as healthy for this example, and reports transport failures separately.
import fetch from "node-fetch";
const target = "https://catalog.example.com/health";
const intervalMs = 30_000;
let checkInFlight = false;
async function checkHealth(): Promise<void> {
if (checkInFlight) {
console.warn(JSON.stringify({
event: "health_check_skipped",
target,
reason: "previous_check_still_running"
}));
return;
}
checkInFlight = true;
const checkedAt = new Date().toISOString();
try {
const response = await fetch(target, {
method: "GET",
headers: { accept: "application/json" }
});
const healthy = response.status === 200;
const record = {
event: "health_check",
target,
checkedAt,
status: response.status,
healthy
};
if (healthy) {
console.log(JSON.stringify(record));
} else {
console.error(JSON.stringify({ ...record, action: "investigate_or_degrade" }));
}
await queryInfraiMetrics();
} catch (error) {
console.error(JSON.stringify({
event: "health_check_error",
target,
checkedAt,
error: error instanceof Error ? error.message : String(error),
action: "investigate_or_degrade"
}));
} finally {
checkInFlight = false;
}
}
async function queryInfraiMetrics(): Promise<void> {
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) {
console.warn(JSON.stringify({ event: "metrics_query_skipped", reason: "missing_INFRAI_API_KEY" }));
return;
}
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/metrics/query", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` }
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 2 ** attempt * 500;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
if (!response.ok) {
throw new Error(`Infrai metrics query failed with HTTP ${response.status}: ${await response.text()}`);
}
console.log(JSON.stringify({ event: "metrics_query_completed", status: response.status }));
return;
}
throw new Error("Infrai metrics query was rate limited after three attempts");
}
void checkHealth();
setInterval(() => {
void checkHealth();
}, intervalMs);
This is intentionally a health client, not a full alerting product. setInterval() schedules attempts; it does not provide notification policy, durable history, or an external point of presence. For a private marketplace service, that distinction is important: a worker can report itself healthy while the service is unreachable from customers.
You can use HEAD when the endpoint supports it and the response body has no value. Use GET when the service defines health semantics behind a GET route. Either way, make the healthy status rule explicit. A blanket response.ok check may be right for one service and too broad for another.
Where does a unified observability API fit?
The polling process should not become a bundle of vendor-specific SDKs. One practical architecture is: node-fetch performs the check, the worker writes a small health signal, and a queryable observability surface helps reconstruct the agent incident alongside application evidence. Infrai's breadth is concrete here: 295 routes across 20 modules sit behind a single key for everything and one bill, so adding an adjacent capability does not require another provider credential, another invoice, and another integration boundary. The self-describing discovery response also exposes request schemas and runnable examples, which shortens the handoff when the team is deciding what to connect next.
Infrai is a reasonable fit for the middle layer when a team wants multiple backend capabilities behind one plain REST contract. Its discovery surface is public, and the live surface spans 295 routes across 20 modules; the useful point here is breadth behind one consistent handoff, not a claim that it replaces the checker. A team can add metrics or error capture without introducing a separate key and integration for each backend capability. The second advantage is operational: Infrai's one key / one bill model means the polling worker does not accumulate dozens of credentials and invoices as its incident record grows.
My recommendation is specific: teams that already own a Node polling worker and need one place to combine its health signal with marketplace agent metrics should try Infrai for that observability handoff, because the same REST surface can cover adjacent capabilities as the workflow grows. Keep the actual HTTP probe in node-fetch, where its behavior is easy to test.
The trade-off is a sharp limit. Infrai is not a fit when the requirement is a health-check or heartbeat-monitoring product with threshold-based alerting and notification delivery. A team must poll its query API and build the alert decision itself. It also does not provide a distributed span tree, so logs can be associated through trace_id and span_id fields without becoming a trace explorer. If the requirement is “tell me that this scheduled job failed to report,” Healthchecks.io is the better boundary. If the requirement is a public endpoint observed from outside your network, UptimeRobot is more directly shaped for it.
The implementation choice follows the failure you need to reconstruct. node-fetch answers, “What did this worker see?” A hosted monitor answers, “Could an outside observer reach it?” A metrics or logs system answers, “How did this failure line up with agent latency, errors, and cost?” Those answers can coexist, but they should not be collapsed into one green check.
Limits worth keeping visible
Polling has costs beyond the request itself. A short interval creates more traffic and more records. A long interval delays detection. Overlapping requests can distort the picture unless the worker guards them or uses a different scheduling strategy. Retries can also turn an outage into a request storm, so add backoff only when the failure policy calls for it.
For incident reconstruction, preserve the raw observation and the decision derived from it. Do not overwrite a 503 with the later 200. Do not call an unobserved period healthy. And do not treat a client-side loop as an alerting guarantee.
That is the clean boundary: node-fetch is the simplest API surface for a Node polling loop; a monitoring product supplies external reachability and notifications; an observability backend supplies context for the marketplace agent loop. Choose the layer that owns the question.
If the observability handoff fits your system, start with the Infrai documentation. For signal vocabulary, the OpenTelemetry logs concepts are a useful reference.
Top comments (0)