Type-safe TypeScript client for the Pi-hole v6 REST API.
- Complete coverage of Pi-hole v6 API (~80 endpoints)
- Type-safe with full TypeScript support
- Result type for explicit error handling (no exceptions)
- Automatic session management with auto-refresh
- Retry with exponential backoff
- Zero runtime dependencies
- Works in Node.js 18+ and browsers
npm install @linx-systems/pihole-api-client
# or
bun add @linx-systems/pihole-api-clientnpm install @linx-systems/pihole-api-client --registry=https://npm.pkg.github.com
import { PiholeClient, isOk, isErr } from "@linx-systems/pihole-api-client"; const client = new PiholeClient({ baseUrl: "http://pi.hole", password: "your-password", }); // Get statistics const statsResult = await client.stats.getSummary(); if (isOk(statsResult)) { console.log("Blocked queries:", statsResult.data.queries.blocked); } // Disable blocking for 5 minutes const blockingResult = await client.dns.disable(300); if (isErr(blockingResult)) { console.error("Failed to disable:", blockingResult.error.message); } // Add domain to denylist await client.domains.deny("ads.example.com"); // Get query log const queries = await client.queries.list({ length: 100 });
const client = new PiholeClient({ // Required baseUrl: "http://pi.hole", // Pi-hole server URL // Authentication (one of these) password: "your-password", // Plain password (auto-authenticates) // or provide session tokens directly sid: "session-id", csrf: "csrf-token", // Optional timeout: 10000, // Request timeout in ms (default: 10000) maxRetries: 3, // Max retry attempts (default: 3) autoRefresh: true, // Auto-refresh sessions (default: true) });
All methods return Result<T, PiholeError>:
type Result<T, E = PiholeError> = | { ok: true; data: T } | { ok: false; error: E }; // Helper functions isOk(result); // Type guard for success isErr(result); // Type guard for error unwrap(result); // Returns data or throws unwrapOr(result, defaultValue); // Returns data or default
await client.auth.login(password, totp?); await client.auth.logout(); await client.auth.check(); await client.auth.getSessions(); await client.auth.deleteSession(id); await client.auth.generateTotp(); await client.auth.createAppPassword();
await client.dns.getStatus(); await client.dns.enable(); await client.dns.disable(seconds?); await client.dns.setBlocking(enabled, timer?);
await client.stats.getSummary(); await client.stats.getUpstreams(); await client.stats.getTopDomains(count?, blocked?); await client.stats.getTopClients(count?, blocked?); await client.stats.getQueryTypes(); await client.stats.getRecentBlocked(); // Database variants available with .database prefix
await client.history.get(from?, until?); await client.history.getClients(from?, until?); // Database variants available
await client.queries.list({ length, from, until, client, domain, type, status }); await client.queries.getSuggestions();
// Denylist await client.domains.deny(domain, comment?); await client.domains.undeny(domain); await client.domains.getDenylist(); // Allowlist await client.domains.allow(domain, comment?); await client.domains.unallow(domain); await client.domains.getAllowlist(); // Regex variants await client.domains.denyRegex(pattern); await client.domains.allowRegex(pattern); // Generic CRUD await client.domains.list(type, kind); await client.domains.add(type, kind, domain, options?); await client.domains.update(type, kind, domain, options); await client.domains.remove(type, kind, domain); await client.domains.batchDelete(items); // Search await client.domains.search(domain);
await client.groups.list(); await client.groups.create(name, options?); await client.groups.update(name, options); await client.groups.delete(name); await client.groups.batchDelete(names);
await client.clients.list(); await client.clients.create(client, options?); await client.clients.update(client, options); await client.clients.delete(client); await client.clients.getSuggestions(); await client.clients.batchDelete(clients);
await client.lists.list(); await client.lists.add(address, options?); await client.lists.update(id, options); await client.lists.delete(id); await client.lists.batchDelete(ids);
await client.dhcp.getLeases(); await client.dhcp.deleteLease(ip);
await client.config.get(); await client.config.getSection(element); await client.config.update(changes); await client.config.addArrayItem(element, value); await client.config.removeArrayItem(element, value);
await client.info.getClient(); await client.info.getSystem(); await client.info.getHost(); await client.info.getFtl(); await client.info.getSensors(); await client.info.getDatabase(); await client.info.getVersion(); await client.info.getMetrics(); await client.info.getLogin(); await client.info.getMessages(); await client.info.getMessageCount(); await client.info.deleteMessage(id);
await client.network.getGateway(); await client.network.getRoutes(); await client.network.getInterfaces(); await client.network.getDevices(); await client.network.deleteDevice(id);
await client.actions.updateGravity(); await client.actions.restartDns(); await client.actions.flushLogs(); await client.actions.flushArp(); await client.actions.flushNetwork();
await client.logs.getDnsmasq(lines?); await client.logs.getFtl(lines?); await client.logs.getWebserver(lines?);
await client.teleporter.export(); await client.teleporter.import(data);
await client.getEndpoints();
import { PiholeErrorCode } from "@linx-systems/pihole-api-client"; const result = await client.dns.disable(); if (isErr(result)) { switch (result.error.code) { case PiholeErrorCode.Unauthorized: // Handle auth error break; case PiholeErrorCode.NetworkError: // Handle network error break; case PiholeErrorCode.Timeout: // Handle timeout break; default: console.error(result.error.message); } }
MIT