Your First Envelope
A spend envelope is a signed, bounded authorization for an AI agent to spend money. Think of it as a signed cheque with strict limits — the agent can only cash it within the bounds you define.
Full envelope example
import { createSpendEnvelope } from '@pqsafe/agent-pay'
const envelope = createSpendEnvelope({ // --- Identity --- agentId: 'procurement-agent-v2', // Which agent this is issued to issuedBy: 'ops-team@example.com', // Who authorized this envelope
// --- Amount bounds --- maxAmount: 100, // Hard cap: agent cannot spend more than $100 currency: 'USD', // ISO 4217 currency code
// --- Time bounds --- validFrom: new Date(), // Optional: not valid before this time validUntil: new Date(Date.now() + 86_400_000), // Expires in 24 hours
// --- Destination constraints --- allowedRecipients: [ // Allowlist — agent can only pay these 'anthropic.com', 'openai.com', ],
// --- Rail constraints --- allowedRails: ['airwallex', 'stripe'], // Which payment rails may be used
// --- Approval gate --- requireApproval: false, // Set true to require human approval before dispatch approvalThreshold: 50, // Auto-approve below $50, require human above
// --- Memo --- memo: 'AI model API costs — Q3 2026',})Field reference
| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | Identifier for the agent instance. Appears in ledger entries. |
maxAmount | number | Yes | Hard cap on spending. SDK rejects any payment request exceeding this. |
currency | string | Yes | ISO 4217 code (USD, EUR, GBP, USDC). |
validUntil | Date | Yes | Envelope expires at this time. Expired envelopes are rejected at signature verification. |
allowedRecipients | string[] | Yes | Domain or address allowlist. Must match recipient in executeAgentPayment(). |
allowedRails | RailName[] | Yes | Which payment rails may be used. |
requireApproval | boolean | No | If true, payment pauses until a human approves via Telegram/webhook. |
approvalThreshold | number | No | Auto-approve below this amount; require approval above it. |
memo | string | No | Human-readable purpose. Appears in ledger and bank reference. |
validFrom | Date | No | Envelope not valid before this time. Useful for deferred authorizations. |
issuedBy | string | No | Issuer identifier. Useful for audit trails in multi-team environments. |
What happens at signing
When you call createSignedEnvelope(envelope, secretKey):
- The envelope is serialized to Canonical JSON (deterministic key ordering, no whitespace)
- An ML-DSA-65 signature is computed over the canonical bytes
- The signature is bound to this exact envelope — any modification invalidates it
- A unique
envelopeIdis derived from the hash of the canonical JSON
import { createSignedEnvelope, verifyEnvelope } from '@pqsafe/agent-pay'
const signed = createSignedEnvelope(envelope, secretKey)
// Verify (useful for debugging or at the receiving end)const isValid = verifyEnvelope(signed, publicKey)console.log('Signature valid:', isValid) // trueEnvelope lifecycle
Created → Signed → [Approval gate if required] → Dispatched → Settled → Archived in ledger ↑ Human approves via TelegramEnvelopes can only be used once per payment (no replay). The ledger records every attempt.