moeru-ai/airi · error · ApiError

INVALID_REQUEST

INVALID_REQUEST

Error message

Invalid checkout request

What it means

The checkout request body failed Valibot validation against CheckoutBodySchema. The API rejects it with 400 INVALID_REQUEST and includes the parsed issues, so the client sent a body that does not match the expected shape (missing/invalid stripePriceId or currency).

Solutions

  1. Read parsed.issues from the error response and fix the offending fields in the request body.
  2. Validate the body against CheckoutBodySchema on the client before sending.
  3. Ensure stripePriceId is a Stripe price ID string (price_...) and currency is a supported lowercase code.
  4. Update the client if the API schema changed.

Example fix

// before
fetch('/checkout', { body: JSON.stringify({ priceId: 'price_x', Currency: 'USD' }) })
// after
fetch('/checkout', { body: JSON.stringify({ stripePriceId: 'price_x', currency: 'usd' }) })
Defensive patterns

Strategy: validation

Validate before calling

import * as v from 'valibot'
const parsed = v.safeParse(CheckoutBodySchema, body)
if (!parsed.success) {
  // fix body before sending; log parsed.issues
}

Try / catch

try {
  await checkout(body)
} catch (e) {
  if (e.code === 'INVALID_REQUEST') {
    // map e.issues to form field errors
  }
}

Prevention

When it happens

Trigger: POSTing to checkout with a body missing required fields (stripePriceId, currency), wrong types (e.g. number instead of string), or malformed JSON structure.

Common situations: Frontend sending snake_case vs camelCase mismatch, forgetting a field after a schema change, sending an old client version against an updated API schema, or tests hitting the endpoint with hand-written payloads.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/69ffbe4860b25d42. Report an issue: GitHub.

Appendix: source

Thrown at server/apps/api/src/routes/stripe/operations/checkout.ts:36

  stripe: Stripe | null,
  priceCatalog: StripePriceCatalog | null,
  configKV: ConfigKVService,
  env: Env,
  metrics: RevenueMetrics | null,
  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,

View on GitHub (pinned to 438a067dde)