Skip to content

API Reference

import {
createReceipt,
signReceipt,
verifyReceipt,
generateKeyPair,
hashReceipt,
verifyChain,
} from "@obsigna/sdk-ts";
function createReceipt(input: CreateReceiptInput): UnsignedAgentReceipt

Build an unsigned receipt. Auto-generates an ID (urn:uuid:...), action ID, issuance date, and action timestamp.

function signReceipt(
unsigned: UnsignedAgentReceipt,
privateKey: string,
verificationMethod: string,
): AgentReceipt

Sign an unsigned receipt with an Ed25519 private key (PEM-encoded). Returns a signed AgentReceipt with an Ed25519Signature2020 proof.

function verifyReceipt(receipt: AgentReceipt, publicKey: string): boolean

Verify the Ed25519 signature on a signed receipt.

function generateKeyPair(): KeyPair

Generate an Ed25519 key pair in PEM format.

function hashReceipt(receipt: AgentReceipt): string

Compute the SHA-256 hash of a receipt (excluding proof) using canonical JSON. Returns sha256:<hex>.

function verifyChain(receipts: AgentReceipt[], publicKey: string): ChainVerification

Verify an entire receipt chain: signatures, hash linkage, and sequence numbers.

function canonicalize(value: unknown): string

RFC 8785 canonical JSON serialization.

function sha256(data: string): string

Compute a SHA-256 hash, returned as hex.

interface CreateReceiptInput {
issuer: Issuer;
principal: Principal;
action: Omit<Action, "id" | "timestamp">;
outcome: Outcome;
chain: Chain;
intent?: Intent;
authorization?: Authorization;
actionTimestamp?: string;
}
interface AgentReceipt {
"@context": readonly string[];
id: string;
type: readonly string[];
version: string;
issuer: Issuer;
issuanceDate: string;
credentialSubject: CredentialSubject;
proof: Proof;
}
type UnsignedAgentReceipt = Omit<AgentReceipt, "proof">
interface KeyPair {
publicKey: string;
privateKey: string;
}
interface Issuer {
id: string;
type?: string;
name?: string;
operator?: Operator;
model?: string;
session_id?: string;
runtime?: Runtime; // open metadata container (v0.5.0, ADR-0026)
}
// Open container for runtime/observability metadata. The index signature keeps
// it extensible, so unknown runtime keys survive round-trips.
interface Runtime {
agent_id?: string; // the sub-agent that issued the receipt
agent_type?: string; // e.g. "general-purpose"
[key: string]: unknown;
}
interface Operator {
id: string;
name: string;
}
interface Principal {
id: string;
type?: string;
}
interface Action {
id: string;
type: string;
risk_level: RiskLevel;
target?: ActionTarget;
parameters_hash?: string;
timestamp: string;
trusted_timestamp?: string | null;
}
interface ActionTarget {
system: string;
resource?: string;
}
interface Outcome {
status: OutcomeStatus;
error?: string | null;
reversible?: boolean;
reversal_method?: string;
reversal_window_seconds?: number;
state_change?: StateChange;
}
interface StateChange {
before_hash: string;
after_hash: string;
}
interface Chain {
sequence: number;
previous_receipt_hash: string | null;
chain_id: string;
}
type ChainVerification = {
valid: boolean;
length: number;
receipts: ReceiptVerification[];
brokenAt: number;
}
type ReceiptVerification = {
index: number;
receiptId: string;
signatureValid: boolean;
hashLinkValid: boolean;
sequenceValid: boolean;
}
interface Intent {
conversation_hash?: string;
prompt_preview?: string;
prompt_preview_truncated?: boolean;
reasoning_hash?: string;
}
interface Authorization {
scopes: string[];
granted_at: string;
expires_at?: string;
grant_ref?: string | null;
}
interface Proof {
type: string;
created?: string;
verificationMethod?: string;
proofPurpose?: string;
proofValue: string;
}
type RiskLevel = "low" | "medium" | "high" | "critical"
type OutcomeStatus = "success" | "failure" | "pending"
const CONTEXT: readonly ["https://www.w3.org/ns/credentials/v2", "https://agentreceipts.ai/context/v2"]
const CREDENTIAL_TYPE: readonly ["VerifiableCredential", "AgentReceipt"]
const RECEIPT_VERSION: "0.5.0" // receipt schema
const VERSION: "0.9.0" // package version

import {
generateForensicKeyPair,
encryptDisclosure,
decryptDisclosure,
type ForensicKeyPair,
type DisclosureEnvelope,
type DisclosureRecipient,
} from "@obsigna/sdk-ts";

HPKE-based envelope for encrypting tool-call parameters into a receipt’s parameters_disclosure field (ADR-0012, ciphersuite hpke-x25519-hkdf-sha256-aes-256-gcm). The emitter holds only the forensic public key and never sees the plaintext again after encryption; the private key stays offline. For the threat model and operator configuration see the Parameter Disclosure specification. For the envelope JSON shape see the schema reference.

This is the SDK-direct path. When emitting through the daemon the operator provides the public key in daemon config; the daemon calls these helpers automatically.

All three functions are async (they use node:crypto internally).

async function generateForensicKeyPair(): Promise<ForensicKeyPair>

Generate an X25519 key pair for forensic disclosure. publicKey (32-byte Uint8Array) is shared with emitters. privateKey (32-byte Uint8Array) must be kept offline, separate from the Ed25519 signing key (ADR-0001 / ADR-0012).

async function encryptDisclosure(
params: Record<string, unknown>,
recipientPublicKey: Uint8Array,
kid: string,
): Promise<DisclosureEnvelope>

Encrypt params as a v1 HPKE disclosure envelope. params is RFC 8785 JCS-canonicalized before encryption so all SDKs produce the same ciphertext for the same parameters object. params must be a plain object (not null or an array). recipientPublicKey must be 32 bytes. kid is the recipient key identifier (sha256:<hex> fingerprint or did:key DID URL). Throws on invalid arguments.

async function decryptDisclosure(
env: DisclosureEnvelope,
recipientPrivateKey: Uint8Array,
): Promise<Record<string, unknown>>

Recover the plaintext parameters from a v1 HPKE disclosure envelope. recipientPrivateKey must be 32 bytes. Throws if the envelope version or algorithm is unsupported, if the key or ciphertext is malformed, or if AEAD authentication fails.

// Key management (run once offline, store private key securely)
const kp = await generateForensicKeyPair();
// Share kp.publicKey with emitters; keep kp.privateKey offline.
// Compute the fingerprint manually: sha256(kp.publicKey) → "sha256:..."
// Emitter side (holds only the public key)
const env = await encryptDisclosure(
{ path: "/etc/passwd", mode: "r" },
kp.publicKey,
"sha256:...",
);
// embed env in action.parameters_disclosure, then sign the receipt
// Forensic / audit side (holds the offline private key)
const params = await decryptDisclosure(env, kp.privateKey);
// params == { mode: "r", path: "/etc/passwd" }

In practice the envelope comes from a stored receipt and the private key from the file obsigna-daemon --init-forensic-key wrote — a raw 32-byte X25519 key, not PEM, so read it as bytes and pass it straight in. Pull the envelope out of the receipt JSON at credentialSubject.action.parameters_disclosure:

import { readFileSync } from "node:fs";
import { decryptDisclosure } from "@obsigna/sdk-ts";
// raw 32-byte private key written by --init-forensic-key (kept offline)
const priv = new Uint8Array(readFileSync("/path/to/forensic.key"));
// from: obsigna receipt show <seq> --json > receipt.json
const receipt = JSON.parse(readFileSync("receipt.json", "utf8"));
const env = receipt.credentialSubject.action.parameters_disclosure;
const params = await decryptDisclosure(env, priv);
// params holds the original tool-call parameters
interface ForensicKeyPair {
/** 32-byte X25519 public key. Share with emitters to enable encryption. */
publicKey: Uint8Array;
/** 32-byte X25519 private key. Keep offline; required to decrypt. */
privateKey: Uint8Array;
}
interface DisclosureEnvelope {
v: "1";
alg: "hpke-x25519-hkdf-sha256-aes-256-gcm";
recipients: [DisclosureRecipient]; // length-1 tuple; v1 single-recipient
ct: string; // AEAD ciphertext; unpadded base64url
}
interface DisclosureRecipient {
kid: string;
enc: string; // HPKE encapsulated key; unpadded base64url, 43 chars for X25519
}

v1 HPKE envelope stored in action.parameters_disclosure. Field names follow RFC 9180 §4.1 (enc, not encap).

Note: ForensicKeyFingerprint and ForensicPublicFromPrivate are available in the Go SDK only (added in PR #722). Use sha256(kp.publicKey) from the existing sha256 export to compute the fingerprint string in TypeScript.


import { ReceiptStore, openStore, verifyStoredChain } from "@obsigna/sdk-ts";

SQLite-backed receipt persistence and querying.

function openStore(dbPath: string): ReceiptStore

Open or create a SQLite receipt store. Pass ":memory:" for an in-memory store.

function verifyStoredChain(
store: ReceiptStore,
chainId: string,
publicKey: string,
): ChainVerification

Load a chain from the store and verify its integrity.

Use openStore(dbPath) to create a store — the class constructor is an implementation detail. openStore is the stable public factory.

class ReceiptStore {
constructor(dbPath: string);
insert(receipt: AgentReceipt, receiptHash: string): void;
getById(id: string): AgentReceipt | undefined;
getChain(chainId: string): AgentReceipt[];
query(filters: ReceiptQuery): AgentReceipt[];
stats(): StoreStats;
close(): void;
}
interface ReceiptQuery {
chainId?: string;
actionType?: string;
riskLevel?: RiskLevel;
status?: OutcomeStatus;
after?: string;
before?: string;
/** When omitted, all matching rows are returned (no default cap). */
limit?: number;
/**
* Sort order. Default `"asc"` (oldest first). Use `"desc"` for newest first;
* ties on timestamp are broken by sequence descending.
*/
order?: "asc" | "desc";
}
interface StoreStats {
total: number;
chains: number;
byRisk: { risk_level: string; count: number }[];
byStatus: { status: string; count: number }[];
byAction: { action_type: string; count: number }[];
}

import {
classifyToolCall,
getActionType,
resolveActionType,
loadTaxonomyConfig,
ALL_ACTIONS,
} from "@obsigna/sdk-ts";

Action type registry and tool call classification.

function classifyToolCall(
toolName: string,
mappings?: TaxonomyMapping[],
): ClassificationResult

Classify a tool call to an action type and risk level using the provided mappings.

function getActionType(type: string): ActionTypeEntry | undefined

Look up an action type by name. Returns undefined if not found.

function resolveActionType(type: string): ActionTypeEntry

Like getActionType but returns an “unknown” fallback instead of undefined.

function loadTaxonomyConfig(filePath: string): TaxonomyMapping[]

Load taxonomy mappings from a JSON file.

interface ActionTypeEntry {
type: string;
description: string;
risk_level: RiskLevel;
}
interface TaxonomyMapping {
tool_name: string;
action_type: string;
}
interface ClassificationResult {
action_type: string;
risk_level: RiskLevel;
}
const FILESYSTEM_ACTIONS: readonly ActionTypeEntry[] // 7 types
const SYSTEM_ACTIONS: readonly ActionTypeEntry[] // 7 types
const ALL_ACTIONS: readonly ActionTypeEntry[] // all + unknown
const UNKNOWN_ACTION: ActionTypeEntry

DATA_ACTIONS (3 data/API types) is exported from the taxonomy subpath, not the package root:

import { DATA_ACTIONS } from "@obsigna/sdk-ts/taxonomy";