SigNoz/signoz · error · model.ApiError
end timestamp must not be before start time
Error message
end timestamp must not be before start time
What it means
The metrics range query parser rejects a time range where end is chronologically before start. SigNoz enforces this because a backwards range cannot produce a meaningful time series and would break downstream PromQL-style evaluation.
Source
Thrown at pkg/query-service/app/parser.go:148
Time: ts,
Query: r.FormValue("query"),
Stats: r.FormValue("stats"),
}, nil
}
func parseQueryRangeRequest(r *http.Request) (*model.QueryRangeParams, *model.ApiError) {
start, err := parseMetricsTime(r.FormValue("start"))
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
end, err := parseMetricsTime(r.FormValue("end"))
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
if end.Before(start) {
err := errors.New("end timestamp must not be before start time")
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
step, err := parseMetricsDuration(r.FormValue("step"))
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
if step <= 0 {
err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer")
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
// For safety, limit the number of returned points per timeseries.
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
if end.Sub(start)/step > 11000 {
err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}View on GitHub (pinned to 5069bf80b0)
Solutions
- Swap the start and end parameters so start is the earlier instant
- Validate that both timestamps use the same unit (Unix seconds) and timezone before sending
- If computing ranges in code, assert end.After(start) or end.Equal(start) before the request
Example fix
// before
params := url.Values{}
params.Set("start", endTs)
params.Set("end", startTs)
// after
params := url.Values{}
params.Set("start", startTs)
params.Set("end", endTs) Defensive patterns
Strategy: validation
Validate before calling
if end.Before(start) {
end, start = start, end // or return an error to the user
}
resp, err := api.QueryRangeMetrics(ctx, start, end, step, query) Try / catch
if err != nil {
if apiErr, ok := err.(*model.ApiError); ok && apiErr.Typ == model.ErrorBadData && strings.Contains(err.Error(), "must not be before") {
log.Printf("inverted time range from user %s..%s", start, end)
}
} Prevention
- Normalize all timestamps to Unix seconds UTC before building requests
- Validate start <= end in dashboard variable code
- Add unit tests for boundary ranges (equal start/end, swapped)
When it happens
Trigger: GET /api/v1/query_range (or the SigNoz metrics range API) where the end timestamp parses to an earlier instant than start, e.g. start=2026-08-28T10:00:00&end=2026-08-28T09:00:00, often from swapped variables or unit confusion (seconds vs milliseconds).
Common situations: Swapping start/end in dashboards or curl commands; passing timestamps in different units or timezones so comparison inverts; relative-time templates (now-1h vs now) wired backwards.
Related errors
- zero or negative query resolution step widths are not accept
- public_dashboard_invalid_input
- atleast 1 metric name must be specified
- query is empty
- CodeLicenseUnavailable
AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28).
Data as JSON: /api/errors/c1bb4dfe628ddde8.
Report an issue: GitHub.