{"record":{"id":"63653eecce766c03","repo":"overleaf/overleaf","slug":"request-failed","errorCode":null,"errorMessage":"request failed","messagePattern":"request failed","errorType":"http","errorClass":"RequestFailedError","httpStatus":null,"severity":"error","filePath":"libraries/fetch-utils/index.js","lineNumber":39,"sourceCode":" * @param {string | URL} url - request URL\n * @param {any} [opts] - fetch options\n * @return {Promise<any>} the parsed JSON response\n * @throws {RequestFailedError} if the response has a failure status code\n */\nasync function fetchJson(url, opts = {}) {\n  const { json } = await fetchJsonWithResponse(url, opts)\n  return json\n}\n\nasync function fetchJsonWithResponse(url, opts = {}) {\n  const { fetchOpts, detachSignal } = parseOpts(opts, url)\n  fetchOpts.headers = fetchOpts.headers ?? {}\n  fetchOpts.headers.Accept = fetchOpts.headers.Accept ?? 'application/json'\n\n  const response = await performRequest(url, fetchOpts, detachSignal)\n  if (!response.ok) {\n    const body = await maybeGetResponseBody(response)\n    throw new RequestFailedError(url, opts, response, body)\n  }\n\n  const json = await response.json()\n  return { json, response }\n}\n\n/**\n * Make a request and return a stream.\n *\n * If the response body is destroyed, the request is aborted.\n *\n * @param {string | URL} url - request URL\n * @param {any} [opts] - fetch options\n * @return {Promise<Readable>}\n * @throws {RequestFailedError} if the response has a failure status code\n */\nasync function fetchStream(url, opts = {}) {\n  const { stream } = await fetchStreamWithResponse(url, opts)","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/libraries/fetch-utils/index.js#L21-L57","documentation":"fetchJsonWithResponse performs an HTTP request expecting JSON and throws a RequestFailedError (an OError subclass carrying the url, opts, response and body) whenever response.ok is false (status outside 200-299). The message is generic 'request failed'; the actual status, URL and response body are attached as properties/info on the error object. JSON parsing failures after a 2xx response throw a different error, so this error specifically means the server returned a failure status.","triggerScenarios":"Calling fetchJsonWithResponse(url, opts) (or the makeRequest-style wrappers) and the remote service responds 4xx/5xx — e.g. a 404 from a wrong route, 401/403 from bad auth headers, 429 rate limiting, or a 500 from the downstream service.","commonSituations":"A dependency service is down or erroring (502/503 from a proxy); wrong base URL or port in service settings; expired/missing auth credentials causing 401; the requested entity was deleted elsewhere giving 404; load-shedding returning 429 under heavy traffic.","solutions":["Inspect err.response.status, err.response.body and err.info (OError info) to identify the status and downstream message.","Verify the URL/route and service hostname/port in configuration.","Check auth headers/options (opts.headers) and credentials validity if status is 401/403.","If the status is 5xx or 429, retry with backoff; if it is 4xx, fix the request rather than retrying.","Check health/logs of the downstream service if 5xx persists."],"exampleFix":"// before\nconst { json } = await fetchJsonWithResponse(url) // throws on 404\n// after\ntry {\n  const { json } = await fetchJsonWithResponse(url)\n} catch (err) {\n  if (err instanceof RequestFailedError && err.response.status === 404) {\n    return null // treat as missing\n  }\n  throw err\n}","handlingStrategy":"try-catch","validationCode":"const url = new URL(path, baseUrl)\nif (!/^https?:$/.test(url.protocol)) throw new Error(`bad service URL: ${url}`)\n// ensure required auth headers present before the call\nif (opts.headers?.Authorization == null && requiresAuth) throw new Error('missing Authorization header')","typeGuard":"function isRequestFailedError(err) {\n  return err instanceof RequestFailedError\n}","tryCatchPattern":"try {\n  const { json, response } = await fetchJsonWithResponse(url, opts)\n} catch (err) {\n  if (err instanceof RequestFailedError) {\n    const { status } = err.response\n    if (status >= 500 || status === 429) {\n      return retryWithBackoff(() => fetchJsonWithResponse(url, opts))\n    }\n    if (status === 404) return null\n    logger.warn({ url, status, body: err.response.body }, 'request failed')\n  }\n  throw err\n}","preventionTips":["Always catch RequestFailedError and branch on err.response.status instead of message text.","Retry only 5xx/429 (and network errors); never blindly retry 4xx.","Include the URL and status in your own logs via the error's attached response/body.","Centralize service-to-service calls in one helper that applies timeout, retry, and error-mapping policy."],"tags":["http","network","fetch","http-status"],"backgroundTag":"http-request-failed","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}