Client SDK.

@token-holder/client-sdk — headless TypeScript, zero native deps, ~14 KB.

The SDK speaks the Token Holder HTTP protocol and signs every request when an Ed25519 key is present. It works in Node, Bun, Deno, edge runtimes (Cloudflare Workers, Vercel Edge), and browsers when a bearer token is provided.

Install

# During private alpha, from a local tarball:
pnpm add ./token-holder-client-sdk-0.8.x.tgz

Create a client

import { createClient } from "@token-holder/client-sdk";

const th = createClient({
  consumerId: "my-app",
  // Optional — if omitted, the request is unsigned (Option A claim).
  // Provide an Ed25519 key to upgrade to Option B signed requests.
  agentId: crypto.randomUUID(),
  agentName: "research-bot-v1",
  agentPrivateKey: process.env.TH_AGENT_PRIVATE_KEY,
});

Grant checks

Ask before you act. The answer is cached; the check is chained.

const check = await th.checkGrant({
  dataType:   "obsidian_vault",
  permission: "read",
  path:       "Projects/BTO/readme.md",
});

if (check.allowed) {
  const file = await th.vault.read("Projects/BTO/readme.md");
  // do something with file.content
}

Your own audit trail

Every consumer can pull their own chain slice without admin scope — so the customer sees what your agent actually did against their data.

const summary = await th.my.usage.summary();
// { consumer_id, chained_rows, last_row_hash, first_ts, last_ts }

const history = await th.my.usage.history({ limit: 50 });
// [{ timestamp, usage_type, data_type, path, agent_id, agent_verified, row_hash, ... }]

Mint an agent identity

For short-lived sessions (a single CLI run, a browser tab) — generate a keypair, register its public half, and let the SDK sign transparently:

import { registerAgent } from "@token-holder/client-sdk";

const { agentId, privateKeyB64, publicKeyB64 } = await registerAgent({
  consumerId: "my-app",
  agentName:  "Interactive session",
});

// Pass these to createClient() for signed requests.
// Revoke a single agent without touching the consumer token.

Request signing, on the wire

When an agent private key is configured, every request carries these headers:

X-TH-Agent-Id:        <uuid>
X-TH-Agent-Timestamp: <unix-ms, ±60s window>
X-TH-Agent-Nonce:     <per-request random>
X-TH-Agent-Signature: <base64 Ed25519 over canonical form>

The middleware verifies, stamps agent_verified=true on the chain row, and strips any client-forged X-TH-Agent-Verified header before handlers run.

Related Audit chain covers how signed rows render in the wallet and what compliance export looks like.