instructure/canvas-lms · error · Pv4BadRequest
invalid request
Error message
invalid request
What it means
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.
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.
Example fix
// before
Pv4Client.for_user(user, start_time: params['start_time']) # raw unvalidated string
// after
start = Time.zone.parse(params['start_time'].to_s)
Pv4Client.for_user(user, start ? { start_time: start.utc.iso8601 } : {}) Defensive patterns
Strategy: fallback
Validate before calling
raise ArgumentError, 'user required' unless user&.global_id
filters.each { |k, v| raise ArgumentError, "bad #{k}" if %i[start_time end_time].include?(k) && v && !v.is_a?(Time) } Try / catch
begin
Pv4Client.for_user(user, filters)
rescue Pv4BadRequest => e
Rails.logger.warn("PV4 bad request for #{user.global_id}: #{e.message}")
[]
end Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- unauthorized request
- Content Export for Outcomes Service failed
- Error queueing export for Outcomes Service: #
- Error retrieving export for Outcomes Service: #
- Error retrieving export state for Outcomes Service: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/8430980bc1d5b9b2.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/page_view/pv4_client.rb:58
end_time: Time.now.utc,
last_page_view_id: nil,
limit: nil)
end_time ||= Time.now.utc
start_time ||= Time.at(0).utc
params = "start_time=#{start_time.utc.iso8601(PRECISION)}"
params << "&end_time=#{end_time.utc.iso8601(PRECISION)}"
params << "&#{cached_root_account_uuids_for(user:)}"
params << "&last_page_view_id=#{last_page_view_id}" if last_page_view_id
params << "&limit=#{limit}" if limit
response = CanvasHttp.get(
@uri.merge("users/#{user.global_id}/page_views?#{params}").to_s,
request_headers
)
case response.code.to_i
when 400
raise Pv4BadRequest, "invalid request"
when 401
raise Pv4Unauthorized, "unauthorized request"
when 404
raise Pv4NotFound, "resource not found"
when 429
raise Pv4TooManyRequests, "rate limit exceeded"
end
json =
begin
response.body.empty? ? {} : JSON.parse(response.body)
rescue JSON::ParserError
{}
end
raise Pv4EmptyResponse, "the response is empty or does not contain expected keys" unless json["page_views"]
json["page_views"].map! do |pv|
pv["session_id"] = pv.delete("sessionid")View on GitHub (pinned to 1c9f0bb801)