Typed Node.js / TypeScript client for Beacon status pages: the full REST API v1, the incident ingest endpoints, heartbeat pings and webhook signature verification.
- Zero runtime dependencies — uses the global
fetch(Node 18+). - ESM and CommonJS builds with bundled type definitions.
- Cursor pagination iterators, typed errors with
code,statusandretryAfter.
Not yet published to npm. Until it is, install from GitHub (npm can't install from a subdirectory of the monorepo, so use the standalone split repo or a tarball):
npm install github:shaferllc/beacon-node#v1.0.0 # builds dist/ via the prepare script # or build a tarball yourself git clone https://github.com/shaferllc/beacon-node.git cd beacon-node && npm ci && npm pack # → beacon-status-node-1.0.0.tgz cd /your/app && npm install /path/to/beacon-status-node-1.0.0.tgz
import { Beacon } from '@beacon-status/node';
const beacon = new Beacon({
token: process.env.BEACON_API_TOKEN!, // bchk_… (Settings → API tokens)
baseUrl: process.env.BEACON_URL, // optional, defaults to https://usebeacon.pro
pageSlug: 'acme', // optional default page for the ingest helpers
});Options: timeoutMs (default 10 000), maxRetries (default 2 — 429s are retried after Retry-After for every method, 5xx and network errors only for GETs) and fetch (custom implementation).
const incident = await beacon.createIncident({
title: 'Payment processing delays',
impact: 'major',
state: 'investigating',
message: 'Stripe webhook delivery is delayed.',
component_ids: [12],
});
await beacon.addIncidentUpdate(incident.id, 'Fix deployed, monitoring.');
await beacon.resolveIncident(incident.id, 'Webhook delivery restored.');
await beacon.updateComponent(12, { status: 'degraded_performance' });
await beacon.updateComponent(12, { status: 'operational' }, 'another-page');Ingest responses use the flat ingest shape (ids are strings), exactly like the HTTP API.
Every resource takes the status page slug first. Lists return { data, meta, links }; iterate() walks every page.
const page = await beacon.statusPages.create({ name: 'Acme Cloud', theme: 'dark' });
const api = await beacon.components.create(page.slug, { name: 'API' });
await beacon.monitors.create(page.slug, {
name: 'API health',
type: 'http',
url: 'https://api.acme.com/health',
interval_minutes: 5,
allow_cold_start: true,
check_window_start: '08:00',
check_window_end: '20:00',
status_component_id: api.id,
});
for await (const incident of beacon.incidents.iterate(page.slug, { state: 'open' })) {
console.log(incident.title);
}
const hook = await beacon.webhooks.create(page.slug, { url: 'https://example.com/beacon' });
console.log(hook.secret); // returned once — store it| Namespace | Methods |
|---|---|
statusPages |
list, iterate, get, create, update, delete |
components |
list, iterate, get, create, update, delete, reorder |
monitors |
list, iterate, get, create, update, delete |
incidents |
list, iterate, get, create, update, delete, listUpdates, addUpdate |
maintenances |
list, iterate, get, create, update, delete |
subscribers |
list, iterate, get, create, update (component filter), delete |
webhooks |
list, iterate, get, create, update, delete |
imports |
list, iterate, get, create |
beacon.request(method, path, body?, query?) reaches anything else under /api/v1.
import { pingHeartbeat } from '@beacon-status/node';
await runNightlyBackup();
await pingHeartbeat('YOUR-HEARTBEAT-TOKEN'); // no API token needed
await pingHeartbeat(monitor.heartbeat_url!, { method: 'GET' });
await beacon.heartbeat('YOUR-HEARTBEAT-TOKEN'); // same, using the client's baseUrlimport { BeaconError } from '@beacon-status/node';
try {
await beacon.components.create('acme', { name: '' });
} catch (err) {
if (err instanceof BeaconError) {
err.status; // 422
err.code; // 'validation_failed' | 'unauthenticated' | 'insufficient_scope' | 'not_found' | 'plan_limit_reached' | 'workspace_read_only' | 'rate_limited' | …
err.errors; // { name: ['The name field is required.'] }
err.retryAfter; // seconds, on 429
}
}import express from 'express';
import { verifyWebhookSignature } from '@beacon-status/node';
app.post('/beacon', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyWebhookSignature(req.body, req.headers, process.env.BEACON_WEBHOOK_SECRET!)) {
return res.sendStatus(403);
}
const event = JSON.parse(req.body.toString());
res.sendStatus(204);
});Pass the raw body (string, Buffer or Uint8Array) and the headers (Node headers object or a Fetch Headers). The check is constant-time and rejects timestamps more than 300 seconds off ({ toleranceSeconds } to change).
npm ci
npm test # vitest, mocked fetch
npm run build # dist/ (ESM + CJS + .d.ts)