Node.js / TypeScript SDK

A zero-dependency typed client over the whole API. Every method below maps 1:1 to an endpoint documented elsewhere in these docs — this page is the full usage reference: every method, every parameter shape, one working example each.

Install

npm install exitprobe

Requires Node 18+ (it uses the global fetch). Works in Node, Bun, Deno, and edge runtimes like Cloudflare Workers.

Configuration

import { ExitProbe } from 'exitprobe';

const client = new ExitProbe({
  apiKey: process.env.EXITPROBE_API_KEY!, // Settings → API Tokens in your dashboard
  baseUrl: 'https://api.exitprobe.com/api/v1', // override for self-hosted/staging
  timeoutMs: 30_000,
});

Error handling

Every non-2xx response throws ExitProbeError, carrying the HTTP status and, for a 422, the field-level validation errors.

import { ExitProbeError } from 'exitprobe';

try {
  await client.monitors.create({ name: '' });
} catch (err) {
  if (err instanceof ExitProbeError) {
    console.error(err.status, err.message, err.errors);
  }
}

Projects

Every method is under client.projects.

list(): Promise<{ projects: Project[] }>

Every project on your account.

const { data } = await client.projects.list();
console.log(data.projects);
create(input: { name: string }): Promise<{ project: Project }>

Create a project to group monitors under.

const { data } = await client.projects.create({ name: 'Production' });
console.log(data.project.id, data.project.api_key);
get(id: number): Promise<{ project: Project }>

Fetch one project.

const { data } = await client.projects.get(3);
update(id: number, input: Partial<{ name: string }>): Promise<{ project: Project }>

Rename a project.

await client.projects.update(3, { name: 'Production (EU)' });
delete(id: number): Promise<unknown>

Soft-delete — recoverable with restore().

await client.projects.delete(3);
restore(id: number): Promise<{ project: Project }>

Undo a delete().

await client.projects.restore(3);
regenerateKey(id: number): Promise<{ project: Project }>

Rotate the project's api_key. The old key stops working immediately.

const { data } = await client.projects.regenerateKey(3);
console.log(data.project.api_key); // the new key
clearChecks(id: number): Promise<unknown>

Delete stored check history for every monitor in the project.

await client.projects.clearChecks(3);
monitors(id: number, params?: ListMonitorsParams): Promise<{ total: number; monitors: Monitor[] }>

Monitors scoped to this project — same filters as monitors.list().

const { data } = await client.projects.monitors(3, { status: 'down' });

Monitors

Every method is under client.monitors.

Listing & fleet status
list(params?: ListMonitorsParams): Promise<{ total, summary, pagination, monitors }>

List monitors, optionally filtered. status is up/down/degraded/flapping/ip_mismatch/unknown. data.summary always counts the whole filtered fleet. Send per_page (max 100) and optionally page to paginate; omit both to get everything.

const { data } = await client.monitors.list({ per_page: 25, page: 1, range: '7d' });

console.log(data.summary);    // { total: 93, up: 88, down: 2, degraded: 1, flapping: 1, ip_mismatch: 1, unknown: 0, inactive: 1 }
console.log(data.pagination); // { page: 1, per_page: 25, total: 93, last_page: 4, has_more: true }

data.monitors.forEach((m) => console.log(m.id, m.name, m.fleet_health, m.uptime_history));
Each row carries fleet_health, protocols_summary (per-protocol status and latency) and uptime_history (timeline bars + uptime %). Add defer_uptime: true and lean: true for a much lighter payload on big fleets.
listAll(params?: Omit<ListMonitorsParams, 'page'>): Promise<Monitor[]>

Walks every page for you and returns all monitors. Uses lean + defer_uptime so a 200-server fleet stays fast.

const down = await client.monitors.listAll({ status: 'down' });
for (const m of down) console.log(m.name, m.last_check_at);
summary(params?: { project_id?: number }): Promise<MonitorsSummary>

How many servers are up, down, degraded, flapping, IP-mismatched or inactive — no rows, so it is cheap to poll from a status board.

const { data } = await client.monitors.summary();
console.log(data.down, 'down,', data.degraded, 'degraded,', data.flapping, 'flapping,', data.inactive, 'inactive');
Adding a monitor
create(input: MonitorInput): Promise<{ monitor: Monitor; protocols: string[] }>

Create a monitor in your default project. See Protocol Reference for the exact per-protocol fields (wireguard_config, vless_uri, ikev2_target_host, etc).

const { data } = await client.monitors.create({
  name: 'EU Exit Node',
  expected_exit_ip: '203.0.113.45',
  check_interval_seconds: 300,
  wireguard_enabled: true,
  wireguard_config: '[Interface]\nPrivateKey=...\n[Peer]\nPublicKey=...\nEndpoint=vpn.example.com:51820',
});
console.log(data.monitor.id, data.protocols); // ['wireguard']
At least one {protocol}_enabled flag plus its config/URI is required, or this throws a 422.
createForProject(projectId: number, input: MonitorInput): Promise<{ monitor: Monitor; protocols: string[] }>

Same as create(), but inside a specific project instead of your default one.

const { data } = await client.monitors.createForProject(3, {
  name: 'US Exit Node',
  vless_enabled: true,
  vless_uri: 'vless://uuid@host:443?security=tls&sni=host#US-Node',
});
Reading & managing a monitor
get(id: number): Promise<{ monitor: Monitor }>

Fetch one monitor's full current state.

const { data } = await client.monitors.get(42);
update(id: number, input: Partial<MonitorInput>): Promise<{ monitor: Monitor }>

Change any subset of fields — e.g. enable a second protocol on an existing monitor.

await client.monitors.update(42, {
  check_interval_seconds: 60,
  hysteria_enabled: true,
  hysteria_uri: 'hysteria2://password@host:443?sni=host&insecure=1',
});
delete(id: number): Promise<unknown>

Soft-delete — recoverable with restore().

await client.monitors.delete(42);
restore(id: number): Promise<{ monitor: Monitor }>

Undo a delete().

await client.monitors.restore(42);
Running an on-demand check
checkNow(id: number): Promise<{ check_ids: number[] }>

Trigger an immediate check across every probe assigned to this monitor. Checks run asynchronously — poll checkStatus() with the returned ids.

const { data } = await client.monitors.checkNow(42);
console.log(data.check_ids); // [9001, 9002, 9003]
checkStatus(id: number, checkIds: number[]): Promise<unknown>

Poll the checks dispatched by checkNow(). data.all_complete flips to true once every probe has answered. (checkNowAndWait() below does this loop for you.)

let done = false;
while (!done) {
  const { data } = await client.monitors.checkStatus(42, [9001, 9002, 9003]);
  done = data.all_complete === true; // data.checks holds each probe's result
  if (!done) await new Promise((r) => setTimeout(r, 2000));
}
History, uptime & analytics
checks(id: number, params?: { page?, per_page?, protocol? }): Promise<unknown>

Paginated raw check history.

const { data } = await client.monitors.checks(42, { per_page: 50, protocol: 'wireguard' });
clearChecks(id: number): Promise<unknown>

Delete this monitor's stored check history.

await client.monitors.clearChecks(42);
uptime(id: number, params?: { range? }): Promise<unknown>

Uptime percentage and latency history. range is 1h/6h/12h/24h/7d/30d/90d.

const { data } = await client.monitors.uptime(42, { range: '7d' });
uptimeRegions(id: number, params?: { range? }): Promise<unknown>

Same, broken down per probe region.

const { data } = await client.monitors.uptimeRegions(42, { range: '30d' });
analytics(id: number, params?: { range? }): Promise<unknown>

The full charting/analytics payload used by the monitor detail page in the dashboard.

const { data } = await client.monitors.analytics(42, { range: '30d' });
status(id: number): Promise<{ status: string }>

A lightweight status probe — cheap to poll frequently from your own uptime page or a status badge.

const { data } = await client.monitors.status(42);
console.log(data.status); // 'up'
Everything about one monitor, bulk edits & waiting on a check
details(id: number, opts?: { range?, checks? }): Promise<{ monitor, status, uptime, regions, analytics, checks }>

Everything the dashboard's monitor page shows — configuration, live status per protocol, uptime for the range, per-region uptime, the analytics behind the graphs, and the latest checks — fetched in parallel in one call.

const d = await client.monitors.details(42, { range: '7d', checks: 50 });

console.log(d.status.status, d.uptime.uptime_percentage);
console.log(d.checks); // latest 50 checks, newest first
checkNowAndWait(id: number, opts?: { timeoutMs?, intervalMs? }): Promise<{ completed: boolean; checks }>

Triggers a check and resolves when every probe has answered (default timeout 90s — an IKEv2 handshake alone can take a minute). If the timeout passes it resolves with completed: false instead of throwing.

const { completed, checks } = await client.monitors.checkNowAndWait(42);
for (const c of checks) console.log(c.protocol, c.probe_name, c.status, c.latency_ms);
bulkUpdate(input: { monitor_ids: number[]; check_interval_seconds?; probe_mode?; probe_ids? }): Promise<unknown>

Change the check interval and/or probe assignment of many monitors at once. A field is only changed when you send it.

await client.monitors.bulkUpdate({
  monitor_ids: [1, 2, 3],
  check_interval_seconds: 300,
});

VPS servers

Every method is under client.vps. Register a server, get the one-time install command, then read the same live health data the dashboard shows.

create(input: { name; ip_address; project_id?; location_* }): Promise<{ vps_server; agent_token; install_command }>

Register a server. The response carries the agent token and the one-time command to run on the server. The token is shown only now — store it, or rotate it later.

const { data } = await client.vps.create({ name: 'prod-db-1', ip_address: '203.0.113.5' });

console.log(data.install_command); // curl -sSL https://… | sudo bash -s -- -s … -t vps_… -r vps
console.log(data.agent_token);     // vps_…
Run install_command once on the server (as root). The server then reports its own health every ~90 seconds.
list(params?: { project_id?, status?, search?, page?, per_page? }): Promise<{ vps_servers, summary, pagination, usage }>

Your servers with fleet counts. status is online/offline/unregistered; search matches name, IP or hostname. Send per_page/page to paginate.

const { data } = await client.vps.list({ per_page: 25 });

console.log(data.summary);
// { total: 4, online: 3, offline: 1, unregistered: 0, inactive: 0, average_health_score: 88, critical: 1, excellent: 2 }
console.log(data.usage); // { count: 4, max_custom_vps: 10, can_add_custom_vps: true }
listAll(params?): Promise<VpsServer[]>

Walks every page and returns all servers.

const offline = await client.vps.listAll({ status: 'offline' });
summary(params?: { project_id? }): Promise<VpsSummary>

Online / offline / unregistered counts and the average health score.

const { data } = await client.vps.summary();
console.log(data.online, data.offline, data.average_health_score);
get(id: number, params?: { range?, limit? }): Promise<{ vps_server, health_snapshots, incidents }>

Everything on the dashboard's server page: live CPU / memory / disk / swap / load, health score and grade, availability (24h / 7d / 30d), system information, top processes, alert thresholds, monthly bandwidth, the snapshot history for the charts, and recent incidents.

const { data } = await client.vps.get(7, { range: '24h' });

const s = data.vps_server;
console.log(s.health_score, s.health_grade, s.cpu_usage, s.memory_usage, s.disk_usage);
console.log(s.availability);      // { '24h': 100, '7d': 99.9, '30d': 99.95 }
console.log(s.top_processes);     // heaviest processes right now
console.log(data.health_snapshots.length, data.incidents);
range is 1h/6h/24h/7d/30d; limit (1–2000, default 500) bounds the number of snapshots, newest first.
history(id: number, params?: { range?, limit? }): Promise<{ health_snapshots, incidents }>

Just the chart history and incidents — nothing else — for a window.

const { data } = await client.vps.history(7, { range: '7d' });
getSummary(id: number): Promise<{ vps_server }>

The current state without the (large) snapshot history — the cheap call for a status widget.

const { data } = await client.vps.getSummary(7);
update(id: number, input: VpsUpdateInput): Promise<{ vps_server }>

Rename, switch off, mute alerts, or change when a CPU / memory / disk alert fires. Thresholds are 50–100; threshold_duration_minutes is 5–1440 (how long a reading must stay over the line).

await client.vps.update(7, {
  cpu_threshold: 85,
  memory_threshold: 90,
  disk_threshold: 90,
  threshold_duration_minutes: 30,
  notifications_enabled: true, // false mutes outbound alerts; incidents are still recorded
});
regenerateToken(id: number): Promise<{ vps_server_id; agent_token; install_command }>

Rotate the agent token. The old token stops working immediately, so run the returned command on the server again.

const { data } = await client.vps.regenerateToken(7);
console.log(data.install_command);
delete(id: number): Promise<unknown>

Remove a server and its history. Final — there is no restore.

await client.vps.delete(7);
lookupIp(ip: string): Promise<…>

Resolve the country and city for an IP before registering.

const { data } = await client.vps.lookupIp('203.0.113.5');

Plan, limits & renewal

Read-only under client.billing. Changing or cancelling a plan is deliberately not possible with an API token — do that in the dashboard.

usage(): Promise<Usage>

Your plan, every limit with how much of it you have used, wallet credit, and the renewal date.

const { data } = await client.billing.usage();

console.log(data.plan.name, data.plan.price, data.plan.billing_interval);
console.log(data.monitors);   // { used: 41, limit: 100, remaining: 59, percentage: 41, … }
console.log(data.custom_vps); // { used: 4, limit: 10, … }
console.log(data.manual_checks.resets_at);

const b = data.billing;
console.log(b.plan_period_ends_at, b.days_until_renewal, b.credit_balance);
days_until_renewal is negative once the paid period has lapsed, and null on a free plan.
current(): Promise<{ plan; usage }>

The plan with its full limits (allowed check intervals, data retention, feature flags) plus the same usage meters.

const { data } = await client.billing.current();
console.log(data.plan.allowed_intervals, data.plan.data_retention_days);
plans(): Promise<…>

The public plan catalog, including add-on prices.

const { data } = await client.billing.plans();

Token abilities

A token created with no abilities has full access. To hand an integration only what it needs, pick abilities when creating the token (Settings → API Tokens):

monitors:read     monitors:write      # VPN monitors
projects:read     projects:write      # projects
vps:read          vps:write           # VPS servers (write = register, edit, delete, rotate token)
billing:read                          # plan, limits, usage, renewal date (read-only)
integrations:read integrations:write  # notification integrations

A token missing the ability a call needs gets a 403 (ExitProbeError.status === 403).

Endpoints without a typed method (incidents, server sources, escalation policies, …) are still reachable through the low-level escape hatch: client.request(method, path, { query?, body? }).