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
- Read parsed.issues from the error response and fix the offending fields in the request body.
- Validate the body against CheckoutBodySchema on the client before sending.
- Ensure stripePriceId is a Stripe price ID string (price_...) and currency is a supported lowercase code.
- 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
- Share CheckoutBodySchema between client and server and validate before sending.
- Use TypeScript types generated from the schema for request payloads.
- Add integration tests that exercise the real schema.
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
- INVALID_PACKAGE
- MISSING_SIGNATURE
- The file is not an AIRI Live2D motion recording.
- WEBHOOK_ERROR
- A motion sample occurs after the recording duration.
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)