
Research
/Security News
737 Chrome VPN Extensions Linked to Brand Impersonation and Browser Traffic Redirection
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.
@wireboard/api
Advanced tools
@wireboard/apiOfficial TypeScript SDK for the WireBoard REST and Live APIs.
Pull historical analytics, subscribe to real-time visitor activity, and integrate WireBoard with anything you can write code against.
npm install @wireboard/api
Mint a token in Settings → API on your WireBoard dashboard (needs the
analytics:read ability for REST, live:read for the Live API). Then:
import { WireBoardClient } from '@wireboard/api';
const wb = new WireBoardClient({ token: process.env.WIREBOARD_TOKEN! });
// Historical
const { sites } = await wb.sites();
const site = sites[0]!;
const summary = await wb.aggregate({
site_id: site.id,
from: '2026-05-01',
to: '2026-05-22',
});
console.log(`${summary.visitors} visitors, ${summary.pageviews} pageviews`);
// Real-time (managed mode — SDK handles state, drop signals, JWT rotation)
const live = wb.live({
siteId: site.id,
categories: ['visitors', 'top_pages'],
});
live.subscribe(state => {
console.log(
'now:', state.live.visitors?.live ?? 0,
'top:', state.live.top_pages[0]?.url,
);
});
await live.start();
// later:
// live.stop();
The SDK handles snapshot rebuild on reconnect, drop signals, and short-lived
JWT rotation for you. A NEW state object is emitted on every update, so
prev !== next works as a change check in React, Vue, or Svelte.
Every method returns the unwrapped data payload from the API envelope and
throws WireBoardApiError / WireBoardAuthError on failure (see
Errors). Every method accepts an optional { signal?: AbortSignal }
as a final argument.
| Method | Returns | What it does |
|---|---|---|
account() | Account | Team-owner identity + the abilities of this token |
sites() | SitesResult | Every site owned by the team |
aggregate(params) | AggregateResult | Period totals (visitors, pageviews, bounce, duration) |
timeseries(params) | TimeseriesResult | One metric, bucketed by hour or day |
history(params) | HistoryResult | Visitors / returning / pageviews / bounce / duration per day |
breakdown<D>(params) | BreakdownResult<D> | Top-N rows by dimension; row type is narrowed by D |
urls(params) | UrlsResult | Per-URL metrics with prefix / contains / exact filters |
events<G>(params) | EventsResult<G> | Custom events report; row type is narrowed by group_by |
dimensions() | Dimensions | Meta: supported dimensions, metrics, limits |
liveState(params) | LiveStateSnapshot | Current per-category snapshot for one site |
liveToken(params?) | LiveTokenResult | Mint a 15-min subscriber JWT for the SSE stream |
live(options) | LiveClient | Managed Live client (handles snapshot + merge + rotation) |
liveRaw(options) | LiveRawClient | Raw Live client (multi-site, custom merge) |
withMeta(fn) | { data, rateLimit } | Run a call and capture its rate-limit headers |
Full reference: REST · Live · Errors.
The SDK exposes both a managed and a raw client over the same SSE protocol. Pick based on what your UI needs.
const live = wb.live({
siteId: 'xK4mP2nT',
categories: ['visitors', 'top_pages', 'active_sessions'],
onChange: state => render(state.live),
onError: err => console.error(err),
onRotate: () => log('jwt rotated'), // optional, observability
onReconnect: () => log('reconnected'), // optional, observability
});
await live.start();
// state available at `live.state`; subscribe(...) returns an unsubscribe fn
The managed client fetches /v1/live/state on connect, merges drop signals
per category (count: 0 → remove from top-N, step_count: 0 → remove from
active_sessions), rotates the JWT 60 s before expiry with a zero-gap
overlap, dedupes events by lastEventId, and refetches the snapshot on
hard reconnect. onRotate and onReconnect are optional observability
hooks for tracking transparent recoveries.
const raw = wb.liveRaw({
sites: ['xK4mP2nT', 'aB3cD4fG'],
categories: ['visitors', 'top_pages'],
onEvent: env => {
// env is a discriminated union — TS narrows env.data by env.category
if (env.category === 'top_pages') {
for (const row of env.data) {
// row.count === 0 means "remove from local state"
}
}
},
});
await raw.start();
Use raw mode for multi-site dashboards, when you already have your own reactive store, or when you want full control over how drop signals apply.
The generic methods narrow row shapes based on the request.
// Dimension narrows the row's per-dimension field:
const c = await wb.breakdown({ site_id, from, to, dimension: 'country' });
c.rows[0]; // { country: string; visitors: number }
const m = await wb.breakdown({ site_id, from, to, dimension: 'ref_medium' });
m.rows[0]; // { medium: string; visitors: number }
// group_by narrows event row keys (cast with `as const` for the tightest types):
const r = await wb.events({
site_id, from, to,
group_by: ['category', 'utm_source'] as const,
});
r.rows[0]; // { category: string | null; utm_source: string | null; count: number; value: number }
The Live envelope is a discriminated union: switch (env.category) narrows
env.data to the right shape automatically — no casts needed.
Bundlers (Vite, Webpack, esbuild, Rollup) pick the browser-targeted ESM build automatically via the package's conditional exports — no config needed:
// React / Vue / Svelte / vanilla — same import, browser ESM build resolved
import { WireBoardClient } from '@wireboard/api';
const wb = new WireBoardClient({ token: yourShortLivedTokenFromYourServer });
For production, do not ship your long-lived bearer token to the
browser. Mint short-lived subscriber JWTs server-side via
liveToken() and pass
those to the browser.
Working browser demos live in examples/browser/ —
four zero-build pages covering REST, historical analytics, and both Live
modes. See examples/ for the full set.
Two error classes, both extend Error:
import { WireBoardApiError, WireBoardAuthError } from '@wireboard/api';
try {
await wb.aggregate({ site_id, from, to });
} catch (err) {
if (err instanceof WireBoardAuthError) {
// 401 → re-auth; 403 → re-mint a token with the right abilities
} else if (err instanceof WireBoardApiError) {
switch (err.code) {
case 'site_not_found': /* unknown site or wrong team */ break;
case 'concurrent_limit_reached': /* too many live subscriptions */ break;
case 'unknown_filter': /* events filter not whitelisted */ break;
// ...
}
// err.fieldErrors, err.httpStatus, err.rateLimit are all on the error
}
throw err;
}
The SDK auto-retries once on a 429 (honouring Retry-After). Opt out
with new WireBoardClient({ token, retryOn429: false }). There are no
retries on 5xx or network errors — your code decides.
Every REST call accepts an AbortSignal via a { signal } second argument
(standard fetch idiom):
const controller = new AbortController();
const promise = wb.urls(
{ site_id, from, to, prefix: '/checkout' },
{ signal: controller.signal },
);
// elsewhere — e.g. component unmount, route change, user cancel
controller.abort();
The Live clients are cancelled via .stop() instead — it also aborts any
in-flight snapshot fetch or JWT mint.
Every successful response carries X-RateLimit-* headers. To read them
without an extra HTTP call, wrap the request in withMeta:
const { data, rateLimit } = await wb.withMeta(c => c.aggregate({
site_id, from, to,
}));
console.log(`${rateLimit?.remaining}/${rateLimit?.limit} requests left this minute`);
withMeta is safe under Promise.all; each call captures its own slot.
Calls on the outer client (not the closure's c) are NOT instrumented.
The package ships a CLI that exercises every endpoint against your real account:
WIREBOARD_TOKEN=… npx @wireboard/api verify
It hits every REST surface for a 7-day window, opens a 45-second managed
Live subscription, and prints a pass/fail summary table. Exit code 0 on
full pass, 1 on any failure, 2 on usage error. Use --no-color for CI
logs and --duration=920 to also observe a full JWT rotation cycle.
| Runtime | Build picked | Notes |
|---|---|---|
| Bundler (Vite, Webpack, esbuild, Rollup) | dist/index.browser.js (ESM) | Uses global EventSource; no eventsource polyfill bundled |
Node ESM ("type": "module") | dist/index.js (ESM) | Pulls in eventsource for SSE |
Node CJS (require(...)) | dist/index.cjs (CJS) | Same as Node ESM |
| TypeScript | dist/index.d.ts / .d.cts | Resolution matches the runtime build |
You don't configure anything — import { WireBoardClient } from '@wireboard/api'
just works in every environment.
git clone https://github.com/wireboard/api-js
cd api-js
npm install
npm run build && npm test
To exercise the browser examples against the packed tarball (the same
shape users get from npm install), put a token in .env at the repo
root and run:
WIREBOARD_TOKEN=… ./scripts/test-examples.sh
The script builds, runs npm pack, installs the tarball into a scratch
directory, transforms each examples/browser/*.html to import the local
bundle, and serves everything on a free port in 8080–8089. Ctrl+C cleans
up. This is the closest you can get to a customer install without
publishing — use it before sending a PR that touches the build, the
public API surface, or any browser example.
MIT.
FAQs
Official TypeScript SDK for the WireBoard REST and Live (SSE) APIs.
The npm package @wireboard/api receives a total of 9 weekly downloads. As such, @wireboard/api popularity was classified as not popular.
We found that @wireboard/api demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.