bitwarden/server · error · BadRequestException
Date range must be < 367 days.
Error message
Date range must be < 367 days.
What it means
Thrown by EventFilterRequestModel.ToDateRange() when the span between Start and End exceeds 367 days. The method defaults to a 30-day window if either bound is null, swaps reversed ranges, but hard-rejects ranges wider than one year + one day. BadRequestException returns HTTP 400.
Source
Thrown at src/Api/Dirt/Public/Models/EventFilterRequestModel.cs:55
public string ContinuationToken { get; set; }
public Tuple<DateTime, DateTime> ToDateRange()
{
if (!End.HasValue || !Start.HasValue)
{
End = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1);
Start = DateTime.UtcNow.Date.AddDays(-30);
}
else if (Start.Value > End.Value)
{
var newEnd = Start;
Start = End;
End = newEnd;
}
if ((End.Value - Start.Value) > TimeSpan.FromDays(367))
{
throw new BadRequestException("Date range must be < 367 days.");
}
return new Tuple<DateTime, DateTime>(Start.Value, End.Value);
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Split the request into multiple paginated calls each within the 367-day limit.
- Client-side: clamp the date picker's maximum range to 367 days before submitting.
- If only Start is provided, the server defaults End to today — ensure the resulting range is within bounds.
- Use the ContinuationToken pagination mechanism for large date ranges instead of one wide window.
Example fix
// before
var events = await api.GetEventsAsync(start: new DateTime(2020,1,1), end: DateTime.UtcNow);
// after — paginate across 367-day windows
var chunkStart = new DateTime(2020, 1, 1);
var end = DateTime.UtcNow;
while (chunkStart < end)
{
var chunkEnd = chunkStart.AddDays(367) < end ? chunkStart.AddDays(367) : end;
var events = await api.GetEventsAsync(start: chunkStart, end: chunkEnd);
// process...
chunkStart = chunkEnd;
} Defensive patterns
Strategy: validation
Validate before calling
// Validate date range client-side before calling the API
public static bool IsValidDateRange(DateTime? start, DateTime? end)
{
if (!start.HasValue || !end.HasValue) return true; // server defaults
var actualStart = start.Value < end.Value ? start.Value : end.Value;
var actualEnd = start.Value < end.Value ? end.Value : start.Value;
return (actualEnd - actualStart) <= TimeSpan.FromDays(367);
} Type guard
public static bool IsWithinMaxRange(DateTime start, DateTime end) =>
(end - start) <= TimeSpan.FromDays(367); Try / catch
try
{
var events = await _eventService.GetEventsAsync(filter);
}
catch (BadRequestException ex) when (ex.Message.Contains("367 days"))
{
// Split into chunks and retry
var chunked = SplitIntoMaxRanges(filter.Start.Value, filter.End.Value, TimeSpan.FromDays(367));
var allEvents = new List<EventLog>();
foreach (var (s, e) in chunked)
allEvents.AddRange(await _eventService.GetEventsAsync(new EventFilter { Start = s, End = e }));
return allEvents;
} Prevention
- Clamp date pickers to a 367-day maximum range client-side.
- Use the ContinuationToken pagination mechanism for large historical queries.
- Split wide date ranges into multiple paginated API calls programmatically.
When it happens
Trigger: Any event-filtering API call (e.g., GET /public/events or /organizations/{id}/events) with Start and End query parameters spanning more than 367 days. The check is (End - Start) > 367 days.
Common situations: Client UI date-picker defaulting to 'all time'; exporting a full audit history in one request; automated reporting script using a fixed start date far in the past; timezone mismatch causing the boundary to be off by a day.
Related errors
- Unable to build callback Url
- There already exists a Slack integration for this organizati
- Invalid response from Slack.
- Unable to build callback Url
- There already exists a Teams integration for this organizati
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/7559d06b37e67125.
Report an issue: GitHub.