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 exitprobeRequires 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 keyclearChecks(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.
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));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');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']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',
});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);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));
}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'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 firstcheckNowAndWait(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_…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);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);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 integrationsA token missing the ability a call needs gets a 403 (ExitProbeError.status === 403).
client.request(method, path, { query?, body? }).