{"record":{"id":"dee837e999e039dc","repo":"hcengineering/platform","slug":"bad-request","errorCode":null,"errorMessage":"Bad Request","messagePattern":"Bad Request","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"pods/link-preview/src/server.ts","lineNumber":88,"sourceCode":"      requests.info(text)\n    }\n  }\n\n  const wrapRequest =\n    (ctx: MeasureContext, name: string, fn: AsyncRequestHandler) =>\n      (req: RequestWithAuth, res: Response, next: NextFunction) => {\n      // eslint-disable-next-line @typescript-eslint/no-floating-promises\n        handleRequest(ctx, name, fn, req, res, next)\n      }\n\n  app.use(morgan('short', { stream: new LogStream() }))\n\n  app.get(\n    '/details',\n    withAuthorization,\n    wrapRequest(ctx, 'getLinkPreviewDetails', async (ctx, req, res) => {\n      if (req.query === undefined) {\n        res.status(400).json({ message: 'Bad Request' })\n        return\n      }\n      if (req.query.q === undefined) {\n        res.status(400).json({ message: 'Bad Request' })\n        return\n      }\n      const url = req.query.q as string\n      try {\n        const result = await parseLinkPreviewDetails(ctx, config, url)\n        res.status(200).json(result)\n      } catch (err) {\n        console.error({ message: 'failed to parse link preview details', url, err })\n        res.status(422).json({ message: 'Unprocessable Entity' })\n      }\n    })\n  )\n\n  app.get('/api/v1/statistics', (req, res) => {","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/pods/link-preview/src/server.ts#L70-L106","documentation":"The /details endpoint of the link-preview server requires a query string. If req.query is undefined entirely, the handler short-circuits with HTTP 400 and { message: 'Bad Request' }. This guards downstream code from dereferencing req.query.","triggerScenarios":"Calling GET /details with no query string at all (e.g. curl without any parameters, or a client that dropped the query when building the URL).","commonSituations":"Hand-built requests missing '?q=...'; proxy/gateway stripping the query string; client HTTP libraries that require explicit query construction; tests hitting the bare endpoint path.","solutions":["Include a query string on the request, e.g. GET /details?q=https%3A%2F%2Fexample.com","URL-encode the target URL value of q","Check intermediaries (proxies, redirects) are not stripping the query string","Fix client code to always append at least one query parameter when calling /details"],"exampleFix":"// before\nfetch(`${base}/details`)\n// after\nconst url = new URL(`${base}/details`)\nurl.searchParams.set('q', targetUrl)\nfetch(url) // /details?q=https%3A%2F%2Fexample.com","handlingStrategy":"validation","validationCode":"function buildDetailsUrl(base: string, target: string): string {\n  if (!target) throw new Error('target url required')\n  const u = new URL(`${base}/details`)\n  u.searchParams.set('q', target)\n  return u.toString()\n}","typeGuard":"null","tryCatchPattern":"const res = await fetch(url)\nif (res.status === 400) {\n  throw new Error('request must include a query string: /details?q=<encoded url>')\n}","preventionTips":["Always append at least one query parameter when calling /details","Build URLs with URL/URLSearchParams instead of string concatenation","Verify after redirects that proxies preserved the query string","Add an integration test asserting the request URL contains ?q="],"tags":["http-400","bad-request","query-string"],"backgroundTag":"missing-query-parameter","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}