Node.js SDK
Complete reference for @backstop/client — BackstopClient, all methods, error types, and TypeScript patterns.
@backstop/client is the TypeScript SDK for interacting with the Backstop gateway. It wraps the JSON-RPC API with a typed interface, automatic retries, and structured error types.
Installation
npm install @backstop/client
# or
yarn add @backstop/client
# or
pnpm add @backstop/clientRequirements: Node.js 18+, TypeScript 5+ (for full type support)
Initialization
import { BackstopClient } from "@backstop/client";
const backstop = new BackstopClient({
url: process.env.BACKSTOP_URL ?? "http://localhost:8080",
token: process.env.BACKSTOP_TOKEN ?? "dev-token",
agentId: process.env.BACKSTOP_AGENT_ID ?? "my-agent",
timeoutMs: 30_000, // optional, default 30s
});Constructor options
| Parameter | Type | Description |
|---|---|---|
urlREQUIRED | string | Base URL of the Backstop gateway. Include the port but not a path. |
tokenREQUIRED | string | Bearer token for authentication. Must have at minimum query:execute scope for most operations. |
agentIdREQUIRED | string | Stable identifier for this agent. Used in audit logs and for quarantine tracking. Should be consistent across sessions. |
timeoutMsOPTIONALdefault: 30000 | number | Request timeout in milliseconds. |
dbUrlOPTIONAL | string | PostgreSQL connection string to use as default for all queries. Can be overridden per-call. |
executeQuery()
Executes SQL against the database, subject to Backstop's policy.
const result = await backstop.executeQuery("SELECT COUNT(*) FROM users");
console.log(result.status); // "executed"
console.log(result.row_count); // 50000
console.log(result.rows); // [{ count: "50000" }]
console.log(result.risk_level); // "SAFE"const result = await backstop.executeQuery("DROP TABLE users");
// If policy requires approval:
console.log(result.status); // "approval_required"
console.log(result.approval_id); // "appr_4f9e2c1a"
console.log(result.snapshot_id); // "snap_a3f9"
console.log(result.risk_level); // "CRITICAL"Options
| Parameter | Type | Description |
|---|---|---|
agentIdOPTIONAL | string | Override the default agentId for this call. |
dbUrlOPTIONAL | string | Override the default database URL for this call. |
snapshotIdOPTIONAL | string | Provide a snapshot ID when resubmitting an approved query. |
throwOnPolicyViolationOPTIONALdefault: false | boolean | If true, throws BackstopPolicyBlockedError instead of returning a blocked result object. |
timeoutMsOPTIONAL | number | Per-call timeout override. |
signalOPTIONAL | AbortSignal | AbortSignal for request cancellation. |
analyzeQuery()
Classifies SQL without executing it. Useful for pre-flight checks.
const analysis = await backstop.analyzeQuery("DELETE FROM payments");
console.log(analysis.risk_level); // "CRITICAL"
console.log(analysis.safety_metadata.operation); // "DELETE"
console.log(analysis.safety_metadata.table); // "payments"
console.log(analysis.safety_metadata.estimated_affected_rows); // 432118query()
Alias for executeQuery(). Provided for ergonomics in simple use cases.
const result = await backstop.query("SELECT * FROM users LIMIT 10");Approval and admin methods
// List pending approvals (requires approval:read scope)
const pending = await backstop.getPendingApprovals();
// Approve a query
await backstop.approve("appr_4f9e2c1a");
// Deny a query
await backstop.deny("appr_4f9e2c1a");
// Emergency pause (requires admin:* scope)
await backstop.pause("Suspicious agent activity detected");
// Resume after pause
await backstop.resume("Incident resolved");
// Check admin status
const status = await backstop.getAdminStatus();
console.log(status.paused); // true/falseMetadata methods
// List snapshots for a table
const snapshots = await backstop.listSnapshots({ table: "users" });
// Get audit events
const events = await backstop.getAudit({ agentId: "cursor-local", risk: "CRITICAL" });
// Get alerts
const alerts = await backstop.getAlerts();
// Get health status
const health = await backstop.getHealth();Error types
import {
BackstopAuthError,
BackstopNetworkError,
BackstopTimeoutError,
BackstopPolicyBlockedError,
BackstopApprovalRequiredError,
BackstopRecoveryNotReadyError,
} from "@backstop/client";
try {
await backstop.executeQuery("DROP TABLE users", {
throwOnPolicyViolation: true,
});
} catch (err) {
if (err instanceof BackstopPolicyBlockedError) {
// Query was permanently blocked
console.log(err.riskLevel); // "CRITICAL"
console.log(err.policyReason); // "DROP TABLE blocked by policy"
} else if (err instanceof BackstopApprovalRequiredError) {
// Query needs operator approval — not an error per se
console.log(err.approvalId); // "appr_4f9e2c1a"
console.log(err.snapshotId); // "snap_a3f9"
} else if (err instanceof BackstopRecoveryNotReadyError) {
// CRITICAL op approved but snapshot is stale or sidecar is down
console.log(err.message);
} else if (err instanceof BackstopAuthError) {
// 401 — invalid or expired token
} else if (err instanceof BackstopTimeoutError) {
// Request exceeded timeoutMs
} else if (err instanceof BackstopNetworkError) {
// Gateway unreachable — fail closed in your application
}
}