{"record":{"id":"6f23f501cef52e78","repo":"hcengineering/platform","slug":"err-code-6f23f5","errorCode":"err.code","errorMessage":"err.message","messagePattern":"err\\.message","errorType":"http","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"services/notification/pod-notification/src/server.ts","lineNumber":52,"sourceCode":"\n  app.use(cors())\n  app.use(express.json())\n\n  endpoints.forEach((endpoint) => {\n    if (endpoint.type === 'get') {\n      app.get(endpoint.endpoint, catchError(endpoint.handler))\n    } else if (endpoint.type === 'post') {\n      app.post(endpoint.endpoint, catchError(endpoint.handler))\n    }\n  })\n\n  app.use((_req, res, _next) => {\n    res.status(404).send({ message: 'Not found' })\n  })\n\n  app.use((err: any, _req: any, res: any, _next: any) => {\n    if (err instanceof ApiError) {\n      res.status(400).send({ code: err.code, message: err.message })\n      return\n    }\n\n    res.status(500).send({ message: err.message })\n  })\n\n  return app\n}\n\nexport function listen (e: Express, port: number, host?: string, onListening?: () => void): Server {\n  const cb = (): void => {\n    if (onListening !== undefined) {\n      onListening()\n    } else {\n      console.log(`Notification service has been started at ${host ?? '*'}:${port}`)\n    }\n  }\n","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/notification/pod-notification/src/server.ts#L34-L70","documentation":"The pod-notification error-handling middleware converts a thrown ApiError into HTTP 400 { code: err.code, message: err.message }. The message in the response is the ApiError's own message. This indicates the server deemed the request invalid — a client-fixable problem — as opposed to the anonymous 500 fallback for non-ApiError exceptions.","triggerScenarios":"Any notification endpoint handler (wrapped by catchError) throws ApiError — e.g. push payload validation failures inside the handler, invalid subscription entries that fail domain checks, or web-push library errors deliberately re-thrown as ApiError.","commonSituations":"Clients sending PushSubscription objects with missing/invalid keys that deeper validation rejects; expired push endpoints rejected by upstream causing the handler to throw ApiError; generic SDK handling that ignores the 'code' field and reports only the message.","solutions":["Inspect the response body's 'code' field — it pinpoints the failing validation; adjust the request accordingly.","Validate each subscription (endpoint URL, keys.p256dh, keys.auth) client-side before submitting the batch.","Update stored subscriptions: remove ones whose codes indicate permanent rejection (e.g. 410 gone upstream).","In handler code you own, throw ApiError with specific codes rather than raw Errors so clients get actionable 400s."],"exampleFix":"// before\nif (!res.ok) throw new Error(`request failed: ${res.status}`)\n// after\nconst body = await res.json()\nif (!res.ok) throw new Error(`[${body.code ?? res.status}] ${body.message}`)","handlingStrategy":"try-catch","validationCode":"function validatePushPayload(body: unknown): string[] {\n  const errs: string[] = []\n  if (typeof (body as any)?.data !== 'object') errs.push('data missing')\n  if (!Array.isArray((body as any)?.subscriptions)) errs.push('subscriptions missing')\n  return errs\n}","typeGuard":"function isApiErrorBody(v: unknown): v is { code: string; message: string } {\n  return v !== null && typeof v === 'object' && 'code' in v && 'message' in v\n}","tryCatchPattern":"const res = await fetch(pushUrl, opts)\nif (!res.ok && res.status !== 404) {\n  const body = await res.json()\n  if (isApiErrorBody(body)) throw new Error(`Push ApiError ${body.code}: ${body.message}`)\n}","preventionTips":["Validate subscriptions and data against the schema before calling","Handle the 'code' field explicitly in client error paths","Remove permanently rejected subscriptions based on returned codes"],"tags":["http-400","api-error","validation","web-push"],"backgroundTag":"api-error-400","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}