TypeScript SDK
TypeScript SDK
Section titled “TypeScript SDK”The official TypeScript SDK is published as @onceonly/onceonly-sdk on npm.
Install
Section titled “Install”npm i @onceonly/onceonly-sdkThe package requires Node.js 20+.
Create a client
Section titled “Create a client”import { OnceOnly } from "@onceonly/onceonly-sdk";
const client = new OnceOnly({ apiKey: process.env.ONCEONLY_API_KEY!});If you self-host, pass a custom baseUrl. The SDK accepts either the root URL or a URL that already includes /v1:
const client = new OnceOnly({ apiKey: process.env.ONCEONLY_API_KEY!, baseUrl: "http://localhost:8080"});You can also configure timeout, fail-open behavior for checkLock(), and retry/backoff:
const client = new OnceOnly({ apiKey: process.env.ONCEONLY_API_KEY!, timeoutMs: 5000, failOpen: true, maxRetries429: 3, retryBackoffSec: 0.5, retryMaxBackoffSec: 10});Idempotency (check-lock)
Section titled “Idempotency (check-lock)”const lock = await client.checkLock({ key: "payment:invoice:INV-123", ttl: 3600, meta: { invoice_id: "INV-123", amount_usd: 99.99 }});
if (lock.duplicate) { throw new Error(`Duplicate (firstSeenAt=${lock.firstSeenAt})`);}
await chargeCustomer();Async alias:
const lock = await client.checkLockAsync({ key: "payment:invoice:INV-123", ttl: 3600, meta: { invoice_id: "INV-123" }});AI Runs and Leases
Section titled “AI Runs and Leases”Keyed background run
Section titled “Keyed background run”const run = await client.ai.run({ key: "support_chat:abc123", ttl: 1800, metadata: { run_id: "run_support_001", agent_id: "support_bot" }});
console.log(run.status, run.leaseId, run.version);Wait for final result
Section titled “Wait for final result”const final = await client.ai.runAndWait({ key: "support_chat:abc123", ttl: 1800, metadata: { run_id: "run_support_001", agent_id: "support_bot" }, timeout: 120});
console.log(final.status, final.errorCode, final.result);Governed tool execution
Section titled “Governed tool execution”const res = await client.ai.runTool({ agentId: "support_bot", tool: "send_email", args: { to: "user@example.com", subject: "Hello" }, runId: "run_support_001", spendUsd: 0.001});
if (res.allowed) { console.log(res.decision, res.result);} else { console.log(res.decision, res.policyReason);}Local exactly-once execution with your own function
Section titled “Local exactly-once execution with your own function”const result = await client.ai.runFn( "email:welcome:user_123", async () => ({ sent: true }), { ttl: 300, metadata: { tool: "send_welcome_email" }, waitOnConflict: true, timeout: 60 });
console.log(result.status, result.result);Low-level lease helpers
Section titled “Low-level lease helpers”const lease = await client.ai.lease("support_chat:abc123", 1800, { chat_id: "abc123" });const status = await client.ai.status("support_chat:abc123");const result = await client.ai.result("support_chat:abc123");
await client.ai.extend("support_chat:abc123", String(lease.lease_id), 1800);await client.ai.complete("support_chat:abc123", String(lease.lease_id), { status: "done" });// orawait client.ai.fail("support_chat:abc123", String(lease.lease_id), "chat_failed");// optional cancel pathawait client.ai.cancel("support_chat:abc123", String(lease.lease_id), "manual_stop");Governance (policies, tools, agent controls)
Section titled “Governance (policies, tools, agent controls)”Policies
Section titled “Policies”const policy = await client.gov.upsertPolicy({ agent_id: "support_bot", allowed_tools: ["send_email", "create_ticket"], blocked_tools: ["delete_user"], max_actions_per_hour: 100, max_spend_usd_per_day: 50});
console.log(policy.agentId, policy.maxActionsPerHour);const templated = await client.gov.policyFromTemplate( "new_support_bot", "moderate", { blocked_tools: ["delete_user"] });
console.log(templated.agentId, templated.policy);const allPolicies = await client.gov.listPolicies();const onePolicy = await client.gov.getPolicy("support_bot");Agent kill switch and observability
Section titled “Agent kill switch and observability”await client.gov.disableAgent("support_bot", "incident_123");await client.gov.enableAgent("support_bot", "incident_resolved");
const logs = await client.gov.agentLogs("support_bot", 100);const metrics = await client.gov.agentMetrics("support_bot", "day");
console.log(logs.length, metrics.totalActions, metrics.blockedActions);Tools Registry CRUD
Section titled “Tools Registry CRUD”const tool = await client.gov.createTool({ name: "send_email", scope_id: "global", url: "https://your-domain.com/tools/send-email", auth: { type: "hmac_sha256", secret: process.env.TOOL_SECRET! }, timeout_ms: 15000, max_retries: 2, enabled: true, description: "Send transactional email"});
console.log(tool.name, tool.enabled);List, fetch, toggle, and delete:
const tools = await client.gov.listTools("global");const oneTool = await client.gov.getTool("send_email", "global");const disabled = await client.gov.toggleTool("send_email", false, "global");const deleted = await client.gov.deleteTool("send_email", "global");See also: Policy Templates | Implementing Tool Backends
Account, Usage, Events, and Metrics
Section titled “Account, Usage, Events, and Metrics”const profile = await client.me();const prefs = await client.updateNotifications({ emailNotificationsEnabled: true, toolErrorNotificationsEnabled: true, runFailureNotificationsEnabled: false});
const makeUsage = await client.usage("make");const aiUsage = await client.usage("ai");const allUsage = await client.usageAll();const summary = await client.metrics("2026-04-01", "2026-04-30");
console.log(profile.email);console.log(makeUsage.usage, aiUsage.usage, allUsage.plan);console.log(summary);Structured run events:
const event = await client.postEvent({ runId: "run_support_001", type: "tool_call", status: "start", step: "step_1", tool: "send_email", agentId: "support_bot"});
const timeline = await client.getRunTimeline("run_support_001", 200, 0);const recentEvents = await client.events(20, 0);
console.log(event.event_id);console.log(timeline.total);console.log(recentEvents.length ?? recentEvents.items?.length ?? 0);The current SDK wraps:
me()/meAsync()updateNotifications()/updateNotificationsAsync()usage()/usageAsync()usageAll()/usageAllAsync()metrics()/metricsAsync()postEvent()/postEventAsync()getRunTimeline()/getRunTimelineAsync()events()/eventsAsync()
The current REST API also has GET /v1/runs, but there is no dedicated listRuns() helper in the SDK yet.
Async Aliases
Section titled “Async Aliases”TypeScript SDK methods are async by default. It also exposes explicit async aliases for parity with Python naming:
checkLockAsync()aiRunAsync()/aiRunAndWaitAsync()meAsync()/updateNotificationsAsync()usageAsync()/usageAllAsync()/metricsAsync()eventsAsync()/postEventAsync()/getRunTimelineAsync()client.ai.runAsync()/runAndWaitAsync()/runToolAsync()/runFnAsync()client.ai.leaseAsync()/extendAsync()/completeAsync()/failAsync()/cancelAsync()client.gov.*Async()variants for all governance methods
Client Aliases
Section titled “Client Aliases”The OnceOnly client exposes convenience aliases for AI runs:
aiRun(...)/aiRunAsync(...)aiRunAndWait(...)/aiRunAndWaitAsync(...)
They delegate to client.ai.run(...) and client.ai.runAndWait(...).
Decorators
Section titled “Decorators”The package exports two decorator helpers from @onceonly/onceonly-sdk:
idempotent(client, fn, opts)forcheck-lockidempotentAi(client, fn, opts)for exactly-once local execution with AI leases
import { OnceOnly, idempotent, idempotentAi } from "@onceonly/onceonly-sdk";
const client = new OnceOnly({ apiKey: process.env.ONCEONLY_API_KEY! });
const chargeInvoice = idempotent( client, async (invoiceId: string) => ({ status: "paid", invoiceId }), { keyPrefix: "payments", ttl: 3600, onDuplicate: async (invoiceId) => ({ status: "duplicate", invoiceId }) });
const handleSupportChat = idempotentAi( client, async (chatId: string, messages: Array<{ role: string; content: string }>) => { return { status: "resolved", messages: messages.length }; }, { keyFn: (chatId) => `support_chat:${chatId}`, ttl: 1800, metadataFn: (chatId, messages) => ({ chat_id: chatId, message_count: messages.length }) });
const out = await handleSupportChat("chat_123", [{ role: "user", content: "Help" }]);console.log(out.status, out.result);idempotentAi returns an AiResult, not the raw function output, because completion is normalized through the lease/result flow.
See also: AI Run API | Runs & Events API | Usage API