SDK API reference

The wrapper SDK has a small public surface: init(), shutdown(), a config object, and the automatic model-capture helpers (wrap= on init() in Python; wrapAnthropic() / wrapOpenAI() in TypeScript — see the Quickstart). Everything else — AGT identity, hash chain, framework adapters — lives in Microsoft's Agent Governance Toolkit. We don't re-document AGT here. See the AGT repo for the AuditLog / AuditLogger API.

Install

Both SDKs pull Microsoft AGT in automatically. No extras to remember.

Python

terminal
pip install wytness-ai

agent-governance-toolkit[full]==4.1.0 is a hard dependency — installed automatically. We wrap AGT's audit log cleanly so framework adapters work unchanged. Requires Python 3.11+.

TypeScript

terminal
npm install @wytness/ai

@microsoft/agent-governance-sdk@5.0.0 is a hard dependency since version 0.2.0-dev.0. Targets Node 18+ — uses the global fetch. Ships ESM + CJS dual output.

init()

Resolves config, builds the shared transport, constructs audit + event sinks, and (when auto_wire / autoWire is True, the default) wires AGT's audit log so every entry it chains is forwarded into the Wytness envelope path. Idempotent — a second call returns the existing config without rebuilding.

Python

agent.py
import wytness_ai
config = wytness_ai.init(
# Reads from WYTNESS_* env vars by default — all of these are optional
# kwargs that override the env var lookup.
api_key="wyt_api_live_...",
endpoint="https://api.wytness.ai",
signing_key="<base64 Ed25519 private>",
# PII keys are optional but paired — supply both to enable
# pseudonymisation, or neither for no-PII mode. Mismatched pair
# raises ConfigError at init time.
pii_pubkey="<base64 X25519 public>",
pii_secret="<base64 HMAC secret>",
# Behaviour flags
auto_wire=True, # Auto-wire AGT's audit log. Default True.
batch_size=100, # Events per batch flush. Default 100.
flush_interval_ms=5000, # Max ms between flushes. Default 5000, min 100.
# PII fields — dotted-path list of values to pseudonymise.
pii_fields=["customer.email", "customer.name"],
)

TypeScript

agent.ts
import { init } from "@wytness/ai";
const config = await init({
// Reads from WYTNESS_* env vars by default — all of these are optional
// kwargs that override the env var lookup.
apiKey: "wyt_api_live_...",
endpoint: "https://api.wytness.ai",
signingKey: "<base64 Ed25519 private>",
// PII keys are optional but paired — supply both to enable
// pseudonymisation, or neither for no-PII mode. Mismatched pair
// raises ConfigError at init time.
piiPubkey: "<base64 X25519 public>",
piiSecret: "<base64 HMAC secret>",
// Behaviour flags
autoWire: true, // Auto-wire AGT's audit logger. Default true.
batchSize: 100, // Events per batch flush. Default 100.
flushIntervalMs: 5000, // Max ms between flushes. Default 5000, min 100.
// PII fields — dotted-path list of values to pseudonymise.
piiFields: ["customer.email", "customer.name"],
});

Config parameters

PythonTypeScriptTypeRequiredDefaultDescription
api_keyapiKeystringYesTransport credential. Read from WYTNESS_API_KEY if omitted. Sent as Bearer token on /ingest POSTs.
endpointendpointstringNo"https://api.wytness.ai"Ingest base URL. Read from WYTNESS_ENDPOINT if omitted. Override for testing or local dev.
pii_pubkeypiiPubkeystring (base64)When PII pseudonymisation (paired)X25519 public key — 32 bytes decoded. Read from WYTNESS_PII_PUBKEY if omitted. Used to encrypt the token map so only the X25519 private-key holder can reverse pseudonyms. Required together with pii_secret, or both omitted (no-PII mode).
pii_secretpiiSecretstring (base64)When PII pseudonymisation (paired)HMAC secret — minimum 16 bytes decoded. Read from WYTNESS_PII_SECRET if omitted. Used to derive deterministic pseudonyms (same value → same token). Required together with pii_pubkey.
signing_keysigningKeystring (base64)YesEd25519 private key — 32 bytes decoded. Read from WYTNESS_SIGNING_KEY if omitted. Used to sign each event's canonical-shape payload. Signing is always on — non-repudiation is the product promise.
auto_wireautoWirebooleanNotrueAuto-wire AGT's audit log (Python) / audit logger (TS) on init. Pass false to wire the sink manually.
batch_sizebatchSizeintegerNo100Events per batch flush. Minimum 1.
flush_interval_msflushIntervalMsintegerNo5000Maximum milliseconds between flushes. Minimum 100.
pii_fieldspiiFieldslist[str]No[]Dotted paths whose values are fully pseudonymised (in addition to regex-detected emails / phones / SSN / TFN / cards). Example: customer.email matches data.customer.email.
dry_rundryRunbooleanNofalseBuild + Ed25519-sign every envelope but log it locally instead of POSTing to /ingest; init() also skips the signing-key registration probe so it runs fully offline. Also enabled by WYTNESS_DRY_RUN.
wrap— (use wrapAnthropic() / wrapOpenAI())module | str | list | TrueNoAutomatic LLM capture (Python): a provider module (anthropic / openai), its name, a list of those, or True to auto-detect installed providers. In TypeScript, wrap the client instance with wrapAnthropic(client) / wrapOpenAI(client) after init().
agent_didagentDidstringNoDID stamped on auto-captured LLM events so they attribute to your agent. No env-var equivalent — pass it explicitly.
Note
Required values can come from kwargs OR env vars — kwargs win when both are set. Missing required values raise ConfigError at init() time. base64 decoding is strict — non-base64 inputs raise ConfigError.

Environment variables

The SDK reads these as process environment variables by default — set them in your secrets manager (Azure Key Vault, AWS Secrets Manager, Vercel env, k8s secrets, etc.) or, for local dev, a .env file loaded by python-dotenv / Node’s --env-file. Either way, init() reads from os.environ / process.env — call it with no args.

Env varMaps toRequired
WYTNESS_API_KEYapi_key / apiKeyYes
WYTNESS_ENDPOINTendpoint / endpointNo (defaults to https://api.wytness.ai)
WYTNESS_SIGNING_KEYsigning_key / signingKeyYes
WYTNESS_PII_PUBKEYpii_pubkey / piiPubkeyWhen PII pseudonymisation (paired with WYTNESS_PII_SECRET)
WYTNESS_PII_SECRETpii_secret / piiSecretWhen PII pseudonymisation (paired with WYTNESS_PII_PUBKEY)
WYTNESS_DRY_RUNdry_run / dryRunNo (truthy values: 1 / true / yes / on)
Important
The signing key and the PII HMAC secret are roots of trust. Generate both in the dashboard Keys page (browser-only, never seen by Wytness) and store them in your secrets manager. See PII Protection — Generating keys.

shutdown()

Flushes the in-flight batch, unwires the audit hook, and tears down the transport. Idempotent — safe to call before init() or twice. Returns True if everything flushed within the timeout.

Python

# Default timeout 5000 ms
ok = wytness_ai.shutdown()
# Or with a custom timeout
ok = wytness_ai.shutdown(timeout_ms=2000)

TypeScript

// Default timeout 5000 ms
const ok = await shutdown();
// Or with a custom timeout
const ok2 = await shutdown(2000);
Tip
Call shutdown() from your process's clean-exit handler (SIGINT / SIGTERM) so the final batch lands before exit. In short-lived scripts, calling it right before sys.exit() / process.exit() is enough.

Manual wiring (auto_wire=False)

Auto-wire hooks AGT's audit log so every entry it chains flows into Wytness without you touching your existing AGT code. If you'd rather wire it explicitly, set auto_wire=False / autoWire: false and write to the audit sink directly.

Python

import wytness_ai
wytness_ai.init(auto_wire=False)
# Access the sink that init() built. write() accepts either a fully-canonical
# snake_case dict or the AGT AuditEntry shape — the canonical transform
# runs at the sink boundary.
transport = wytness_ai.get_transport()

TypeScript

import { init, getAuditSink } from "@wytness/ai";
await init({ autoWire: false });
const sink = getAuditSink()!;
sink.write({
agentId: "agent-1",
action: "tool_invocation:search_web",
decision: "allow",
hash: "<sha-256>",
previousHash: "0".repeat(64),
data: { query: "..." },
});

setNextEntryExtras() (TypeScript only)

Microsoft's TypeScript AGT SDK accepts a small camelCase entry shape on AuditLogger.log()agentId, action, decision, resource, previousHash, hash. Wytness extras (data, outcome, resource) aren't part of AGT-TS's surface, so stash them on the next entry with setNextEntryExtras(...).

import { setNextEntryExtras, AuditLogger } from "@wytness/ai";
const audit = new AuditLogger();
setNextEntryExtras({
data: { customer: { email: "alice@example.com" }, query: "..." },
outcome: "success",
resource: "https://example.com",
});
audit.log({
agentId: "audit-test-agent",
action: "tool_invocation:search_web",
decision: "allow",
});

Python doesn't need this — AGT's Python AuditLog.log() takes data=, outcome=, and resource= as named parameters, so they can be passed inline. The full signature is log(event_type, agent_did, action, resource=None, data=None, outcome="success", policy_decision=None, trace_id=None) — it does not accept arbitrary keyword arguments. Custom fields belong inside data={...}.

Errors

Both SDKs raise a small typed hierarchy rooted at WytnessSDKError — catch that to handle the whole family.

ClassWhen raised
WytnessSDKErrorBase class — never raised directly. Catch this to handle every Wytness-typed error.
ConfigError (alias WytnessConfigError)init() is called with missing required values, malformed base64, wrong-length keys, or out-of-range batch / flush parameters. Caught at init time, before any events are emitted.
WytnessInitErrorinit() fails after config resolution: the signing key isn't registered (or was revoked) — caught by the init-time registration probe — or transport / sink construction or auto-wire fails. The most likely first-run error; register the key on the dashboard Keys page and re-run.
WytnessTransportErrorAn explicit force_flush() / post_batch() exhausts the retry budget (5xx or network failure). The batch is written to the dead-letter file first, so no events are lost. Background flushes never raise into your code — they log and dead-letter.

Diagnostics

Three read-only helpers expose SDK state for your own /healthz integration. All are safe to call at any time, initialised or not.

PythonTypeScriptReturns
stats()stats()Runtime counters — initialised, version, effective endpoint, PII on/off, dry-run, wire state, plus transport counters (posts, events, retries, dead-letters, queue depth, last error). Stable keys, suitable for a /healthz JSON body.
health()health()Coarse snapshot: {healthy, issues[], env, ...stats}. When not initialised, issues names the specific missing WYTNESS_* vars; when running, flags queue back-up, skipped auto-wire, and an unwritable dead-letter file.
wire_status()getAgtWireResult()The AGT auto-wire result from the last init() — wired is true iff the AuditLog hook landed. None/null before init().

CLI

Both packages ship a wytness-ai command (python -m wytness_ai / npx @wytness/ai). All commands read the same WYTNESS_* env vars and never generate keys — customer-held keys come from the dashboard Keys page.

CommandWhat it does
wytness-ai verifyRound-trip one signed event end-to-end and confirm it arrives in the dashboard. Distinct exit codes name the failing stage (config, init, send, auth, arrival timeout).
wytness-ai tailStream recent dashboard events to your terminal. Flags: --agent-id, --limit, --interval, --once, --json.
wytness-ai validate-chainPull recent events and walk the AGT hash chain locally, proving link integrity without trusting the server. Flags: --agent-id, --limit, --json.

Dead-letter file

If the transport can't reach /ingest after retries, the batch is written to a local dead-letter file at ~/.wytness/wytness-deadletter.jsonl — one CloudEvents envelope per line, plain JSONL. Replay is manual: the wrapper endpoint expects a JSON array, so wrap the lines with jq -s before POSTing.

terminal
jq -s . ~/.wytness/wytness-deadletter.jsonl | curl -X POST https://api.wytness.ai/ingest \
-H "Content-Type: application/vnd.wytness.agt+json" \
-H "Authorization: Bearer $WYTNESS_API_KEY" \
--data-binary @-

REST API — bulk event export

GET /export streams your org's audit events as CSV or JSONL — the full column set including encrypted_token_map, the AGT chain values, and the envelope signature, so exports stay verifiable and your PII stays decryptable offline. It's the same API the dashboard's Export page drives. Authenticate with a dashboard-issued access token (Authorization: Bearer); rate limit 5/minute; JSONL requires the JSONL-export plan feature.

Query paramRequiredDescription
from_ts / to_tsYesTime range (ISO 8601). Events are returned oldest-first.
agent_idNoRestrict to one agent.
statusNoRestrict to one event status.
formatNocsv (default) or jsonl.
afterNoKeyset cursor: the event_id of the LAST row of the previous page. The response resumes strictly after that row in (timestamp, event_id) order.

A single response returns at most 100,000 rows. When more rows match, the response carries X-Export-Truncated: true, X-Export-Row-Limit, and X-Export-Total-Rows (rows remaining from the cursor position). Walk the full history by re-requesting with after= set to the last row's event_id until the truncation header disappears. Ordering is deterministic — timestamp ascending with event_id as the tie-break — so pages never overlap or skip rows.

terminal
# Page 1
curl -sD headers.txt "https://api.wytness.ai/export?from_ts=2026-01-01T00:00:00&to_ts=2026-07-01T00:00:00&format=jsonl" \
-H "Authorization: Bearer $ACCESS_TOKEN" -o page1.jsonl
# While headers.txt contains X-Export-Truncated: true, continue from the last event_id
last=$(tail -1 page1.jsonl | jq -r .event_id)
curl -s "https://api.wytness.ai/export?from_ts=2026-01-01T00:00:00&to_ts=2026-07-01T00:00:00&format=jsonl&after=$last" \
-H "Authorization: Bearer $ACCESS_TOKEN" -o page2.jsonl

We set no cookies. Sign-in and preferences use essential first-party browser storage only — no tracking, advertising, or third-party analytics. Privacy Policy

SDK API reference
TABLE OF CONTENTS