Description
The public Stats API GET /api/v1/stats/breakdown endpoint crashes with a 500 Internal Server Error when the page query parameter is not a valid positive integer.
The parameter is passed directly into String.to_integer/1:
https://github.com/plausible/analytics/blob/master/lib/plausible_web/controllers/api/external_stats_controller.ex#L44
page = String.to_integer(Map.get(params, "page", "1"))
String.to_integer/1 raises ArgumentError on any non-numeric input, so the request fails with a 500 instead of a helpful client error.
Steps to reproduce
GET /api/v1/stats/breakdown?site_id=<site>&property=event:page&page=foo
Also triggered by page=1.5, page= (empty), etc.
Expected behavior
A clean 400 Bad Request with a descriptive error message — consistent with the sibling limit parameter a few lines below, which is already validated via Integer.parse/1 (validate_or_default_limit/1).
Actual behavior
500 Internal Server Error (and a reported exception).
Proposed fix
Validate page the same way limit is validated, returning a 400 for invalid or non-positive values. Happy to open a PR.
Description
The public Stats API
GET /api/v1/stats/breakdownendpoint crashes with a 500 Internal Server Error when thepagequery parameter is not a valid positive integer.The parameter is passed directly into
String.to_integer/1:https://github.com/plausible/analytics/blob/master/lib/plausible_web/controllers/api/external_stats_controller.ex#L44
String.to_integer/1raisesArgumentErroron any non-numeric input, so the request fails with a 500 instead of a helpful client error.Steps to reproduce
Also triggered by
page=1.5,page=(empty), etc.Expected behavior
A clean 400 Bad Request with a descriptive error message — consistent with the sibling
limitparameter a few lines below, which is already validated viaInteger.parse/1(validate_or_default_limit/1).Actual behavior
500 Internal Server Error(and a reported exception).Proposed fix
Validate
pagethe same waylimitis validated, returning a 400 for invalid or non-positive values. Happy to open a PR.