TypeScript SDK for 1Claw Vault — HSM-backed secret management for AI agents and humans.
npm install @1claw/sdkNote: This package is ESM-only. Use it in an ESM context (e.g. "type": "module" in package.json, or .mjs files). Next.js and other bundlers handle ESM natively. Running scripts with plain node or tsx may require an ESM setup to avoid ERR_PACKAGE_PATH_NOT_EXPORTED.
import { createClient } from "@1claw/sdk";
const client = createClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "ocv_...", // auto-exchanges for a JWT
});
// List vaults
const { data } = await client.vault.list();
console.log(data?.vaults);
// Store a secret
await client.secrets.set("vault-id", "OPENAI_KEY", "sk-...", {
type: "api_key",
});
// Retrieve a secret
const secret = await client.secrets.get("vault-id", "OPENAI_KEY");
console.log(secret.data?.value);API contract: This SDK is built from the OpenAPI 3.1 spec. The canonical spec is published as @1claw/openapi-spec (YAML/JSON). Types are generated with npm run generate (openapi-typescript ../openapi-spec/openapi.yaml). Run generate after spec changes, then npm run build. Shapes such as LlmTokenBillingStatus (including optional credit_balance and billing_cycle_usage.metered_lines) come from the generated api-types.ts. For a full endpoint list, see the API reference or the spec.
Agent JWTs issued by POST /v1/auth/agent-token may include optional claims such as shroud_enabled and shroud_config (when Shroud is on for that agent). Those are for services like Shroud that verify the JWT; the TypeScript SDK uses the token for Vault API calls and does not need to read shroud_config unless you build custom tooling.
The SDK supports three authentication modes:
// 1. User API key (auto-authenticates)
const client = createClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "ocv_...",
});
// 2. Agent with API key (auto-authenticates as agent)
const agent = createClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "ocv_...",
agentId: "agent-uuid",
});
// 3. Pre-authenticated JWT
const authed = createClient({
baseUrl: "https://api.1claw.xyz",
token: "eyJ...",
});
// Or authenticate manually:
await client.auth.login({ email: "...", password: "..." });
await client.auth.agentToken({ agent_id: "...", api_key: "..." });
await client.auth.google({ id_token: "..." });
// Password reset (public; no Bearer token — use a client without stored JWT)
await client.auth.forgotPassword({ email: "user@example.com" });
await client.auth.resetPassword({ token: "...", new_password: "..." });
// Set password (platform OIDC users who don't have one)
await client.auth.setPassword({ password: "...", password_confirm: "..." });
// Change email (sends verification code to new address)
await client.auth.changeEmail({ new_email: "new@example.com" });
await client.auth.verifyEmailChange({ code: "123456" });| Resource | Methods |
|---|---|
client.vault |
create, get, list, delete, enableMpc, disableMpc |
client.secrets |
set, get, delete, list, rotate |
client.access |
grantHuman, grantAgent, update, revoke, listGrants |
client.agents |
enroll (also AgentsResource.enroll(baseUrl, …) static), create, getSelf, get, list, update, delete, rotateKey, submitTransaction, signTransaction, getTransaction, listTransactions, simulateTransaction, simulateBundle, sign |
client.chains |
list, get, adminList, create, update, delete |
client.sharing |
create, access, listOutbound, listInbound, accept, decline, revoke |
client.approvals |
request, list, approve, deny, check, subscribe |
client.billing |
usage, history, llmTokenBilling, subscribeLlmTokenBilling, disableLlmTokenBilling (LLM token billing / Stripe AI Gateway) |
client.audit |
query |
client.org |
listMembers, getAgentKeysVault, updateMemberRole, removeMember |
client.auth |
login, signup, agentToken, apiKeyToken, google, changePassword, setPassword, changeEmail, verifyEmailChange, forgotPassword, resetPassword, exportData, exchangeFederatedToken, logout, getMe, updateMe, deleteMe |
client.apiKeys |
create, list, revoke |
client.treasury |
create, list, get, update, delete, addSigner, removeSigner, requestAccess, listAccessRequests, approveAccess, denyAccess |
client.treasuryWallets |
generateWallets, listWallets, getWallet, exportWallet, rotateWallet, deactivateWallet |
client.signingKeys |
create, list, rotate, deactivate, export |
client.platform |
createApp, listApps, getApp, updateApp, deleteApp, createTemplate, listTemplates, upsertUser, listUsers, bootstrapUser, claimPreview, claimRedeem, listConnectedApps, disconnectApp |
Platform bootstrap response: bootstrapUser() returns a summary object containing agent_api_key (one-time, not retrievable later) and signing_keys[] (with chain, address, and public_key for each provisioned key).
| client.x402 | getPaymentRequirement, pay, verifyReceipt, withPayment |
Agent create response: agents.create() returns { agent: AgentResponse, api_key?: string }. The api_key is only present for auth_method: "api_key" and is shown once — use data.agent.id and data.api_key from the response.
Access grants: grantAgent(vaultId, agentId, permissions, options?) — positional args; options include secretPathPattern, conditions, expires_at.
All methods return a typed envelope:
interface OneclawResponse<T> {
data: T | null;
error: { type: string; message: string; detail?: string } | null;
meta?: { status: number };
}Check error before accessing data:
const res = await client.secrets.get("vault-id", "key");
if (res.error) {
console.error(res.error.type, res.error.message);
} else {
console.log(res.data.value);
}The SDK exports a typed error hierarchy for catch-based flows:
| Error | HTTP Status | Description |
|---|---|---|
OneclawError |
any | Base error class |
AuthError |
401, 403 | Authentication/authorization failure |
PaymentRequiredError |
402 | x402 payment required (includes paymentRequirement) |
ResourceLimitExceededError |
403 | Tier limit reached (vaults, agents, secrets) |
ApprovalRequiredError |
403 | Human approval gate triggered |
NotFoundError |
404 | Resource not found |
RateLimitError |
429 | Rate limit exceeded |
ValidationError |
400 | Invalid request body |
ServerError |
500+ | Server-side failure |
Agents can be granted the ability to sign and broadcast on-chain transactions through the Intents API. Private keys stay in the HSM — the agent submits intent, the API signs and broadcasts.
Toggle intents_api_enabled when creating or updating an agent:
// Register an API key agent with Intents API access (default auth_method)
const { data } = await client.agents.create({
name: "defi-bot",
auth_method: "api_key", // "api_key" | "mtls" | "oidc_client_credentials"
scopes: ["keys/*", "api-keys/*"],
intents_api_enabled: true,
});
// data.api_key is only returned for auth_method: "api_key"
// All agents automatically receive an Ed25519 SSH keypair (data.agent.ssh_public_key)
// Register an mTLS agent (no API key returned)
const { data: mtlsAgent } = await client.agents.create({
name: "mtls-bot",
auth_method: "mtls",
client_cert_fingerprint: "sha256-fingerprint-hex",
});
// Register an OIDC agent (no API key returned)
const { data: oidcAgent } = await client.agents.create({
name: "oidc-bot",
auth_method: "oidc_client_credentials",
oidc_issuer: "https://accounts.google.com",
oidc_client_id: "your-client-id",
});
// Or enable it later
await client.agents.update(agentId, {
intents_api_enabled: true,
});
// Check an agent's proxy status
const agent = await client.agents.get(agentId);
console.log(agent.data?.intents_api_enabled); // trueOnce intents_api_enabled is true and the agent has a signing key stored in an accessible vault, the agent can submit transaction intents:
const txRes = await client.agents.submitTransaction(agentId, {
to: "0x000000000000000000000000000000000000dEaD",
value: "0.01", // ETH
chain: "base",
// Optional: data, signing_key_path, nonce, gas_price, gas_limit
});
console.log(txRes.data?.status); // "signed"
console.log(txRes.data?.tx_hash); // "0x..."
console.log(txRes.data?.signed_tx); // signed raw transaction hexThe backend fetches the signing key from the vault, signs the EIP-155 transaction, and returns the signed transaction hex. The signing key is decrypted in-memory, used, and immediately zeroized — it never leaves the server.
The SDK automatically generates an Idempotency-Key header (UUID v4) on each submitTransaction call, providing replay protection. Duplicate requests within 24 hours return the cached response instead of re-signing.
Use signTransaction when you want the server to sign but not broadcast — for example, to submit via your own RPC (Flashbots, MEV protection, custom relayers):
const signRes = await client.agents.signTransaction(agentId, {
to: "0x000000000000000000000000000000000000dEaD",
value: "0.01",
chain: "base",
});
console.log(signRes.data?.signed_tx); // raw signed tx hex
console.log(signRes.data?.tx_hash); // precomputed keccak hash
console.log(signRes.data?.from); // derived sender address
// Broadcast yourself via ethers, viem, or raw JSON-RPCAll agent guardrails (allowlists, value caps, daily limits) are enforced exactly as for submit. The transaction is recorded for audit and daily-limit tracking with status: "sign_only".
Key properties:
- Disabled by default — a human must explicitly enable per-agent
- Signing keys never leave the HSM — same envelope encryption as secrets
- Idempotent by default — each submission includes an auto-generated
Idempotency-Keyheader - Every transaction is audit-logged with full calldata
- Revocable instantly — set
intents_api_enabled: falseto cut off access
https://api.1claw.xyz is a fully OpenID Connect–compliant issuer. External relying parties — Anthropic Workload Identity Federation, GCP STS, AWS STS, Stytch, etc. — can validate 1claw-issued JWTs by fetching:
GET https://api.1claw.xyz/.well-known/openid-configurationGET https://api.1claw.xyz/.well-known/jwks.json
The SDK exposes one method to mint a federation token:
const tokenRes = await client.auth.exchangeFederatedToken({
audience: "https://api.anthropic.com",
// subjectToken? — defaults to the client's current login or apiKey
// scope? — optional space-separated subset of agent's scopes
});
const oidcJwt = tokenRes.data?.access_token; // RS256-signed; ~15 min default TTL, hard cap 1hThe acting agent must have federation_enabled: true and the audience must be on its federation_audiences allowlist (set via dashboard or client.agents.update). Every active KMS key version is published in JWKS, so verifiers reject unknown kids automatically when 1claw rotates keys.
End-to-end Anthropic WIF demo: see the examples/anthropic-wif repo and internal-docs/runbooks/oidc-federation.md.
For enterprises that require cryptographic proof that 1claw cannot access their secrets unilaterally, the SDK provides client-side CMEK utilities. Keys are generated and managed entirely on your side — only the SHA-256 fingerprint is stored on the server.
import { cmek } from "@1claw/sdk";
// Generate a 256-bit AES key (returns CryptoKey)
const key = await cmek.generateCmekKey();
// Compute fingerprint (SHA-256 hex)
const fingerprint = await cmek.cmekFingerprint(key);
// Enable CMEK on a vault
await client.vault.enableCmek(vaultId, { fingerprint });
// Encrypt a secret value before storing
const encrypted = await cmek.cmekEncrypt(key, "my-secret-value");
await client.secrets.set(vaultId, "path/to/secret", encrypted);
// Decrypt after retrieving
const res = await client.secrets.get(vaultId, "path/to/secret");
const plaintext = await cmek.cmekDecrypt(key, res.data.value);await client.vault.rotateCmek(vaultId, oldKey, newKey, {
new_fingerprint: await cmek.cmekFingerprint(newKey),
});The server re-encrypts all secrets in batches of 100. Poll rotation status:
const job = await client.vault.getRotationJobStatus(vaultId, jobId);
console.log(job.data?.status, job.data?.processed, "/", job.data?.total_secrets);Vaults can optionally use multi-party computation (MPC) for secret splitting. When MPC is enabled, the server holds one share and the client holds another — neither party can reconstruct the secret alone.
// Enable MPC on a vault
await client.vault.enableMpc(vaultId);
// Store a secret — response includes the client_share
const res = await client.secrets.set(vaultId, "my-key", "secret-value");
const clientShare = res.data?.client_share; // Save this securely on your side
// Retrieve a secret — pass client_share to reconstruct
const secret = await client.secrets.get(vaultId, "my-key", {
client_share: clientShare,
});
console.log(secret.data?.value);
// Disable MPC (converts back to standard encryption)
await client.vault.disableMpc(vaultId);When MPC is enabled, set responses include a client_share field that must be stored client-side. The get method accepts an optional client_share parameter to reconstruct the full secret. Without the client share, the server returns only its share.
When using agent credentials (agentId + apiKey), the SDK automatically refreshes tokens 60 seconds before expiry. No manual token management needed:
const client = createClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "ocv_...",
agentId: "agent-uuid",
});
// Tokens refresh transparently — just make API callsWhen free-tier limits are exceeded, the API returns 402 Payment Required. The SDK can automatically handle payments if you provide a signer:
import { createClient, type X402Signer } from "@1claw/sdk";
const signer: X402Signer = {
getAddress: async () => "0x...",
signPayment: async (accept) => {
// Sign EIP-712 payment with your wallet library (ethers, viem, etc.)
return signedPayloadHex;
},
};
const client = createClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "ocv_...",
x402Signer: signer,
maxAutoPayUsd: 0.01, // auto-pay up to $0.01 per request
});
// Or use the explicit pay-and-fetch flow:
const secret = await client.x402.withPayment("vault-id", "key", signer);The SDK supports optional plugin interfaces for extending behavior without modifying the core:
import { createClient } from "@1claw/sdk";
import type { CryptoProvider, AuditSink, PolicyEngine } from "@1claw/sdk";
const client = createClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "ocv_...",
plugins: {
cryptoProvider: myAwsKmsProvider,
auditSink: mySplunkSink,
policyEngine: myOpaEngine,
},
});| Interface | Purpose | Default behavior |
|---|---|---|
CryptoProvider |
Client-side encryption (encrypt, decrypt, generateKey) | Server-side HSM (no-op) |
AuditSink |
Forward SDK events to external systems (Splunk, Datadog) | No-op (server handles audit) |
PolicyEngine |
Pre-evaluate policies locally before API calls | No-op (server enforces) |
Implement any interface in your own package — no PRs to the SDK needed.
Agents can route LLM traffic through Shroud, a TEE-based proxy with comprehensive security features. Configure per-agent security policies via the shroud_config object:
const { data } = await client.agents.create({
name: "secure-agent",
shroud_enabled: true,
shroud_config: {
// Basic settings
pii_policy: "redact", // block | redact | warn | allow
injection_threshold: 0.7,
// Model restrictions
allowed_models: ["gpt-4o-mini", "claude-sonnet-4"], // Whitelist specific models
denied_models: ["gpt-3.5-turbo"], // Blacklist models
allowed_providers: ["openai", "anthropic"], // Restrict providers
// Threat detection
unicode_normalization: {
enabled: true,
strip_zero_width: true,
normalize_homoglyphs: true,
},
command_injection_detection: {
enabled: true,
action: "block", // block | sanitize | warn | log
},
social_engineering_detection: {
enabled: true,
action: "warn",
sensitivity: "medium", // low | medium | high
},
encoding_detection: { enabled: true, action: "warn" },
network_detection: { enabled: true, action: "warn" },
filesystem_detection: { enabled: false }, // disabled by default
// Advanced inspection (Phase 2+3)
tool_call_inspection: {
enabled: true,
scan_arguments: true,
block_credential_exfil: true,
action: "block",
},
output_policy: {
enabled: true,
blocked_entities: ["CompetitorCo"],
block_harmful_content: true,
harmful_categories: ["malware", "illegal"],
action: "warn",
},
secret_injection_detection: {
enabled: true,
action: "block",
sensitivity: "medium",
},
advanced_redaction: {
enabled: true,
detect_base64_encoded: true,
detect_split_secrets: true,
detect_prefix_leak: true,
},
semantic_policy: {
enabled: true,
allowed_topics: ["customer_support"],
denied_tasks: ["code_generation", "data_export"],
action: "warn",
},
flagged_request_retention_days: 30,
// Global settings
sanitization_mode: "block", // block | surgical | log_only
threat_logging: true,
},
});Update an existing agent's security config:
await client.agents.update(agentId, {
shroud_config: {
command_injection_detection: { enabled: true, action: "block" },
social_engineering_detection: { enabled: true, action: "block" },
allowed_models: ["gpt-4o-mini"], // Restrict to cost-effective models
},
});When making LLM requests to Shroud, specify the model in one of two ways:
Option 1: Header
const res = await fetch("https://shroud.1claw.xyz/v1/chat/completions", {
method: "POST",
headers: {
"X-Shroud-Agent-Key": `${agentId}:${agentApiKey}`,
"X-Shroud-Provider": "openai",
"X-Shroud-Model": "gpt-4o-mini", // ← Model in header
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: [{ role: "user", content: "Hello" }],
}),
});Option 2: Request Body (for OpenAI-style providers)
body: JSON.stringify({
model: "gpt-4o-mini", // ← Model in body
messages: [{ role: "user", content: "Hello" }],
})Shroud enforces the agent's allowed_models and denied_models restrictions automatically — requests using unauthorized models return 403 Forbidden.
See the Shroud Security Guide for full configuration options.
The SDK's request types are generated from the OpenAPI 3.1 spec, published as @1claw/openapi-spec. Advanced users can access the raw generated types:
import type { paths, components, operations, ApiSchemas } from "@1claw/sdk";
// Access any schema from the spec
type Vault = ApiSchemas["VaultResponse"];
type Agent = ApiSchemas["AgentResponse"];Regenerate types after spec changes (from the monorepo): cd packages/sdk && npm run generate — reads ../openapi-spec/openapi.yaml via openapi-typescript.
The SDK exposes MCP-compatible tool definitions for AI agents:
import { getMcpToolDefinitions, McpHandler } from "@1claw/sdk/mcp";
import { createClient } from "@1claw/sdk";
// Get tool definitions for your agent's tool registry
const tools = getMcpToolDefinitions();
// → 1claw_get_secret, 1claw_set_secret, 1claw_list_secret_keys, etc.
// Dispatch tool calls from your agent
const client = createClient({ baseUrl: "...", token: "..." });
const handler = new McpHandler(client);
const result = await handler.handle("1claw_get_secret", {
vault_id: "...",
key: "OPENAI_KEY",
});import { tool } from "ai";
import { z } from "zod";
import { createClient } from "@1claw/sdk";
const client = createClient({ baseUrl: "...", apiKey: "..." });
export const oneclawTools = {
getSecret: tool({
description: "Fetch a secret from the 1claw vault",
parameters: z.object({
vaultId: z.string(),
key: z.string(),
}),
execute: async ({ vaultId, key }) => {
const res = await client.secrets.get(vaultId, key);
if (res.error) return { error: res.error.message };
return { status: "available", hint: `Secret retrieved (${key})` };
},
}),
};