{"record":{"id":"8430980bc1d5b9b2","repo":"instructure/canvas-lms","slug":"invalid-request","errorCode":null,"errorMessage":"invalid request","messagePattern":"invalid request","errorType":"http","errorClass":"Pv4BadRequest","httpStatus":400,"severity":"error","filePath":"app/models/page_view/pv4_client.rb","lineNumber":58,"sourceCode":"              end_time: Time.now.utc,\n              last_page_view_id: nil,\n              limit: nil)\n      end_time ||= Time.now.utc\n      start_time ||= Time.at(0).utc\n\n      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\")","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/page_view/pv4_client.rb#L40-L76","documentation":"Pv4Client#fetch calls the PV4 page-views HTTP API for a user and maps response codes to typed errors; an HTTP 400 response raises Pv4BadRequest with message \"invalid request\". This means the PV4 service rejected the request as malformed — bad query parameters, an invalid user id format, or an unsupported parameter combination.","triggerScenarios":"Calling Pv4Client.for_user (which calls fetch) when the built request to users/{global_id}/page_views?{params} is rejected with 400 — e.g. invalid start_time/end_time params, unknown query parameter names, or a malformed user global id in the URL.","commonSituations":"Passing improperly formatted/nil time filters that serialize into garbage query strings; calling PV4 before the user's data exists in the new page-views store; proxy/gateway returning 400 for oversized or malformed requests.","solutions":["Inspect the outgoing URL and query params (log @uri before the request) and fix invalid parameter names/values or formats.","Validate the user's global_id is present and correctly formatted before calling for_user.","Sanitize time-range params (start_time/end_time) — ensure they are valid, ordered datetimes or omitted.","Rescue Pv4BadRequest in the caller and degrade gracefully (e.g. return empty page views) since PV4 may not have data for the user."],"exampleFix":"// before\nPv4Client.for_user(user, start_time: params['start_time']) # raw unvalidated string\n// after\nstart = Time.zone.parse(params['start_time'].to_s)\nPv4Client.for_user(user, start ? { start_time: start.utc.iso8601 } : {})","handlingStrategy":"fallback","validationCode":"raise ArgumentError, 'user required' unless user&.global_id\nfilters.each { |k, v| raise ArgumentError, \"bad #{k}\" if %i[start_time end_time].include?(k) && v && !v.is_a?(Time) }","typeGuard":null,"tryCatchPattern":"begin\n  Pv4Client.for_user(user, filters)\nrescue Pv4BadRequest => e\n  Rails.logger.warn(\"PV4 bad request for #{user.global_id}: #{e.message}\")\n  []\nend","preventionTips":["Serialize time filters as iso8601 UTC strings","Log the full request URI to catch malformed query strings early","Handle PV4 gracefully when a user has no PV4 data yet"],"tags":["ruby","http","page-views","bad-request"],"backgroundTag":"http-error-response","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"}