Skip to content

SDK reference ​

@sectigo/axiom-sdk (packages/sdk) is the TypeScript SDK: an agent client, wake-event helpers, provider helpers and an offline receipt verifier. This page is generated by hand from the TSDoc in packages/sdk/src/*.ts; the source is authoritative.

Developer preview

Version 0.1.0, a workspace package of TypeScript sources. It is not yet published to a registry; use it from the monorepo (run scripts with npx tsx). It runs on Node 22+, Workers and browsers: every client takes an injectable fetch, clock and sleep.

Entry points ​

ImportContents
@sectigo/axiom-sdkEverything below.
@sectigo/axiom-sdk/providerAxiomProvider and its types (receiving applications).
@sectigo/axiom-sdk/verifyverifyReceipt, formatReport, assertReceiptBundle.

The root module also re-exports the wire types from the core contract (ActionState, ActionStatus, CapabilitiesDocument, ErrorCode, ForwardedAuthorization, Json, OutcomeAttestation, Problem, PrivateJwk, PublicJwk, ReasonCode, ReceiptBundle, ReceiptClaims, ResumeActionRequest, Signer, SubmitActionRequest, VerificationReport, WaitMode, WakeEvent) and the constants ACTION_STATES, API_VERSION ("2026-11-14"), HEADERS and REASON_CODES.

Runtime options ​

Every client accepts RuntimeOptions:

OptionTypeDefault
fetchFetchLike ((input, init) => Promise<Response>; Hono's app.request satisfies it)global fetch
nowClock (() => number, ms since epoch)Date.now
sleepSleep ((ms, signal?) => Promise<void>)setTimeout-based, abortable

Helpers: resolveRuntime(o), realSleep, epochSeconds(now), trimBase(url).

AxiomAgent ​

The agent-side client. The base contract is submit, status, resume, plus cancel, receipt, capabilities and delegate; the helpers waitForDecision, trackOutcome, run and continue are built only from those, so an agent without the SDK gets identical behaviour from the HTTP contract.

Invariants the client keeps: every call carries a DPoP proof bound to the current token and exact URL; a submit is only ever retried with the same operation key; after a resume, no second resume is sent unless an authenticated status read shows the action is still approved; executing and outcome_unknown are tracked by status reads only; the action ID, operation key and deadline are persisted before a warm wait ends.

ts
new AxiomAgent(options: AxiomAgentOptions)
OptionTypeNotes
baseUrlstringAPI origin, e.g. https://api.sectigoaxiom.com.
agentIdstringStable agent ID (agt_…).
signerSignerThe agent's currently enrolled key (local, KMS or HSM).
tokenEndpointstringDefault {baseUrl}/oauth/token; must equal the issuer's token endpoint.
scopeAgentScope[]Default ["actions:submit", "actions:read"].
persistenceActionPersistenceWhere pending actions are stored across restarts. Optional.
submitRetriesnumberTransport retries for submit, same key each time. Default 2.
plus RuntimeOptions
MemberDescription
agentIdThe stable agent ID this client authenticates as.
tokenManagerThe TokenManager, for callers that need a raw token (e.g. an embedded-SDK provider).
rotateSigner(signer)Switch to a newly enrolled key after legitimate rotation. The agent ID, and the authority to resume its actions, is unchanged.
authHeaders(method, url){ authorization, dpop } for calling a third-party URL (an embedded-SDK provider) as this agent. The proof is bound to that URL.
capabilities()GET /v1/capabilities; cached for the life of the client.
submit(input, { signal? })POST /v1/actions. Transport failures are retried with the same key (backoff 0.5 s, doubling, max 8 s). A changed request under the same key throws AxiomProblem with code idempotency_conflict.
status(actionId)Authenticated status read; the only source of truth after any notification.
resume(actionId, input)POST /v1/actions/{id}/resume with the approved operation_key, resource, params (and optional payload). The result may still be executing or outcome_unknown: track it, never resume again.
cancel(actionId)Cancel a pending or approved action.
receipt(actionId, { reveal? })Download the signed receipt bundle; reveal limits disclosed fields.
delegate(grantId, input)POST /v1/grants/{id}/delegate: a narrower child grant for another agent, bound to one root action. Returns DelegationResult.
request(method, path, body?)Authenticated request to any API path. A 401 invalid_token gets one retry with a fresh token.
waitForDecision(actionId, { budgetSeconds?, signal? })Bounded warm wait while pending_approval. Sleeps retry_after_seconds between reads; budget capped at max_warm_wait_seconds; never starts a poll that would end after the budget; transport errors count as no news. Returns WaitResult.
trackOutcome(actionId, opts)The same while executing or outcome_unknown. Never resumes or resubmits.
run(input, opts)Submit, warm-wait, resume as the original agent and track to a confirmed outcome within one budget. Returns RunResult; persists the record when the budget ends first.
continue(record, opts)Pick up a persisted action after a restart: re-authenticate, read status, carry on. Throws if record.agent_id is another agent.
pendingRecord(status, input)Build the persistable PendingActionRecord for an action created elsewhere (e.g. by an embedded-SDK provider).

isProblem(e, code?) is a type guard for AxiomProblem with an optional code.

Types ​

TypeShape
SubmitInputSubmitActionRequest plus payload? (non-material passthrough, never stored or approved) and execute? (default true).
ResumeInputResumeActionRequest (params, resource, operation_key) plus payload?.
DelegateInput{ child_agent, root_action_id, operations[], resources[], expires_at }; expires_at is clamped to the parent grant's expiry.
DelegationResult{ grant_id, parent_grant_id, root_action_id, agent, operations, resources, expires_at, depth }.
WaitOptions{ budgetSeconds?, signal? }.
WaitResult{ done: true, status } or { done: false, status, actionId, checkAfter, message }.
RunOptionsWaitOptions plus resume?: ResumeExecutor. Default resume is POST …/resume; for embedded-SDK services pass a function that re-sends the original request to the provider with the same operation key.
ResumeExecutor(status, request: ResumeInput) => Promise<ActionStatus>.
RunResult{ kind: "completed" } (succeeded/failed), "stopped" (denied, expired, revoked, cancelled), "waiting" (with record, message), "tracking" (dispatched or handed to a provider, not confirmed; with record, message).

run resumes at most twice while status still says approved, then returns tracking, so an embedded provider that never claims cannot cause a resume loop.

TokenManager ​

Caches and refreshes the agent's DPoP-bound access token (client_credentials + private_key_jwt). Concurrent callers share one token request.

MemberDescription
new TokenManager(options, runtime)Options: agentId, signer, tokenEndpoint (also the DPoP htu and assertion aud), scope?, resource? (RFC 8707), refreshSkewSeconds? (default 60; tokens last 300 s).
signer, agentIdCurrent key and stable ID.
get()A valid AgentToken (accessToken, tokenType: "DPoP", scope, expiresAt), fetching when none is cached or it is about to expire. Expiry is measured from when the request started.
headersFor(method, url){ authorization: "DPoP …", dpop } for one request.
rotate(signer)Switch keys; drops the cached token (bound to the old thumbprint).
invalidate()Forget the cached token.

Constants: JWT_BEARER (the RFC 7523 assertion type). Type AgentScope is "actions:submit" | "actions:read".

Status helpers ​

FunctionDescription
nextStep(state)NextStep for a state: wait_for_decision (pending), resume (approved), track (executing, outcome_unknown), read_outcome (succeeded, failed), stop (denied, expired, revoked, cancelled).
isSettled(state)True when nothing more will happen (read_outcome or stop).
currentDeadline(status)resume_deadline when approved, decision_deadline when pending (falling back to expires_at).
waitingOn(status)Who the action waits on, from approvals[].next_party.
describeStatus(status, { checkCommand? })A one-paragraph, data-minimised description for the agent's user, e.g. "Action act_… is waiting for approval: … Waiting on finance_approver in axiom. A decision is needed by …". Default check command axiom status {id}.

Operation keys and persistence ​

SymbolDescription
newOperationKey(prefix = "op")A fresh random key (prefix- plus 18 random bytes, base64url). Generate once per intent and reuse for every retry.
deriveOperationKey(prefix, parts)A deterministic key from the intent's identifying fields (SHA-256 of the canonical JSON, 32 characters), for runtimes that cannot store a key before submitting.
PendingActionRecord{ action_id, operation_key, agent_id, service, operation, tenant, resource, params, deadline?, state, status_url, saved_at }: enough to read status after a restart and resume with the exact approved request.
ActionPersistencesave(record), remove(actionId), get(actionId), list(). save must be durable before it resolves.
MemoryActionPersistenceIn-memory implementation for tests and short-lived processes.

Wake events ​

SymbolDescription
WakeEventReceiverVerifies and deduplicates wake events. Options: issuer or jwksUrl or keys (one is required), maxAgeSeconds (default 86400, DEFAULT_WAKE_MAX_AGE_SECONDS; deduplication by (action_id, seq) is the replay defence), tenant (reject other tenants), seqStore, plus RuntimeOptions.
receiver.receive(body)Verify a delivery body (compact JWS) and classify it: { event, disposition: "fresh" | "duplicate" | "stale" }. Throws on a wrong signature, type, age or tenant. Keys are Axiom's event role keys.
receiver.markProcessed(event)Record the event's seq as processed. Call only after the status read it triggered succeeded.
handleWake(agent, receiver, body)Verify, then read authoritative status for every event that names an action (fresh, duplicate or stale) and mark it processed. A read returns the current state, so an out-of-order event cannot move you backwards. Returns HandledWake (WakeResult plus status?; use disposition to tell how it compared).
SeqStore / MemorySeqStoreHighest processed seq per action. Back it with durable storage if the consumer restarts.

See webhooks and relay.

AxiomProvider ​

@sectigo/axiom-sdk/provider. Helpers for an application that receives Axiom-authorised requests. Full guide: embedded SDK provider guide.

MemberDescription
new AxiomProvider(options)issuer, audience (origin or origins), apiUrl?, apiKey? (provider:authorize), jwksUrl?, keys? (pinned), outcomeSigner?, clockSkewSeconds? (default 30), plus RuntimeOptions.
outcomeKeys(){ keys: [publicJwk] } to publish and register on the service.
verifyDispatch(headers)Verify Axiom-Authorization and that Idempotency-Key / Axiom-Action-Id agree with it. Returns DispatchContext (the signed claims plus kid).
verifyDispatchToken(jws)Verify a compact axiom.dispatch.v1 JWS: grant-key signature, type, issuer, audience, lifetime, claims, and that canonical reproduces request_digest.
assertMatches(ctx, { resource, params })Throw 409 binding_mismatch unless the received request equals the approved one (decimals by value, currencies case-insensitively).
authorize(agent, action)Embedded SDK: forward the agent's credential and the authoritative action to POST /v1/authorize. Returns AuthorizeResult with context verified when decision === "execute".
authorizeRequest(req, action, externalUrl?)The same, taking the agent's headers from a Request.
attestOutcome(ctx, outcome)Sign an axiom.outcome.v1 attestation with outcomeSigner.
reportOutcome(actionId, attestation)Embedded SDK: POST /v1/actions/{id}/outcome so Axiom closes the action and issues the receipt.
executeOnce(store, ctx, fn, { maxAttempts? })Commit at most once per operation key; returns { record, replayed }.
handleOperationLookup(store, operationKey, headers)Answer GET /.well-known/axiom/operations/{key} with { state, attestation, target_ref } or 404.

Other exports: ProviderError (status, code, toResponse()), MemoryIdempotencyStore, attestationKid(jws), and the types DispatchContext, OperationRef, OperationRecord, InsertResult ("inserted" | "exists" | "write_rejected"), IdempotencyStore<W>, MemoryWrite, BusinessOutcome<W>, ExecuteOnceResult, AuthorizeResponse (decision: "execute" | "completed" | "pending" | "in_progress" | "stop", status, claim?), AuthorizeResult, ForwardedAgentRequest, AuthorizeAction, AxiomProviderOptions.

Receipt verification ​

@sectigo/axiom-sdk/verify. Offline by default: the bundle carries a snapshot of the keys that signed it.

SymbolDescription
verifyReceipt(bundle, options?)Returns ReceiptVerification: valid, the full report, keySource (bundle_snapshot, fetched or pinned), fetchedKeyStatus (kid to active/retired/revoked) and text. A signature by a key the fetched JWKS marks revoked makes the result not valid.
optionsissuerJwksUrl, providerJwksUrl (fetch keys instead of trusting the snapshot), issuerKeys, providerKeys (pinned, preferred), trustedIssuers (fail unless the receipt's issuer is listed), fetch.
formatReport(report, ctx?)Plain-text rendering, one check per line.
assertReceiptBundle(value)Throws unless value looks like an axiom.receipt.bundle.v1.

Pin keys for real trust

A bundle's embedded key snapshot proves the bundle is internally consistent, not who issued it: anyone can build a self-consistent bundle with their own keys. To rely on a receipt, pin or fetch Axiom's JWKS (issuerJwksUrl or issuerKeys) and the provider's outcome keys (providerJwksUrl or providerKeys), and set trustedIssuers. See receipts and verification.

Signer roles are kept apart. Without pinned provider keys, outcome attestations are checked against the bundle's provider_keys (the service's registered outcome keys), falling back to keys for older bundles. Every key that signed the receipt, and every pinned issuer key, is excluded from that set, so an Axiom key can never pass as the provider. The attestation's header must be typ: axiom-outcome+jws.

Keys and JWKS ​

SymbolDescription
generateAgentKey(alg = "ES256")A fresh ES256 or EdDSA key pair with its RFC 7638 thumbprint as kid (GeneratedKey: kid, privateJwk, publicJwk).
signerFromJwk(privateJwk)A Signer from a private JWK (as written by axiom keygen).
publicJwkOf(privateJwk)The public half, with its thumbprint as kid.
fetchJwks(url, fetch)Fetch a JWKS and return its keys (AnnotatedJwk, with axiom:role and axiom:status).
plainJwk(key)Strip the annotations for WebCrypto import.
JwksCacheCached JWKS (default 300 s) with refetch on an unknown kid (at most every 30 s). keysFor(role) and find(role, kid) exclude revoked keys and keys of other roles.
KeyRole"identity" | "grant" | "observation" | "policy" | "event".

Policy simulator ​

simulatePolicy({ baseUrl, apiKey, projectId, ...runtime }, input) calls POST /v1/projects/{id}/simulate with a project API key that has project.read. input is SimulateInput (service_id, operation, tenant_id, agent_id?, grant_id?, resource, params, environment?); the result's decision has effect (allow, deny, require_approval), reason_code and the rule trace. Nothing is created. Pass grant_id to apply that grant's rules (such as an amount threshold); without it, grant restrictions are skipped and the result's note says so. The CLI equivalent is axiom simulate --grant.

Key enrollment ​

Exported from packages/sdk/src/enroll.ts. Binding a key is an administrator action (agent.write), so these helpers act through a signed-in console session. They are a developer convenience for local and test setups; production enrollment belongs in the console or an automated registration flow.

SymbolDescription
consoleSession({ baseUrl, cookie, ...runtime })Open a ConsoleSession from an axiom_session cookie (axiom_session=… or the bare token). Reads the CSRF token from GET /auth/session; throws if the session is not signed in.
ConsoleSession{ baseUrl, json(method, path, body?) }: calls a console endpoint with the cookie and x-axiom-csrf, parses JSON, throws AxiomProblem on errors.
signPopChallenge(challenge, signer, agentId?)Compact JWS over challenge.sign.payload ({ agent_id, nonce, aud }) with header typ: challenge.sign.typ (axiom-pop+jws). Refuses a challenge for another agent when agentId is given.
bindAgentKey(session, { agentId, signer, issueCertificate?, validitySeconds? })Fetch a challenge, sign it, submit the public key and proof. Earlier active keys enter the rotation overlap window. Returns the new active BoundCredential (id, kind, thumbprint, status, not_before, not_after, certificate_pem?). A key that is already bound is a 409 conflict.
PopChallenge{ challenge_id, nonce, sign: { typ, payload }, expires_in }.
SESSION_COOKIE"axiom_session".
ts
import { readFile } from "node:fs/promises";
import { bindAgentKey, consoleSession, signerFromJwk } from "@sectigo/axiom-sdk";

const session = await consoleSession({ baseUrl: "https://api.sectigoaxiom.com", cookie: process.env.AXIOM_SESSION_COOKIE! });
const signer = await signerFromJwk(JSON.parse(await readFile(process.env.AXIOM_KEY_FILE!, "utf8")));
const cred = await bindAgentKey(session, { agentId: "agt_01kd0m6n3b4v5c6x7z8a9s0d1f", signer });
console.log(cred.id, cred.thumbprint, cred.not_after);

Errors ​

SymbolDescription
AxiomProblemAn error response. status, code (an ErrorCode or OAuth code: invalid_client, invalid_grant, invalid_request, unsupported_grant_type, invalid_dpop_proof), reasonCode, changedFields, actionId, issues, retryAfterSeconds, the raw problem, and isIdempotencyConflict.
TransportErrorNo HTTP response (network failure, abort, timeout). Whether the server acted is unknown: replay with the same key or read status.
problemFromBody(status, body)Build an AxiomProblem from a problem document or an OAuth error body.

Every code is explained in errors and reason codes.

Developer preview. Contract version 2026-11-14.