Developer documentation

TypeScript SDK

Last reviewed 31 August 2026

QuickstartCLI referencePython SDKTypeScript SDKMCPIntegrationsPoliciesActionProofEvidenceCAIN TraceConformanceTroubleshootingDeveloper portalMarketplaceFree tierBenchmarksArchitectureCAIN IdentityCAIN ControlCAIN BudgetCAIN GovernanceCAIN MemorySelf-Hosted MCPGateCAIN PrivateCAIN TrajectoryCAIN Agent SecurityCAIN Drift7-Moat ArchitectureChangelog

TypeScript SDK

npm install @cain/sdk

Node 18+. No runtime dependencies -- it uses global fetch, so it will not drag

a transitive tree into a project that is already fighting its own.

Mirrors the Python SDK deliberately: same canonical decision, same five verdicts,

same invariant. Two SDKs that disagreed about what UNKNOWN means would be worse

than having one.

Declarative: trust

import { trust } from "@cain/sdk";

export const sendEmail = trust(
  { action: "send_email", resource: "customer_inbox" },
  async (to: string, subject: string, body: string) => {
    ...                       // unchanged
  },
);

Throws NotAuthorized before the body runs unless the verdict is ALLOW.

Imperative: verify

import { verify } from "@cain/sdk";

const decision = await verify({
  action: "delete_customer",
  resource: customerId,
});
if (decision.allowed) {
  await deleteCustomer(customerId);
} else {
  console.warn("refused:", decision.explain());
}

verify() never rejects. Timeout, DNS failure and non-2xx all resolve to a

decision with verdict ERROR.

guard() is the same call but throws unless ALLOW.

The Decision

decision.verdict          // "ALLOW" | "DENY" | "REQUIRE_APPROVAL" | "UNKNOWN" | "ERROR"
decision.allowed          // true only for ALLOW
decision.determinate      // false for UNKNOWN and ERROR
decision.deniedBy         // string[] -- stages that refused and were enforcing
decision.shadowDenials    // string[] -- refused but not enforcing
decision.policyVersion
decision.explain()

There is deliberately no decision.denied. if (!decision.denied) would treat

UNKNOWN as permission.

Options

trust(
  {
    action: "refund",
    resource: "billing",
    includeArgs: false,               // default: do not ship arguments
    onUnauthorized: "throw",          // or "returnUndefined". No "proceed".
  },
  fn,
);

Configuration

Reads CAIN_ENDPOINT, CAIN_API_KEY, CAIN_AGENT and CAIN_STRICT from the

environment -- which is what cain run sets, so a wrapped program needs no code

change. Or in code:

import { configure } from "@cain/sdk";
configure({ endpoint: "https://fabric.internal", strict: true });