hcengineering/platform · warning

Missing or invalid field: plan

Error message

Missing or invalid field: plan

What it means

HTTP 400 returned by POST /api/v1/subscriptions/:subscriptionId/updatePlan when req.body.plan is missing or not a string (services/payment/pod-payment/src/server.ts:397-400). The handler destructures `{ plan }` from the JSON body and requires a string plan name such as 'common', 'rare', 'epic', or 'legendary'. This is input validation rejecting the request before any provider or DB calls.

Source

Thrown at services/payment/pod-payment/src/server.ts:398

    withToken,
    withLoginInfo,
    withOwner,
    (req: RequestWithAuth, res: Response) => {
      if (provider === undefined) {
        res.status(503).json({ error: 'Payment provider is not configured' })
        return
      }

      void handleRequest(
        ctx,
        'update-plan',
        async (ctx) => {
          const subscriptionId = req.params.subscriptionId
          const { plan } = req.body
          const loginInfo = req.loginInfo as WorkspaceLoginInfo

          if (plan === undefined || typeof plan !== 'string') {
            res.status(400).json({ error: 'Missing or invalid field: plan' })
            return
          }

          if (loginInfo?.workspaceUrl === undefined) {
            res.status(401).json({ error: 'Missing workspace url in login info' })
            return
          }

          // Get subscription from our database using internal ID
          const subscription = await accountClient.getSubscriptionById(subscriptionId)

          if (subscription === undefined || subscription === null) {
            res.status(404).json({ error: 'Subscription not found' })
            return
          }

          const accountUuid = subscription.accountUuid ?? req.token?.account
          if (accountUuid == null) {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Send a JSON body containing a string plan: { "plan": "pro" } with Content-Type: application/json.
  2. Validate the plan field on the client before calling (typeof plan === 'string').
  3. Confirm the plan name matches one of the valid plan identifiers ('common', 'rare', 'epic', 'legendary').
  4. If using a fetch/axios wrapper, ensure JSON.stringify is used and the content-type header is set.

Example fix

// before
await fetch(url, { method: 'POST', body: { plan: 2 } })
// after
await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ plan: 'epic' })
})
Defensive patterns

Strategy: validation

Validate before calling

function validateUpdatePlanBody(body: unknown): { plan: string } {
  const { plan } = (body ?? {}) as { plan?: unknown }
  if (typeof plan !== 'string' || plan.length === 0) throw new ValidationError('plan must be a non-empty string')
  return { plan }
}

Type guard

function hasValidPlan(body: unknown): body is { plan: string } {
  return typeof body === 'object' && body !== null && 'plan' in body && typeof (body as { plan: unknown }).plan === 'string'
}

Try / catch

try {
  const res = await fetch(`${base}/api/v1/subscriptions/${id}/updatePlan`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...auth },
    body: JSON.stringify({ plan })
  })
  if (res.status === 400) {
    const { error } = await res.json()
    throw new ClientError(error ?? 'invalid plan field')
  }
  return await res.json()
} catch (err) {
  logger.warn({ err }, 'updatePlan rejected')
  throw err
}

Prevention

When it happens

Trigger: POST .../updatePlan with no JSON body, body without a `plan` key, plan sent as a non-string (number, object, null), or request missing Content-Type: application/json so Express leaves body.plan undefined.

Common situations: Client forgot the JSON body or used form encoding; plan identifier renamed/typo'd on the client; sending plan id (number) instead of plan name (string); middleware/json parsing not configured on a custom client.

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 hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/1b1b23e791ed0d5e. Report an issue: GitHub.