{"record":{"id":"9eb64fd4d89ae60f","repo":"instructure/canvas-lms","slug":"rate-limit-exceeded","errorCode":null,"errorMessage":"rate limit exceeded","messagePattern":"rate limit exceeded","errorType":"http","errorClass":"Pv4TooManyRequests","httpStatus":429,"severity":"error","filePath":"app/models/page_view/pv4_client.rb","lineNumber":64,"sourceCode":"      params = \"start_time=#{start_time.utc.iso8601(PRECISION)}\"\n      params << \"&end_time=#{end_time.utc.iso8601(PRECISION)}\"\n      params << \"&#{cached_root_account_uuids_for(user:)}\"\n      params << \"&last_page_view_id=#{last_page_view_id}\" if last_page_view_id\n      params << \"&limit=#{limit}\" if limit\n      response = CanvasHttp.get(\n        @uri.merge(\"users/#{user.global_id}/page_views?#{params}\").to_s,\n        request_headers\n      )\n\n      case response.code.to_i\n      when 400\n        raise Pv4BadRequest, \"invalid request\"\n      when 401\n        raise Pv4Unauthorized, \"unauthorized request\"\n      when 404\n        raise Pv4NotFound, \"resource not found\"\n      when 429\n        raise Pv4TooManyRequests, \"rate limit exceeded\"\n      end\n\n      json =\n        begin\n          response.body.empty? ? {} : JSON.parse(response.body)\n        rescue JSON::ParserError\n          {}\n        end\n      raise Pv4EmptyResponse, \"the response is empty or does not contain expected keys\" unless json[\"page_views\"]\n\n      json[\"page_views\"].map! do |pv|\n        pv[\"session_id\"] = pv.delete(\"sessionid\")\n        vhost = pv.delete(\"vhost\")\n        http_request = pv.delete(\"http_request\")\n        pv[\"url\"] = if vhost.present? && http_request.present?\n                      \"#{HostUrl.protocol}://#{vhost}#{http_request}\"\n                    elsif http_request.present?\n                      \"#{HostUrl.protocol}:#{http_request}\"","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/page_view/pv4_client.rb#L46-L82","documentation":"Pv4Client#fetch raises Pv4TooManyRequests when the PV4 page-view service returns HTTP 429, meaning the client exceeded the service's rate limit. The library surfaces this as a typed error instead of retrying, letting callers decide on backoff.","triggerScenarios":"Calling Pv4Client#fetch (via PageView.for_user) too frequently — e.g. polling page-view history in a tight loop or a burst of user requests hitting the same PV4 service.","commonSituations":"Automated scripts or monitoring repeatedly loading page-view history; many concurrent users requesting history during peak load; missing caching of history results on the Canvas side.","solutions":["Implement exponential backoff and retry the request after the delay indicated by the 429 response","Cache page-view history results to reduce repeat calls to PV4","Throttle/queue calls to PageView.for_user in background jobs","Ask the PV4 service operators to raise the rate limit if legitimate traffic is being throttled"],"exampleFix":"// before\nviews = PageView.for_user(user)\n// after\nbegin\n  views = PageView.for_user(user)\nrescue Pv4TooManyRequests\n  sleep(backoff)\n  retry\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"retries = 0\nbegin\n  views = PageView.for_user(user)\nrescue Pv4TooManyRequests\n  retries += 1\n  raise if retries > 3\n  sleep(2**retries)\n  retry\nend","preventionTips":["Cache page-view history responses","Avoid polling loops against PageView.for_user","Move bulk history reads into throttled background jobs","Respect Retry-After semantics from the PV4 service"],"tags":["http","rate-limit","network","pageviews"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}