moeru-ai/airi · error · ApiError
INVALID_PACKAGE
INVALID_PACKAGE
Error message
Invalid price
What it means
The submitted stripePriceId is valid syntactically but priceCatalog.findActivePrice found no active price matching it for the configured flux product. The API rejects with 400 INVALID_PACKAGE because checkout cannot bill against an unknown, inactive, or foreign price.
Solutions
- Use a price ID that belongs to the product configured as STRIPE_FLUX_PRODUCT_ID and is currently active.
- Fetch the current active price from the price catalog/API instead of hardcoding it in the client.
- Verify you are hitting the right environment (test vs live mode price IDs differ).
- Re-create/activate the price in Stripe if it was archived unintentionally.
Example fix
// before
body: { stripePriceId: 'price_old_archived', currency: 'usd' }
// after
body: { stripePriceId: await fetchActivePriceId(), currency: 'usd' } Defensive patterns
Strategy: validation
Validate before calling
// Only offer prices known to be active and belonging to the flux product
const activePriceIds = new Set((await fetchCatalogPrices()).map(p => p.id))
if (!activePriceIds.has(stripePriceId)) throw new Error('price not in active catalog') Try / catch
try {
await checkout({ stripePriceId, currency })
} catch (e) {
if (e.code === 'INVALID_PACKAGE') {
// refresh price catalog and ask user to reselect
}
} Prevention
- Never hardcode Stripe price IDs in clients; fetch them from the catalog API.
- Refresh cached prices on app start and after pricing changes.
- Keep test-mode and live-mode price IDs in separate config.
When it happens
Trigger: POSTing checkout with a stripePriceId that belongs to a different Stripe product than STRIPE_FLUX_PRODUCT_ID, an archived/inactive price, or a typo/nonexistent price ID.
Common situations: Hardcoded price IDs left over after re-creating prices in Stripe dashboard; staging client pointing at production price IDs (or vice versa); price archived during a pricing change while clients still cache it.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/55464ddeb75cf66e.
Report an issue: GitHub.
Appendix: source
Thrown at server/apps/api/src/routes/stripe/operations/checkout.ts:41
productEventService: ProductEventService | null,
) {
return async (
user: { id: string, email: string },
body: unknown,
request: Request,
): Promise<{ url: string }> => {
const fluxProductId = await configKV.getOptional('STRIPE_FLUX_PRODUCT_ID')
if (!stripe || !priceCatalog || !fluxProductId)
throw createServiceUnavailableError('Stripe is not configured', 'STRIPE_NOT_CONFIGURED')
const parsed = safeParse(CheckoutBodySchema, body)
if (!parsed.success)
throw createBadRequestError('Invalid checkout request', 'INVALID_REQUEST', parsed.issues)
const { stripePriceId, currency } = parsed.output
const price = await priceCatalog.findActivePrice(fluxProductId, stripePriceId)
if (!price)
throw createBadRequestError('Invalid price', 'INVALID_PACKAGE', { stripePriceId })
const fluxAmount = Number(price.metadata.fluxAmount)
if (!Number.isFinite(fluxAmount) || fluxAmount <= 0)
throw createBadRequestError('Price is missing fluxAmount metadata', 'INVALID_PACKAGE', { stripePriceId })
const redirectBase = resolveCheckoutRedirectBase(request, env.ADDITIONAL_TRUSTED_ORIGINS, env.WEB_APP_URL)
const openpanelIdentity = readOpenpanelIdentityHeaders(request)
const order = await payment.openPending({
userId: user.id,
processor: 'stripe',
packKey: stripePriceId,
fluxAmount,
currency,
})
const sessionParams: Stripe.Checkout.SessionCreateParams = {
line_items: [{ price: stripePriceId, quantity: 1 }],View on GitHub (pinned to 438a067dde)