{"record":{"id":"c3502b882068f7da","repo":"immich-app/immich","slug":"the-route-request-path-was-requested-as-reque","errorCode":null,"errorMessage":"The route ${request.path} was requested as ${request.header('accept')}, but only returns text/html","messagePattern":"The route (.+?) was requested as (.+?), but only returns text/html","errorType":"http","errorClass":"NotAcceptableException","httpStatus":406,"severity":"warning","filePath":"server/src/services/api.service.ts","lineNumber":61,"sourceCode":"    try {\n      index = readFileSync(resourcePaths.web.indexHtml).toString();\n    } catch {\n      this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`);\n    }\n\n    return async (request: Request, res: Response, next: NextFunction) => {\n      const method = request.method.toLowerCase();\n      if (\n        request.url.startsWith('/api') ||\n        (method !== 'get' && method !== 'head') ||\n        excludePaths.some((item) => request.url.startsWith(item))\n      ) {\n        return next();\n      }\n\n      const responseType = request.accepts('text/html');\n      if (!responseType) {\n        throw new NotAcceptableException(\n          `The route ${request.path} was requested as ${request.header('accept')}, but only returns text/html`,\n        );\n      }\n\n      let status = 200;\n      let html = index;\n\n      const defaultDomain = request.host ? `${request.protocol}://${request.host}` : undefined;\n\n      let meta: OpenGraphTags | null = null;\n\n      const shareKey = request.url.match(/^\\/share\\/(.+)$/);\n      if (shareKey) {\n        try {\n          const key = shareKey[1];\n          const auth = await this.authService.validateSharedLinkKey(key);\n          meta = await this.sharedLinkService.getMetadataTags(auth, defaultDomain);\n        } catch {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/api.service.ts#L43-L79","documentation":"Thrown by the ApiService.ssr Express middleware as NotAcceptableException (HTTP 406) when a non-/api GET/HEAD request (not on an excluded path) does not accept text/html. The middleware powers server-side rendering of the web app and Open Graph tags for share links, so it only serves HTML. Any client that demands JSON (e.g. Accept: application/json) on a share or root route trips this.","triggerScenarios":"GET /share/:key, /, or any non-/api GET route with an Accept header that excludes text/html — e.g. `Accept: application/json`, `Accept: image/*`, or a strict `Accept` from a programmatic client/scraper.","commonSituations":"Curl/HTTPie defaults that send `Accept: */*` work, but clients that pin `Accept: application/json` for all calls get 406; bots/scrapers with restrictive Accept; SDK code reused for share-link fetching that assumes JSON.","solutions":["Send `Accept: text/html` (or `*/*`) when fetching share pages or any non-/api route","Use the /api/* endpoints for machine-readable data instead of the SSR routes","Configure programmatic clients to vary Accept by route family"],"exampleFix":"// before\nawait fetch(`${origin}/share/abc`, { headers: { accept: 'application/json' } }); // 406\n// after\nawait fetch(`${origin}/share/abc`, { headers: { accept: 'text/html' } });","handlingStrategy":"validation","validationCode":"// Set Accept correctly for SSR vs API routes\nconst headers = isShareRoute(url) ? { accept: 'text/html' } : { accept: 'application/json' };\nawait fetch(url, { headers });","typeGuard":"function acceptsHtml(accept: string | undefined): boolean {\n  if (!accept) return true; // default\n  return /text\\/html|\\*\\/\\*/i.test(accept);\n}","tryCatchPattern":"try {\n  return await fetch(url, { headers: { accept: 'text/html' } });\n} catch (e) {\n  if (e instanceof HttpError && e.status === 406) {\n    // retry with explicit text/html, or fall back to /api route\n  }\n  throw e;\n}","preventionTips":["Default programmatic clients to Accept: */* to avoid 406","Pin Accept: text/html only for share/root SSR routes","Use /api/* paths for JSON data — never scrape SSR routes for structured data"],"tags":["http","content-negotiation","ssr","middleware","share","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}