Developer documentation
TypeScript SDK
Last reviewed 31 August 2026
All docs
TypeScript and JavaScript#
There is no npm package yet. The decision API is one HTTP call, so the helper below is the whole integration: copy it into your project. Node 18+ (global fetch), Deno, Bun and edge runtimes. No dependencies.
// cain.ts
const CAIN_URL = process.env.CAIN_BASE_URL ?? "https://cainstudio.online";
export class CainRefused extends Error {
constructor(message: string, readonly decision?: any) { super(message); }
}
/** Ask CAIN whether a tool call may run. Resolves with the decision; never throws. */
export async function decide(tool: string, payload: object, agentId = "my-agent", runId?: string) {
try {
const r = await fetch(`${CAIN_URL}/fabric/decisions`, {
method: "POST",
headers: { "X-API-Key": process.env.CAIN_API_KEY ?? "", "content-type": "application/json" },
body: JSON.stringify({ path: `/tools/${tool}`, payload, agent_id: agentId, chain_id: runId }),
signal: AbortSignal.timeout(5000),
});
return await r.json();
} catch (e) {
return { verdict: "ERROR", blocked: true, detail: String(e) }; // fail closed
}
}
/** The only permit: an ALLOWED* verdict that is not blocked. */
export const allowed = (d: any) =>
d?.blocked === false && String(d?.verdict ?? "").startsWith("ALLOWED");
/** Wrap a tool: it runs only if CAIN allows this call with these arguments. */
export function guard<A extends object, R>(tool: string, fn: (args: A) => Promise<R>, agentId?: string) {
return async (args: A): Promise<R> => {
const d = await decide(tool, args, agentId);
if (!allowed(d)) throw new CainRefused(`${tool}: ${d.verdict ?? d.detail ?? "no verdict"} (decision ${d.decision_id ?? "-"})`, d);
return fn(args);
};
}
import { guard } from "./cain";
export const sendEmail = guard("send_email", async ({ to, subject }: { to: string; subject: string }) => {
// unchanged
});
A new agent has no trust history, so its first calls come back REQUIRE_APPROVAL: approve them in the console, or add a tool rule. The verdicts, the decision record and the approval flow are the same as in the Python SDK and the HTTP quickstart. The full API is in openapi.json.