TypeScript SDK

The @axtary/actionpass package is the developer entry point for instrumenting your own tools and internal APIs directly — no proxy in the path. It exposes five verbs over a single flat request shape: authorize, verify, record, revoke, and explain.

npm install @axtary/actionpass

Decide before you act

import { axtary } from "@axtary/actionpass";

const decision = await axtary.authorize({
  agent: "codex-prod",
  human: "user@company.com",
  intent: "Open a PR for Linear issue AXT-418",
  tool: "github.pull_requests.create",
  resource: "repo:company/web-app",
  payload,
});

if (decision.status === "allow") {
  await github.createPullRequest(payload);
}

authorize always returns a decision. status is "allow", "deny", or "step_up". An allow also issues a signed ActionPass bound to the exact payload (decision.pass); a deny or step-up issues no pass (decision.pass === null). Every call also returns decision.payloadHash, the canonical hash of the exact payload, and a decision.ledger record.

The five verbs

VerbWhat it does
authorize(request)Decide an action and, for an allow, issue a payload-bound ActionPass.
verify(pass, action)Re-check a pass against an action before you execute. Fails closed if the payload was changed or the pass was revoked.
record(result)Append the execution result to the local hash-chained ledger, linked to the decision.
revoke(passId)Revoke a pass so a later verify of it fails closed.
explain(decision)Turn a decision into a deterministic, human-readable explanation (no model call).

Verify the exact payload

const check = await axtary.verify(decision); // or verify(decision.pass, decision.action)
if (!check.valid) {
  // e.g. "payload_hash_mismatch" if the payload changed since authorization
  throw new Error(check.reason);
}

A pass is bound to the exact action it was issued for. If the payload, tool, resource, or actor differs at verify time, verify returns { valid: false, reason: "payload_hash_mismatch" } (or the specific mismatch), so a swapped payload never executes under a valid-looking pass.

Record the result

await github.createPullRequest(payload);
axtary.record({
  action: decision.action,
  decision: decision.decision,
  pass: decision.pass,
  outcome: { status: "succeeded", resultId: "PR-512" },
});

Explain a decision

const explanation = axtary.explain(decision);
// { label: "Step-up required", reviewerAction: "approval_required",
//   summary: "...", reasonDetails: [{ reason, detail }] }

explain is fully deterministic — the same decision always produces the same text. It is the same prose the CLI and dashboard use, so reasons read identically everywhere.

Configuring keys

By default the SDK signs ActionPasses with a persistent local dev key so the example above runs with zero key code. The zero-config path uses a local keyring file: ./.axtary/sdk-dev-keyring.json, created 0600 and gitignored.

For CI, containers, or a shared development key, pass an environment map explicitly:

const keypair = await devKeypair({ env: process.env });

That opt-in path reads AXTARY_DEV_SIGNING_KEY (an ES256 private key as PEM or base64-encoded PEM), optional AXTARY_DEV_KEY_ID, and optional AXTARY_HOME. The package does not read ambient process.env unless you pass it.

Both file-backed and explicit env-backed keys survive process restarts, so passes you issue verify again later on the same machine. The default instance warns once that it is using a dev key.

Dev keypair helper

devKeypair() returns that same stable keypair so you can pass it explicitly — useful when you create several instances or want to control the path:

import { createAxtary, devKeypair } from "@axtary/actionpass";

const sdk = createAxtary(await devKeypair());
// or pin the file: await devKeypair({ path: ".axtary/my-dev-keyring.json" })

Production keys

For production, supply your own issuer key (or point at a hosted issuer) instead of the dev key:

import { createAxtary } from "@axtary/actionpass";

const sdk = createAxtary({
  issuer: "https://issuer.your-company.com",
  signingKey, // your ES256 private key
  verificationKey, // the matching public key, used by verify()
});

SDK vs. proxy

The SDK is the in-process path: you call it from your own code and it uses a built-in policy for the action shapes it ships with. For configurable policy files, credential isolation, durable on-disk ledger writes, and wrapping tools you don't own (MCP servers, HTTP APIs), run the local proxy instead. Both produce the same ActionPass and ledger artifacts.