{"record":{"id":"9efb32ea23314fdd","repo":"n8n-io/n8n","slug":"invalid-nps-survey-state-structure","errorCode":null,"errorMessage":"Invalid nps survey state structure","messagePattern":"Invalid nps survey state structure","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"warning","filePath":"packages/cli/src/controllers/user-settings.controller.ts","lineNumber":46,"sourceCode":"\t\treturn {\n\t\t\twaitingForResponse: true,\n\t\t\tignoredCount: state.ignoredCount,\n\t\t\tlastShownAt: state.lastShownAt,\n\t\t};\n\t}\n\n\treturn;\n}\n\n@RestController('/user-settings')\nexport class UserSettingsController {\n\tconstructor(private readonly userService: UserService) {}\n\n\t@Patch('/nps-survey')\n\tasync updateNpsSurvey(req: NpsSurveyRequest.NpsSurveyUpdate): Promise<void> {\n\t\tconst state = getNpsSurveyState(req.body);\n\t\tif (!state) {\n\t\t\tthrow new BadRequestError('Invalid nps survey state structure');\n\t\t}\n\n\t\tawait this.userService.updateSettings(req.user.id, {\n\t\t\tnpsSurvey: state,\n\t\t});\n\t}\n}\n","sourceCodeStart":28,"sourceCodeEnd":54,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/cli/src/controllers/user-settings.controller.ts#L28-L54","documentation":"Thrown by PATCH /user-settings/nps-survey when req.body is not a structurally valid NPS survey state. The local getNpsSurveyState() validator only accepts two shapes: a 'responded' state ({responded:true, lastShownAt:number}) or a 'waitingForResponse' state ({waitingForResponse:true, ignoredCount:number, lastShownAt:number}); both require lastShownAt to be a number. Any other object, non-object, missing fields, or wrong types causes this BadRequestError (HTTP 400).","triggerScenarios":"Calling PATCH /user-settings/nps-survey with a body that is not an object, lacks lastShownAt, has lastShownAt as a non-number, or declares neither responded:true nor the waitingForResponse+ignoredCount combo (e.g. {responded:true} alone, or {lastShownAt:'2024-01-01'}).","commonSituations":"Frontend NPS survey component sending a partial state object during an A/B test variant, a stale frontend shipping an outdated state schema after a backend bump, hand-crafted curl/automation with malformed JSON, or a serializer stripping numeric fields to strings.","solutions":["Ensure the request body matches one of the two accepted shapes exactly: {responded:true,lastShownAt:<number>} OR {waitingForResponse:true,ignoredCount:<number>,lastShownAt:<number>}.","Verify lastShownAt is a numeric Unix-style timestamp (ms), not an ISO string or Date.","If adding a new state variant, extend getNpsSurveyState() in user-settings.controller.ts and the NpsSurveyState type in n8n-workflow before relying on it."],"exampleFix":"// before\nawait patch('/user-settings/nps-survey', { responded: true });\n// after\nawait patch('/user-settings/nps-survey', {\n  responded: true,\n  lastShownAt: Date.now(),\n});","handlingStrategy":"validation","validationCode":"function buildNpsState(input) {\n  if (typeof input.lastShownAt !== 'number' || !Number.isFinite(input.lastShownAt)) {\n    throw new Error('lastShownAt must be a finite number');\n  }\n  if (input.responded === true) return { responded: true, lastShownAt: input.lastShownAt };\n  if (input.waitingForResponse === true && typeof input.ignoredCount === 'number') {\n    return { waitingForResponse: true, ignoredCount: input.ignoredCount, lastShownAt: input.lastShownAt };\n  }\n  throw new Error('State must be responded:true or waitingForResponse:true+ignoredCount:number');\n}\n// call before PATCH /user-settings/nps-survey\nconst body = buildNpsSurveyState(rawState);","typeGuard":"function isNpsSurveyState(s: unknown): s is { lastShownAt: number } & (\n  | { responded: true }\n  | { waitingForResponse: true; ignoredCount: number }\n) {\n  if (typeof s !== 'object' || s === null) return false;\n  const o = s as Record<string, unknown>;\n  if (typeof o.lastShownAt !== 'number') return false;\n  if (o.responded === true) return true;\n  return o.waitingForResponse === true && typeof o.ignoredCount === 'number';\n}","tryCatchPattern":null,"preventionTips":["Always set lastShownAt to Date.now() (a number), never an ISO string.","Send exactly one of the two accepted state variants; never both.","Mirror getNpsSurveyState() logic in the client to fail before the round-trip."],"tags":["validation","user-settings","nps-survey","rest-api","bad-request"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}