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
pip install wytness-aiagent-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
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
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
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
| Python | TypeScript | Type | Required | Default | Description |
|---|---|---|---|---|---|
| api_key | apiKey | string | Yes | — | Transport credential. Read from WYTNESS_API_KEY if omitted. Sent as Bearer token on /ingest POSTs. |
| endpoint | endpoint | string | No | "https://api.wytness.ai" | Ingest base URL. Read from WYTNESS_ENDPOINT if omitted. Override for testing or local dev. |
| pii_pubkey | piiPubkey | string (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_secret | piiSecret | string (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_key | signingKey | string (base64) | Yes | — | Ed25519 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_wire | autoWire | boolean | No | true | Auto-wire AGT's audit log (Python) / audit logger (TS) on init. Pass false to wire the sink manually. |
| batch_size | batchSize | integer | No | 100 | Events per batch flush. Minimum 1. |
| flush_interval_ms | flushIntervalMs | integer | No | 5000 | Maximum milliseconds between flushes. Minimum 100. |
| pii_fields | piiFields | list[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_run | dryRun | boolean | No | false | Build + 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 | True | No | — | Automatic 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_did | agentDid | string | No | — | DID stamped on auto-captured LLM events so they attribute to your agent. No env-var equivalent — pass it explicitly. |
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 var | Maps to | Required |
|---|---|---|
| WYTNESS_API_KEY | api_key / apiKey | Yes |
| WYTNESS_ENDPOINT | endpoint / endpoint | No (defaults to https://api.wytness.ai) |
| WYTNESS_SIGNING_KEY | signing_key / signingKey | Yes |
| WYTNESS_PII_PUBKEY | pii_pubkey / piiPubkey | When PII pseudonymisation (paired with WYTNESS_PII_SECRET) |
| WYTNESS_PII_SECRET | pii_secret / piiSecret | When PII pseudonymisation (paired with WYTNESS_PII_PUBKEY) |
| WYTNESS_DRY_RUN | dry_run / dryRun | No (truthy values: 1 / true / yes / on) |
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 msok = wytness_ai.shutdown() # Or with a custom timeoutok = wytness_ai.shutdown(timeout_ms=2000)TypeScript
// Default timeout 5000 msconst ok = await shutdown(); // Or with a custom timeoutconst ok2 = await shutdown(2000);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.
| Class | When raised |
|---|---|
| WytnessSDKError | Base 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. |
| WytnessInitError | init() 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. |
| WytnessTransportError | An 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.
| Python | TypeScript | Returns |
|---|---|---|
| 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.
| Command | What it does |
|---|---|
| wytness-ai verify | Round-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 tail | Stream recent dashboard events to your terminal. Flags: --agent-id, --limit, --interval, --once, --json. |
| wytness-ai validate-chain | Pull 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.
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 param | Required | Description |
|---|---|---|
| from_ts / to_ts | Yes | Time range (ISO 8601). Events are returned oldest-first. |
| agent_id | No | Restrict to one agent. |
| status | No | Restrict to one event status. |
| format | No | csv (default) or jsonl. |
| after | No | Keyset 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.
# Page 1curl -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_idlast=$(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.jsonlRelated
- Quickstart (Python)
- Quickstart (TypeScript)
- PII Protection
- Event schema
- Built on AGT — the two ways in (wrapper + direct), feature matrix, and how to choose