Start free

TypeScript — @hesohq/sdk

The @hesohq/sdk package is a thin TypeScript wrapper over @hesohq/core: gating helpers, a cloud client, a proxy, and the wire types. It does no crypto of its own. The Rust core does the verifying, so a verdict here matches one from Python or the browser exactly.

Use this package when you verify receipts and talk to the control plane from Node. It does not capture or gate your own agent — that is the Python SDK with its decorators. In Node you verify receipts (and construct or redact them directly with @hesohq/core) and push them to the cloud.

Install

Add the package with your package manager. It targets Node 18 or newer and ships as CommonJS, so it imports cleanly from both require and ESM interop.

bash
pnpm add @hesohq/sdk

Then import the helpers and types you need from the single entry point:

gate.ts
import {  configure,  gate,  assertGate,  isDecisionAllowed,  shortHash,  wrap,  pullPolicy,  pushReceipt,  pushReceipts,  openApproval,  pollApproval,  waitForApproval,  submitApprovalToken,} from "@hesohq/sdk"import type { ActionReceipt, DecisionPath, TrustLevel } from "@hesohq/sdk"
Crypto lives in the core

Every verify, sign, and redact call runs in @hesohq/core, the native Rust addon. Ed25519 and BLAKE3 are never re-implemented in JavaScript — the SDK is wiring and types around the one Rust source of truth.

Configuration

Call configure once at startup, before any cloud call. It sets the API key and endpoint the client uses for every request. See Authentication for where the key comes from.

configure()function

Set the cloud credentials once at startup. Stores a HesoCoreConfig the cloud client reads on every request. Call it before pullPolicy, pushReceipt, or any approval call.

ts
configure(apiKey: string, endpoint: string): void

Parameters

apiKeystringrequired
Your team’s API key, sent as Authorization: Bearer <key>.
endpointstringrequired
The base URL of your control plane.

Returns

Nothing. It mutates module-level config in place.

Example

startup.ts
import { configure } from "@hesohq/sdk" configure(process.env.HESO_API_KEY!, process.env.HESO_ENDPOINT!)
Configure before you call

The cloud functions read the config at call time. If you invoke them before configure, the request has no key or endpoint and fails. The local gating helpers below do not need it — they verify offline.

Gating

These helpers verify a receipt locally and turn the verdict into a decision. They run entirely offline through @hesohq/core and follow the same seven-gate verify order as every other surface. minTrust defaults to "L0"; pass "L1" to require a human co-signature.

gate()function

Verify a receipt and return a structured result. Never throws on a failed verdict — it reports it. Use this when you want to branch on the outcome.

ts
gate(receiptBytes: Buffer | string, minTrust?: TrustLevel = "L0"): GateResult

Parameters

receiptBytesBuffer | stringrequired
The receipt JSON, as a string or buffer.
minTrustTrustLevel= "L0"
The minimum re-derived trust level required to allow.

ReturnsGateResult

A GateResult of { allowed: boolean; trustLevel: TrustLevel | null; verdict: string }. verdict is the resolved verdict state (e.g. valid, hash_mismatch, trust_mismatch); trustLevel is null when verification fails before trust is re-derived.

Example

gate.ts
import { gate } from "@hesohq/sdk"import { readFile } from "node:fs/promises" const bytes = await readFile("receipt.json", "utf8")const result = gate(bytes, "L1") if (!result.allowed) {  console.error("rejected:", result.verdict, result.trustLevel)}

assertGate()function

The throwing form of gate. Returns normally when the receipt verifies and meets minTrust; throws otherwise. Use it as a guard clause before a sensitive code path.

ts
assertGate(receiptBytes: Buffer | string, minTrust?: TrustLevel = "L0"): void

Parameters

receiptBytesBuffer | stringrequired
The receipt JSON, as a string or buffer.
minTrustTrustLevel= "L0"
The minimum trust level required to pass.

Returns

Nothing on success; throws when the receipt is not allowed.

Throws

An error carrying the resolved verdict when verification fails or the trust level is below minTrust.

Example

fulfill.ts
import { assertGate } from "@hesohq/sdk" // Throws unless the receipt verifies AND meets the minimum trust level.assertGate(receiptBytes, "L1") // Past this line the action is provably authorized and human-approved.fulfillOrder(order)

isDecisionAllowed()function

Check whether a receipt’s recorded decision path is one you accept. This reads the policy outcome already embedded in the receipt; it does not re-verify signatures — pair it with gate when authenticity matters.

ts
isDecisionAllowed(receipt: ActionReceipt, allowedPaths: DecisionPath[]): boolean

Parameters

receiptActionReceiptrequired
A parsed receipt object.
allowedPathsDecisionPath[]required
The decision paths you treat as acceptable, e.g. ["allow", "redact"].

Returnsboolean

true when the receipt’s decision_path is in allowedPaths.

Example

decision.ts
import { isDecisionAllowed } from "@hesohq/sdk"import type { ActionReceipt } from "@hesohq/sdk" const receipt: ActionReceipt = JSON.parse(receiptBytes) if (isDecisionAllowed(receipt, ["allow", "redact"])) {  // The policy let this action through (possibly after redaction).}

shortHash()function

Render a long hex hash in a short display form: the first eight characters, optionally namespaced with a prefix. Handy for logs and UI without re-deriving anything.

ts
shortHash(hex: string, prefix?: string): string

Parameters

hexstringrequired
A hex string such as an action_hash or a chain hash.
prefixstring
An optional label rendered as prefix:first8.

Returnsstring

The short form — first8, or prefix:first8 when a prefix is given.

Example

display.ts
import { shortHash } from "@hesohq/sdk" shortHash("9f2c4e7a1b08d3f6…")            // "9f2c4e7a"shortHash("9f2c4e7a1b08d3f6…", "action")  // "action:9f2c4e7a"

The proxy

wrap returns a stand-in for any client that behaves just like the original. After each method call it checks the result for a __heso_receipt field, and if it finds one, gates it. This sets a minimum trust level across every method on the client without changing each call site.

wrap()function

Wrap a client so it gates every method result carrying a __heso_receipt. The returned object has the same type as the original, so you can swap it in with no other changes.

ts
wrap<T extends object>(client: T, options?: WrapOptions): T

Parameters

clientTrequired
The object to wrap. Each of its methods is intercepted; results without a __heso_receipt pass through unchanged.
optionsWrapOptions
Per-wrap configuration; documented below.

ReturnsT

A proxy of the same type T as the wrapped client.

Example

wrap.ts
import { wrap, configure } from "@hesohq/sdk" configure(process.env.HESO_API_KEY!, process.env.HESO_ENDPOINT!) const gated = wrap(paymentsClient, {  minTrust: "L1",  onGateFail: (method, verdict) => {    console.error(`${method} blocked by gate: ${verdict}`)    return false // do not let the call result through  },  onReceipt: (method, receiptJson) => {    void pushReceipt(JSON.parse(receiptJson))  },}) // Each method result is gated by reading its __heso_receipt field.await gated.createTransfer({ amountUsd: 4200, payee: "Globex LLC" })

WrapOptions controls the trust floor and the two hooks fired around gating:

FieldTypeBehaviour
minTrustTrustLevelThe minimum trust level a result’s receipt must reach to pass the gate.
onGateFail(method, verdict) => boolean | Promise<boolean>Called when a result fails the gate. Return true to let the result through anyway, false to suppress it.
onReceipt(method, receiptJson) => void | Promise<void>Called with the raw receipt JSON for every gated result — a natural place to push it to the cloud.

Cloud client

These functions talk to the control plane over HTTP. They all read the config set by configure, so call that first. For the underlying endpoints and request shapes, see the cloud API reference.

The server re-verifies

When you push a receipt, the control plane runs the full verify order again before accepting it — the same math your gate call ran locally. A receipt that fails comes back with accepted: false and a rejectionReason.

pullPolicy()function

Fetch the active policy bundle for a team. Maps to GET /v1/teams/{teamId}/policy.

ts
pullPolicy(teamId: string): Promise<PolicyBundle>

Parameters

teamIdstringrequired
The team whose policy you want.

ReturnsPromise<PolicyBundle>

A PolicyBundle of { version: string; rules: PolicyRule[]; fetchedAt: string }. See policy files for the rule shape.

Example

policy.ts
import { pullPolicy } from "@hesohq/sdk" const bundle = await pullPolicy("team_19")console.log(bundle.version, bundle.rules.length, bundle.fetchedAt)

pushReceipt()function

Submit one receipt to the control plane. Maps to POST /v1/receipts; the server re-verifies before accepting.

ts
pushReceipt(receipt: ActionReceipt): Promise<OutboxPushResult>

Parameters

receiptActionReceiptrequired
The receipt to submit.

ReturnsPromise<OutboxPushResult>

An OutboxPushResult of { receiptId; accepted; rejectionReason? }. Check accepted before treating the action as recorded.

Example

push.ts
import { pushReceipt } from "@hesohq/sdk"import type { ActionReceipt } from "@hesohq/sdk" const receipt: ActionReceipt = JSON.parse(receiptBytes)const result = await pushReceipt(receipt) // server re-verifies before accepting if (!result.accepted) {  console.error("rejected:", result.rejectionReason)}

pushReceipts()function

Submit a batch of receipts in one round trip. Maps to POST /v1/receipts/batch.

ts
pushReceipts(receipts: ActionReceipt[]): Promise<OutboxPushResult[]>

Parameters

receiptsActionReceipt[]required
The receipts to submit together.

ReturnsPromise<OutboxPushResult[]>

An OutboxPushResult[] — one result per submitted receipt, in order.

Example

batch.ts
import { pushReceipts } from "@hesohq/sdk" const results = await pushReceipts(pending)const rejected = results.filter((r) => !r.accepted)

openApproval()function

Open a human approval for a receipt that policy routed to a person. Maps to POST /v1/approvals.

ts
openApproval(req: OpenApprovalRequest): Promise<OpenApprovalResult>

Parameters

reqOpenApprovalRequestrequired
{ receipt: ActionReceipt; routingHint?: string } — the receipt to approve and an optional routing hint.

ReturnsPromise<OpenApprovalResult>

An OpenApprovalResult of { approvalId; token?; expiresAt }.

Example

approval.ts
import { openApproval } from "@hesohq/sdk"import type { ActionReceipt } from "@hesohq/sdk" const receipt: ActionReceipt = JSON.parse(receiptBytes)const opened = await openApproval({ receipt, routingHint: "finance" })console.log(opened.approvalId, opened.expiresAt)

pollApproval()function

Read the current state of an approval once. Maps to GET /v1/approvals/{approvalId}.

ts
pollApproval(approvalId: string): Promise<PollApprovalResult>

Parameters

approvalIdstringrequired
The id returned by openApproval.

ReturnsPromise<PollApprovalResult>

A PollApprovalResult of { outcome: ApprovalOutcome; approval?: Approval }. The approvalcarries the approver’s Ed25519 co-signature once a decision lands.

Example

poll.ts
import { pollApproval } from "@hesohq/sdk" const { outcome, approval } = await pollApproval(approvalId) if (outcome === "approved" && approval) {  // approval carries the approver's Ed25519 co-signature.}

waitForApproval()function

Poll an approval until it resolves, then return the Approval. A convenience loop over pollApproval.

ts
waitForApproval(approvalId: string, options?: { pollIntervalMs?: number; timeoutMs?: number }): Promise<Approval>

Parameters

approvalIdstringrequired
The approval to wait on.
options.pollIntervalMsnumber= 2000
How often to poll, in milliseconds.
options.timeoutMsnumber= 300000
How long to wait before giving up, in milliseconds.

ReturnsPromise<Approval>

The resolved Approval.

Throws

An error when the timeout elapses before the approval resolves.

Example

wait.ts
import { waitForApproval } from "@hesohq/sdk" // Polls every 2 s, throws after 5 min by default.const approval = await waitForApproval(approvalId, {  pollIntervalMs: 3000,  timeoutMs: 120000,})

submitApprovalToken()function

Redeem the one-time token a reviewer signed with their device-held key. Maps to POST /v1/approvals/{approvalId}/token. The cloud holds no signing key — the approver’s signature is theirs alone.

ts
submitApprovalToken(approvalId: string, token: string): Promise<Approval>

Parameters

approvalIdstringrequired
The approval being decided.
tokenstringrequired
The signed approval token.

ReturnsPromise<Approval>

The completed Approval, including the approver’s co-signature.

Example

submit.ts
import { submitApprovalToken } from "@hesohq/sdk" // Redeem the one-time token a reviewer signed with their device-held key.const approval = await submitApprovalToken(approvalId, token)

Types

The package re-exports the wire types so you can type receipts, policies, and approvals without a second dependency. These mirror the JSON on the network exactly; for a field-by-field breakdown of the receipt envelope see the receipt schema.

Among the exports are Verb, DecisionPath, TrustLevel, ConditionOp, ActionReceipt, ActionContent, ActionDetail, PolicyOutcome, SignatureEntry, PolicyRule, and Approval, among others.

TypeWhat it is
ActionReceiptThe signed envelope — see receipt schema.
DecisionPathallow, block, redact, or require_approval.
TrustLevelL0 (operator-signed) or L1 (human co-signed).
VerbThe action verb captured for a gated call — see actions & verbs.
ConditionOpA policy condition operator — see conditions & operators.
PolicyRuleOne rule from a policy file.
ApprovalA resolved human approval, including the approver’s co-signature.
What this SDK does — and does not — do

@hesohq/sdkverifies receipts and moves them to and from the cloud. It does not capture your agent’s actions: to gate, sign, and chain your own calls, use the Python SDK. A passing verdict proves the action was authorized under a known policy and the bytes are untampered — what was authorized, not whether the action succeeded downstream.

Next steps