VendorStacks
On this section
← All guides

Handle 402s and top-ups gracefully

The paywall anatomy, why nothing is ever half-charged, and the retry-after-payment pattern for scripts and agents.

Markdown mirror: /docs/guides/handle-402s.md

Every VendorStacks request is priced before it runs. If the key's balance can't cover it, the API returns 402 payment_required and charges nothing — there is no partial state to clean up, which makes the retry logic trivial.

Anatomy

Example
HTTP/1.1 402 Payment Required
X-Request-Id: 9f6f4a1e-…

{
  "error": "payment_required",
  "message": "This request costs 1 credit; balance is 0.",
  "remaining_credits": 3,
  "credits_needed": 10,
  "topup_url": "https://vendorstacks.com/app/billing"
}
  • credits_needed is the price of *this* request, not a suggested pack size.
  • topup_url goes straight to billing — pass it to whoever holds the card.
  • The failed request is safe to retry byte-for-byte after top-up.

Script pattern: pause, poll, resume

retry-after-payment
async function callWithTopupPause(url, opts, key) {
  for (;;) {
    const res = await fetch(url, opts);
    if (res.status !== 402) return res;

    const { topup_url, credits_needed } = await res.json();
    console.error(`Paused: need ${credits_needed} credits → ${topup_url}`);

    // Poll the free balance endpoint until a top-up lands, then resume.
    for (;;) {
      await new Promise(r => setTimeout(r, 30_000));
      const bal = await fetch("https://vendorradar-production.up.railway.app
/v1/balance", {
        headers: { Authorization: `Bearer ${key}` },
      }).then(r => r.json());
      if (bal.credit_balance >= credits_needed) break;
    }
  }
}

/v1/balance is free to call, so the poll loop costs nothing. For agents, don't poll — relay topup_url to the human and end the turn; see the agent guide. For batch jobs, checking /v1/pricing and /v1/balance up front lets you fail before doing half the work.