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.
@zihin/agent-client is being published to npm. The API documented here is final.
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
});
| Option | Default | Description |
|---|---|---|
apiKey | — | Required. zhn_live_* / zhn_test_*. |
baseUrl | https://llm.zihin.ai | Override for a private deployment. |
timeoutMs | 120000 | Max total time for the whole call (headers and stream). |
maxRetries | 2 | Retries with exponential backoff. |
allowCustomBaseUrl | false | Allow a baseUrl outside the Zihin domain whitelist (SSRF opt-in). |
allowHttp | false | Allow http/localhost (development only). |
fetch | global | Inject 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.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);
}
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: the backend has no name
// uniqueness, so a repeated createTrigger would create duplicates.
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, 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
| Operation | Required role |
|---|---|
invokeAgent / streamAgent | any role |
createTrigger / getExecution | admin or editor |
A member key calling a restricted operation gets a ZihinAgentError with kind: 'auth'.
Errors
Every failure is a ZihinAgentError:
| Field | Meaning |
|---|---|
kind | config · auth · not_found · rate_limit · timeout · aborted · server · network · protocol · unknown |
status | HTTP status, when applicable |
retryable | whether a retry could succeed |
endpoint | "POST host/path" for debugging |
requestId | backend handle — share it with support to correlate |
Messages redact only secrets (key / Bearer / stack); request_id and trace_id are preserved.
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.
timeoutMs(default 120 s, matching the backend cap) bounds the whole call. - 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-Afteris honored. - Memory safety — stream limits (
maxStreamEventBytes1 MB,maxStreamTotalBytes64 MB) abort withkind: 'protocol'if exceeded. - Transport safety —
redirect: 'error'(a cross-host 3xx would leak the key) andcache: '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.
With allowCustomBaseUrl: true, a host that resolves to a private IP (DNS rebinding) will pass. Keep
the whitelist on in sensitive environments.