Skip to content

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 publicKey passed to verifyEnvelope()
  • Envelope serialized/deserialized with different JSON libraries (use canonicalizeEnvelope())

Fix:

// ❌ Wrong: mutating envelope after signing
const signed = createSignedEnvelope(envelope, secretKey)
envelope.memo = 'updated' // Invalidates signature!
// ✅ Correct: finalize before signing
const 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 allowedRecipients
const 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>