#14290: feat: x402 payment info parsing and configurable billing recovery [AI]
agents
stale
size: L
Cluster:
Rate Limit Management Enhancements
## Summary
Billing/402 errors currently disable auth profiles for **5–24 hours** with exponential backoff. This is correct for traditional API keys where top-up requires manual action, but too aggressive for prepaid credit systems where an agent can top up in minutes via x402-compatible endpoints.
This PR adds three capabilities:
- **x402 payment info extraction** — new `parseX402PaymentInfo()` function parses structured payment data (top-up URL, balance, payment scheme) from billing error response bodies that follow the [x402 v2 protocol](https://x402.org)
- **Rich billing error messages** — `formatBillingErrorMessage()` replaces the generic "check your dashboard" message with actionable info: remaining balance and a direct top-up URL when available
- **Configurable billing recovery mode** — new config option `auth.cooldowns.billingRecoveryMode` with three modes:
- `"disable"` (default): existing behavior, 5–24h exponential backoff
- `"retry"`: 5 minute cooldown, for systems with fast automated top-up
- `"notify"`: no cooldown at all, profile stays available immediately
The x402 payment info is also propagated through `FailoverError.paymentInfo` so higher-level consumers (agent frameworks, skills, UIs) can programmatically access the top-up URL and balance without parsing error message text.
## Motivation
Agents using prepaid credit gateways (e.g., OpenClaw Fuel via Bifrost) hit a dead end when credits run out: the profile gets disabled for hours, all fallback models through the same gateway fail, and the user sees a generic "check your dashboard" message. With x402 support:
1. The agent sees *exactly* what happened: `$0.00 of $20.00 remaining`
2. The agent knows *where* to top up: `https://openclaw.rocks/fuel`
3. With `billingRecoveryMode: "retry"`, the profile is available again after 5 minutes instead of 5 hours
4. With `billingRecoveryMode: "notify"`, the profile stays available immediately for retry after top-up
## Changes
| File | Change |
|---|---|
| `src/agents/pi-embedded-helpers/types.ts` | Add `X402PaymentInfo` type |
| `src/agents/pi-embedded-helpers/errors.ts` | Add `parseX402PaymentInfo()` and `formatBillingErrorMessage()`; use them in `formatAssistantErrorText()` and `sanitizeUserFacingText()` |
| `src/agents/pi-embedded-helpers.ts` | Export new symbols |
| `src/agents/failover-error.ts` | Add `paymentInfo` field to `FailoverError`; extract x402 info in `coerceToFailoverError()` |
| `src/agents/pi-embedded-runner/run.ts` | Pass x402 payment info through billing `FailoverError` throws |
| `src/config/types.auth.ts` | Add `billingRecoveryMode` to `AuthConfig.cooldowns` |
| `src/config/zod-schema.ts` | Add `billingRecoveryMode` enum validation |
| `src/config/schema.hints.ts` | Add hint for `billingRecoveryMode` |
| `src/config/schema.field-metadata.ts` | Add label and description for `billingRecoveryMode` |
| `src/agents/auth-profiles/usage.ts` | Implement `"retry"` (5min cooldown) and `"notify"` (no cooldown) modes; clear stale state on mode switch |
## Config example
```yaml
auth:
cooldowns:
billingRecoveryMode: retry # 5 min cooldown instead of 5 hours
billingBackoffHoursByProvider:
bifrost: 0.08 # ~5 min (still works as before for per-provider override)
```
## Test plan
- [x] `parseX402PaymentInfo` — 8 test cases covering JSON extraction, HTTP prefix, x402 `accepts` array, edge cases
- [x] `formatBillingErrorMessage` — 4 test cases covering generic fallback, balance + URL, partial info
- [x] `FailoverError.paymentInfo` — 3 test cases covering x402 extraction, non-billing exclusion, plain billing errors
- [x] `billingRecoveryMode` — 5 test cases: `"disable"`, `"retry"`, `"notify"` modes + mode-switch regression tests (disable→notify, disable→retry)
- [x] Full suite: `pnpm build && pnpm check && pnpm test` — 267 tests pass, 0 failures
- [x] TypeScript compiles with zero errors
- [x] Formatting + lint (oxlint) pass with zero errors/warnings
## Backwards compatibility
- Default `billingRecoveryMode` is `"disable"` — existing behavior is unchanged
- `X402PaymentInfo` is additive (new optional field on `FailoverError`)
- Error messages are enriched only when x402 info is present; generic message unchanged otherwise
- All existing tests pass without modification
---
## AI Disclosure
- [x] **AI-assisted**: This PR was written with Claude Code (Claude Opus 4.6)
- [x] **Degree of testing**: Fully tested — 20 new tests across 3 test files, plus full `pnpm build && pnpm check && pnpm test` pipeline (267 tests, 0 failures)
- [x] **I understand what this code does**: `parseX402PaymentInfo` extracts structured payment metadata from JSON error bodies; `formatBillingErrorMessage` renders actionable user-facing messages from that metadata; `billingRecoveryMode` gates the cooldown logic in `computeNextProfileUsageStats` between three strategies (exponential disable, short cooldown, or no cooldown). The x402 info propagates through `FailoverError.paymentInfo` so consumers don't need to re-parse error text.
Most Similar PRs
#12273: fix: prevent billing error false positive on bare '402' in chat con...
by Yida-Dev · 2026-02-09
72.9%
#14574: fix: gentler rate-limit cooldown backoff + clear stale cooldowns on...
by JamesEBall · 2026-02-12
72.4%
#22898: feat(skills): add x402engine — invisible service access for 70+ pai...
by agentc22 · 2026-02-21
72.0%
#16307: fix: surface billing/auth FailoverErrors as user-friendly messages
by petter-b · 2026-02-14
69.8%
#15109: fix: distinguish transient API errors from billing errors
by jwchmodx · 2026-02-13
69.1%
#9173: Fix: Improve error messaging for API rate limits and billing errors
by vishaltandale00 · 2026-02-04
69.0%
#19267: fix: derive failover reason from timedOut flag to prevent unknown c...
by austenstone · 2026-02-17
68.6%
#19252: fix(agents): continue model fallback on failover text payloads
by mahsumaktas · 2026-02-17
68.2%
#20262: fix: detect Venice 402 insufficient USD/Diem balance as billing fai...
by ZPTDclaw · 2026-02-18
68.1%
#18670: feat: add first-class Claude Code CLI auth path + CLI model UX hard...
by SmithLabsLLC · 2026-02-16
67.9%