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
| Verb | What 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.