Skip to main content

Agent Client SDK

@zihin/agent-client is the official Node.js client for invoking Zihin agents. It is the shared core behind every Zihin connector (n8n, Activepieces, Zapier) and the reference client for your own backends. Zero platform dependencies.

npm install @zihin/agent-client

Requires Node ≥ 18 (uses global fetch / ReadableStream).

Create a client​

import { createClient } from '@zihin/agent-client';

const zihin = createClient({
apiKey: process.env.ZIHIN_API_KEY, // zhn_live_* — tenant is embedded
});
OptionDefaultDescription
apiKey—Required. zhn_live_* / zhn_test_*.
baseUrlhttps://llm.zihin.aiOverride for a private deployment.
timeoutMsper operationMax total time for the whole call (headers and stream). Defaults: 200 s stream · 260 s dispatch · 35 s short reads.
maxRetries2Retries with exponential backoff.
allowCustomBaseUrlfalseAllow a baseUrl outside the Zihin domain whitelist (SSRF opt-in).
allowHttpfalseAllow http/localhost (development only).
fetchglobalInject a fetch for runtimes without one (edge/serverless).
onRetry / onError—Observability hooks (no console).

Direct invocation​

Wait for the result. The client buffers the SSE stream and consolidates the response and metrics.

const res = await zihin.invokeAgent({
agentId: 'uuid-of-the-agent',
message: 'What is the sales forecast?',
sessionId: previousSessionId, // optional — continues the conversation
});

res.content; // final text
res.contentSource; // 'response' = the agent's final answer · 'tokens' = raw partial (stream ended early) — review before publishing
res.sessionId; // reuse on the next message for continuity
res.model; // effective model
res.usage; // { inputTokens, outputTokens, totalTokens, costUsd }
res.sources; // tool / retrieval sources, when present

Streaming​

Receive deltas as the agent thinks, calls tools, and answers:

for await (const chunk of zihin.streamAgent({ agentId, message: 'Hello' })) {
if (chunk.type === 'token') process.stdout.write(chunk.content);
if (chunk.type === 'tool') console.log('tool:', chunk.tool);
}
Session continuity

Pass the sessionId returned by a previous call to keep context across turns. Leave it empty to start a fresh conversation.

Asynchronous invocation (triggers)​

For long-running or decoupled work, provision a webhook bound to the agent and dispatch to it. The dispatch returns immediately; the result arrives at your callbackUrl (or via polling).

// Once — provision (or reuse) a webhook. Prefer findOrCreateTrigger to avoid creating
// duplicate triggers.
const { triggerId, webhookUrl } = await zihin.findOrCreateTrigger({
agentId,
name: 'Integration X',
mode: 'async',
callbackUrl: 'https://my-platform.com/webhook/zihin',
});

// N times — dispatch (responds 202; the result is delivered to the callback)
const { executionId } = await zihin.dispatchAgent({
triggerId,
payload: { message: 'Process this order' },
});

// Optional — poll the result instead of using a callback
const exec = await zihin.getExecution({ triggerId, executionId });

mode: 'sync' instead responds 200 + JSON immediately. Trigger lifecycle helpers: listTriggers, getTrigger, updateTrigger (read-merge-write — a partial update won't wipe the stored callback config), deleteTrigger.

List agents​

const agents = await zihin.listAgents(); // GET /api/v2/agents
// [{ id, name, commercialName, status, toolsCount, ... }]

Useful to populate an agent picker in a connector UI.

API key permissions​

OperationRequired role
invokeAgent / streamAgentany role
createTrigger / getExecutionadmin or editor

A member key calling a restricted operation gets a ZihinAgentError with kind: 'auth'.

Errors​

Every failure is a ZihinAgentError:

FieldMeaning
kindconfig · auth · trial_expired · no_subscription · quota_exceeded · not_found · rate_limit · timeout · aborted · server · network · protocol · unknown
statusHTTP status, when applicable
retryablewhether a retry could succeed
endpoint"POST host/path" for debugging
requestIdbackend handle — share it with support to correlate

Messages redact only secrets (key / Bearer / stack); request_id and trace_id are preserved. The three commercial kinds (trial_expired, no_subscription, quota_exceeded) are detected from the body of a 403/429 — an exhausted quota is not retried as if it were rate limiting. Network errors carry the root cause (ECONNREFUSED / ENOTFOUND / TLS…). Cancel any call with an AbortSignal via params.signal.

Robustness​

  • Timeout covers everything — headers and the entire body/stream. The per-operation defaults (200 s stream / 260 s dispatch / 35 s reads) sit above the backend's own turn budgets, so the client receives the server's structured outcome (e.g. an explicit turn timeout) instead of aborting blind; timeoutMs overrides them.
  • Retries never duplicate effects — idempotent reads retry on network/429/5xx; side-effecting POSTs (invokeAgent, dispatchAgent, createTrigger) retry only on 429/503 and never on a network error (the request may already have been processed). Retry-After is honored.
  • Memory safety — stream limits (maxStreamEventBytes 1 MB, maxStreamTotalBytes 64 MB) abort with kind: 'protocol' if exceeded.
  • Transport safety — redirect: 'error' (a cross-host 3xx would leak the key) and cache: 'no-store' on every request.

Security​

The client validates the base (and callback) URL by hostname against the Zihin whitelist. Set allowCustomBaseUrl: true only for a trusted private deployment — the API key is sent to that host.

warning

With allowCustomBaseUrl: true the built-in URL validation is disabled — enable it only for trusted, controlled deployments.