Skip to content

API Overview

The @pqsafe/agent-pay package exports a small, focused API surface organized into five functional areas. This page provides a navigable overview; auto-generated TypeDoc documentation for every exported symbol lives in the API Reference section.

Installation

Terminal window
npm install @pqsafe/agent-pay

Functional areas

Key Management

Generate and rotate ML-DSA-65 key pairs. Issue agent subkeys with narrower permissions.

Envelope Lifecycle

Create, sign, verify, and revoke spend envelopes.

Payment Execution

Dispatch payments to five rails: Airwallex, Wise, Stripe, USDC Base, x402.

Approval Gates

Request and resolve human-in-the-loop approvals via Telegram, Slack, or webhook.

Ledger

Build and submit tamper-evident ledger records for every payment attempt.


Key management

generateKeyPair()

Generate a fresh ML-DSA-65 key pair. Uses a cryptographically secure 32-byte seed.

import { generateKeyPair } from '@pqsafe/agent-pay'
const { publicKey, secretKey } = await generateKeyPair()
// publicKey: hex string, 3904 chars (1952 bytes)
// secretKey: hex string, 8064 chars (4032 bytes)

Key sizes (ML-DSA-65 / FIPS 204):

KeyBytesHex chars
Secret key4,0328,064
Public key1,9523,904
Signature3,2936,586

createAgentSubkey()

Derive a spend key with reduced permissions from an issuer root key.

import { createAgentSubkey } from '@pqsafe/agent-pay'
const subkey = await createAgentSubkey(rootKey, {
agentId: 'procurement-agent-v2',
maxAmountCap: 500, // Sub-key cannot sign envelopes above $500
expiresAt: new Date(Date.now() + 7 * 86_400_000), // 7-day key
})

rotateSpendKey()

Replace a spend key while preserving the issuer hierarchy. Old key is revoked.

import { rotateSpendKey } from '@pqsafe/agent-pay'
const { newPublicKey, newSecretKey } = await rotateSpendKey(oldSecretKey, {
reason: 'scheduled-rotation',
})

Envelope lifecycle

createSpendEnvelope()

Build an unsigned SpendEnvelope object from a set of authorization parameters.

import { createSpendEnvelope } from '@pqsafe/agent-pay'
const envelope = createSpendEnvelope({
agentId: 'my-agent',
maxAmount: 100,
currency: 'USD',
allowedRails: ['airwallex'],
allowedRecipients: ['vendor.com'],
validUntil: new Date(Date.now() + 3_600_000),
})

SpendEnvelope type:

type SpendEnvelope = {
envelopeId: string // SHA-256 of canonical JSON (auto-computed)
agentId: string
issuedBy?: string
maxAmount: number
currency: string // ISO 4217 or 'USDC'
validFrom?: Date
validUntil: Date
allowedRecipients: string[]
allowedRails: Rail[]
requireApproval?: boolean
approvalThreshold?: number
memo?: string
}

createSignedEnvelope()

Sign an envelope with ML-DSA-65. Returns a SignedEnvelope with the hex-encoded signature attached.

import { createSignedEnvelope } from '@pqsafe/agent-pay'
const signed = createSignedEnvelope(envelope, secretKey)
// signed.signature: 6586-char hex string
// signed.envelopeId: SHA-256 of JCS canonical JSON

The signature is computed over the JCS (RFC 8785) canonical JSON of the envelope — deterministic key ordering, no whitespace, no trailing commas. Any modification to the envelope after signing invalidates the signature.

verifyEnvelope()

Verify an ML-DSA-65 signature. Returns true if valid.

import { verifyEnvelope } from '@pqsafe/agent-pay'
const valid = verifyEnvelope(signed, publicKey)
// true | false

revoke()

Mark an envelope as revoked in the PQSafe revocation service. Revoked envelopes cannot be dispatched.

import { revoke } from '@pqsafe/agent-pay'
await revoke(signed.envelopeId, {
reason: 'key-compromise',
revokedBy: 'security-team',
})

isRevoked()

Check whether an envelope has been revoked.

import { isRevoked } from '@pqsafe/agent-pay'
const revoked = await isRevoked(signed.envelopeId)

Payment execution

executeAgentPayment()

The primary payment dispatch function. Validates the envelope, checks revocation, enforces all invariants, dispatches to the appropriate rail, and returns a PaymentResult.

import { executeAgentPayment } from '@pqsafe/agent-pay'
const result = await executeAgentPayment(signed, {
recipient: 'vendor.com/billing',
amount: 50,
memo: 'Invoice #INV-2026-042',
})
// result.status: 'settled' | 'pending' | 'failed'
// result.txId: rail-specific transaction ID
// result.rail: 'airwallex' | 'wise' | 'stripe' | 'usdc-base' | 'x402'
// result.settledAt: Date (if settled)
// result.errorCode: PQSafeErrorCode (if failed)

Invariants checked before dispatch (in order):

  1. Signature valid (ML-DSA-65 verify)
  2. validUntil not exceeded
  3. validFrom reached (if set)
  4. amount ≤ maxAmount
  5. recipient in allowedRecipients
  6. rail in allowedRails
  7. Envelope not revoked (revocation service check)
  8. Approval granted (if requireApproval: true)

All eight checks must pass. There is no bypass mechanism.

Supported rails

RailRail valueCurrencyTypical use
Airwallex'airwallex'USD, EUR, GBP, HKD, AUDCross-border, ACH, wire
Wise'wise'40+ currenciesInternational wire
Stripe'stripe'USD, EUR, GBPCard-on-file, ACP
USDC Base'usdc-base'USDCOn-chain, crypto-native
x402'x402'USDC, ETHHTTP 402 micropayments

Approval gates

requestApproval()

Initiate a human-in-the-loop approval request for a pending payment.

import { requestApproval } from '@pqsafe/agent-pay'
const request = await requestApproval(signed, {
channel: 'telegram',
chatId: process.env.TELEGRAM_CHAT_ID!,
message: `Payment of $${amount} to ${recipient} requires your approval.`,
expiresIn: 3_600_000, // 1 hour (must be < envelope validUntil)
})
// request.approvalId: string
// request.status: 'pending'

getApprovalStatus()

Poll or check the current status of an approval request.

import { getApprovalStatus } from '@pqsafe/agent-pay'
const status = await getApprovalStatus(request.approvalId)
// status.state: 'pending' | 'approved' | 'rejected' | 'expired'

executeWithApproval()

High-level helper that combines payment execution with approval gating — blocks until approved or times out.

import { executeWithApproval } from '@pqsafe/agent-pay'
const result = await executeWithApproval(signed, {
recipient: 'vendor.com/billing',
amount: 250,
memo: 'Q2 services invoice',
approval: {
channel: 'telegram',
chatId: process.env.TELEGRAM_CHAT_ID!,
timeoutMs: 3_600_000,
},
})

Ledger

buildLedgerRecord()

Construct a LedgerRecord from a signed envelope and payment result.

import { buildLedgerRecord } from '@pqsafe/agent-pay'
const record = buildLedgerRecord(signed, result)
// record.envelopeId
// record.agentId
// record.recipient
// record.amount
// record.currency
// record.rail
// record.status
// record.txId
// record.timestamp
// record.signatureFingerprint ← first 16 hex chars of signature

submitToLedger()

Append a ledger record to the tamper-evident ledger. Returns the hash and sequence number.

import { submitToLedger } from '@pqsafe/agent-pay'
const entry = await submitToLedger(record)
// entry.hash: SHA-256 of record + previous hash (chain integrity)
// entry.sequence: monotonically increasing integer
// entry.timestamp: server-side timestamp

Configuration

setAgentPayConfig()

Set global SDK configuration. Call once at application startup.

import { setAgentPayConfig } from '@pqsafe/agent-pay'
setAgentPayConfig({
issuerKey: process.env.PQSAFE_ISSUER_KEY,
defaultRail: 'airwallex',
logLevel: 'info',
revocationService: {
url: 'https://revoke.pqsafe.xyz',
timeoutMs: 2_000,
},
webhooks: {
envelopeConsumed: {
threshold: 0.8, // Alert at 80% of maxAmount consumed
url: 'https://your-server.com/webhooks/pqsafe',
},
},
})

Error reference

All PQSafe errors extend PQSafeError and include a code property.

import { PQSafeError } from '@pqsafe/agent-pay'
try {
await executeAgentPayment(signed, params)
} catch (err) {
if (err instanceof PQSafeError) {
console.error(err.code) // e.g. 'ENVELOPE_EXPIRED'
console.error(err.message) // human-readable description
}
}
Error codeMeaning
ENVELOPE_SIGNATURE_INVALIDML-DSA-65 verification failed — envelope was tampered
ENVELOPE_EXPIREDvalidUntil has passed
ENVELOPE_NOT_YET_ACTIVEvalidFrom is in the future
AMOUNT_EXCEEDS_CEILINGamount > maxAmount
RECIPIENT_NOT_ALLOWEDrecipient not in allowedRecipients
RAIL_NOT_ALLOWEDRail not in allowedRails
ENVELOPE_REVOKEDEnvelope was revoked via revoke()
APPROVAL_REQUIREDrequireApproval is true and no approval token provided
APPROVAL_REJECTEDHuman explicitly rejected the payment
APPROVAL_TIMEOUTApproval request expired before human responded
RAIL_ERRORPayment rail returned an error (see err.railCode)
RATE_LIMITToo many requests — back off and retry

Auto-generated API reference

Full TypeDoc-generated documentation for every exported symbol, interface, type alias, and error class:

API Reference (auto-generated)

The API reference is regenerated on every SDK release from JSDoc comments in agent-pay/src/. If you find a discrepancy between this overview and the auto-generated docs, the auto-generated docs are authoritative.