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

  1. Swap the start and end parameters so start is the earlier instant
  2. Validate that both timestamps use the same unit (Unix seconds) and timezone before sending
  3. 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

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


AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28). Data as JSON: /api/errors/c1bb4dfe628ddde8. Report an issue: GitHub.