{"record":{"id":"3d827f29c8521477","repo":"hcengineering/platform","slug":"both-width-and-height-must-be-provided","errorCode":null,"errorMessage":"Both width and height must be provided","messagePattern":"Both width and height must be provided","errorType":"validation","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"services/print/pod-print/src/server.ts","lineNumber":169,"sourceCode":"  if (orientation !== undefined && !validPageOrientations.includes(orientation as any)) {\n    throw new ApiError(400, `Invalid page orientation: ${orientation}`)\n  }\n\n  const rawWidth = (query.width ?? '') as string\n  const rawHeight = (query.height ?? '') as string\n\n  let viewport: PrintOptions['viewport'] | undefined\n  if (rawWidth.length > 0 && rawHeight.length > 0) {\n    viewport = {\n      width: parseInt(rawWidth, 10),\n      height: parseInt(rawHeight, 10)\n    }\n\n    if (Number.isNaN(viewport.width) || Number.isNaN(viewport.height)) {\n      throw new ApiError(400, 'Invalid width or height')\n    }\n  } else if (rawWidth.length > 0 || rawHeight.length > 0) {\n    throw new ApiError(400, 'Both width and height must be provided')\n  }\n\n  return { kind, orientation, viewport }\n}\n\nexport function createServer (\n  storageConfig: StorageConfiguration,\n  allowedHostnames: string[]\n): { app: Express, close: () => void } {\n  const storageAdapter = buildStorageFromConfig(storageConfig)\n  const measureCtx = initStatisticsContext('print', {\n    factory: () =>\n      createOpenTelemetryMetricsContext(\n        'print',\n        {},\n        {},\n        newMetrics(),\n        new SplitLogger('print', {","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/print/pod-print/src/server.ts#L151-L187","documentation":"The viewport parameters are all-or-nothing: if exactly one of `width` or `height` is present in the query string, parsePrintOptions throws this 400 ApiError. A viewport needs both dimensions to be meaningful for the headless browser.","triggerScenarios":"GET /print?link=...&width=1280 (height missing), or width= (empty value counts as absent) paired with height=600. Also happens when URL builders drop empty parameters asymmetrically.","commonSituations":"Copy-pasting an example URL and deleting one parameter; conditional client code that appends width but only conditionally appends height; a serializer that omits undefined height but keeps width.","solutions":["Always send both width and height together, e.g. &width=1280&height=720.","Or send neither — omit both to use defaults.","Fix the client URL builder so the two parameters are added as a unit.","Sanitize empty strings to undefined before deciding whether to append the params."],"exampleFix":"// before\nparams.set('width', width ?? '') // may yield lone width=\n// after\nif (width != null && height != null) {\n  params.set('width', String(width))\n  params.set('height', String(height))\n}","handlingStrategy":"validation","validationCode":"function appendViewport (params: URLSearchParams, width?: number, height?: number): void {\n  if ((width == null) !== (height == null)) {\n    throw new Error('width and height must be provided together')\n  }\n  if (width != null && height != null) {\n    params.set('width', String(width))\n    params.set('height', String(height))\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(url)\n  if (!res.ok) {\n    const body = await res.json()\n    if (body.code === 400 && /Both width and height/.test(body.message)) {\n      // either add the missing dimension or drop both and retry\n    }\n    throw new Error(body.message)\n  }\n  return await res.json()\n} catch (err) { /* handle */ }","preventionTips":["Treat width/height as a single viewport object in client code so they travel together.","Drop both params when either is missing rather than sending one.","Add an assertion in your URL builder that viewport params come in pairs."],"tags":["validation","http-400","query-params","viewport"],"backgroundTag":"missing-required-parameter","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}