Symptom
GET /api/billing/plans is public and returns every active Stripe product as a plan, not only the configured subscription plans. One-time products (e.g. compute packs sold through the extras endpoint) come back as plans with monthlyPrice: 0 and stripePriceMonthly: null.
Root cause
modules/billing/services/billing.plans.service.js, fetchPlansFromStripe (line ~74):
const planId = product.metadata?.planId || product.id;
A product without metadata.planId falls back to its raw Stripe product id, so anything active in the Stripe account advertises itself as a plan.
Impact
No UI defect when the pricing page selects by planId. The problem is the public API contract: a CLI, MCP client or agent that iterates this endpoint sees entries that are not plans; a checkout built from one carries a null price id.
Fix
Keep only products carrying metadata.planId and drop the raw-id fallback (the real plans already follow that convention). Add a unit test with a mixed catalogue (plan products + one-time products).
Found downstream during a QA pass; reproduces on current master.
Symptom
GET /api/billing/plansis public and returns every active Stripe product as a plan, not only the configured subscription plans. One-time products (e.g. compute packs sold through the extras endpoint) come back as plans withmonthlyPrice: 0andstripePriceMonthly: null.Root cause
modules/billing/services/billing.plans.service.js,fetchPlansFromStripe(line ~74):A product without
metadata.planIdfalls back to its raw Stripe product id, so anything active in the Stripe account advertises itself as a plan.Impact
No UI defect when the pricing page selects by
planId. The problem is the public API contract: a CLI, MCP client or agent that iterates this endpoint sees entries that are not plans; a checkout built from one carries a null price id.Fix
Keep only products carrying
metadata.planIdand drop the raw-id fallback (the real plans already follow that convention). Add a unit test with a mixed catalogue (plan products + one-time products).Found downstream during a QA pass; reproduces on current
master.