Common Errors
Error code reference
ENVELOPE_SIGNATURE_INVALID
Cause: The ML-DSA-65 signature doesn’t match the envelope’s canonical JSON.
Common reasons:
- Envelope object mutated after signing
- Wrong
publicKeypassed toverifyEnvelope() - Envelope serialized/deserialized with different JSON libraries (use
canonicalizeEnvelope())
Fix:
// ❌ Wrong: mutating envelope after signingconst signed = createSignedEnvelope(envelope, secretKey)envelope.memo = 'updated' // Invalidates signature!
// ✅ Correct: finalize before signingconst envelope = createSpendEnvelope({ memo: 'final value', ...opts })const signed = createSignedEnvelope(envelope, secretKey)ENVELOPE_EXPIRED
Cause: envelope.validUntil has passed.
Fix: Create a new envelope with a future validUntil. Expired envelopes cannot be renewed.
const envelope = createSpendEnvelope({ validUntil: new Date(Date.now() + 3_600_000), // 1 hour from now ...})ENVELOPE_ALREADY_USED
Cause: An envelope with this envelopeId was already dispatched. Single-use semantics.
Fix: Create a new envelope for each payment.
AMOUNT_EXCEEDS_ENVELOPE
Cause: request.amount > envelope.maxAmount.
Fix: Either increase maxAmount in a new envelope, or reduce the payment amount.
RECIPIENT_NOT_ALLOWED
Cause: request.recipient is not in envelope.allowedRecipients.
Fix: Check exact string match (case-insensitive, no trailing slashes):
// ❌ envelope has 'anthropic.com' but request uses 'anthropic.com/billing'// ✅ Add 'anthropic.com/billing' to allowedRecipientsconst envelope = createSpendEnvelope({ allowedRecipients: ['anthropic.com', 'anthropic.com/billing'], ...})RAIL_NOT_ALLOWED
Cause: request.rail is not in envelope.allowedRails.
Fix: Add the rail to allowedRails in a new envelope, or use an allowed rail.
APPROVAL_REQUIRED
Cause: envelope.requireApproval = true and payment exceeds approvalThreshold, but no approval has been received.
Fix: Use executeWithApproval() instead of executeAgentPayment() for envelopes with requireApproval: true.
RAIL_TIMEOUT
Cause: The payment rail API did not respond within the timeout window.
Fix: Implement retry with exponential backoff:
import { PQSafeError } from '@pqsafe/agent-pay'
for (let i = 0; i < 3; i++) { try { return await executeAgentPayment(signed, req) } catch (err) { if (err instanceof PQSafeError && err.code === 'RAIL_TIMEOUT' && i < 2) { await new Promise(r => setTimeout(r, 2 ** i * 1000)) continue } throw err }}INSUFFICIENT_BALANCE
Cause: The rail source account doesn’t have enough funds.
Fix: Top up your rail account balance. Check:
- Airwallex: Dashboard → Balances
- Wise: Dashboard → Account balances
- USDC on Base:
cast balance --erc20 <USDC_ADDRESS> <WALLET>