thanos-io/thanos · error · api.ApiError

end timestamp must not be before start time

Error message

end timestamp must not be before start time

What it means

In queryRange, after parsing the `start` and `end` timestamps, the API rejects requests where `end` is earlier than `start`, returning HTTP 400 (api.ErrorBadData). This mirrors Prometheus' range-query semantics and prevents nonsensical or inverted time ranges.

Solutions

  1. Swap or recompute start/end so end >= start before sending the request.
  2. Normalize all timestamps to RFC3339/UTC in the client before building the URL.
  3. Add client-side validation: if end.Before(start), reject the query before the HTTP call.

Example fix

// before
GET /api/v1/query_range?query=up&start=2026-01-02T00:00:00Z&end=2026-01-01T00:00:00Z&step=1m
// after
GET /api/v1/query_range?query=up&start=2026-01-01T00:00:00Z&end=2026-01-02T00:00:00Z&step=1m
Defensive patterns

Strategy: validation

Validate before calling

const start = Date.parse(startStr), end = Date.parse(endStr);
if (Number.isNaN(start) || Number.isNaN(end)) throw new Error('invalid timestamps');
if (end < start) throw new Error('end must not be before start');

Try / catch

const body = await res.json();
if (body.status === 'error' && /end timestamp must not be before start/.test(body.error)) {
  // swap or recompute the range client-side before retrying
}

Prevention

When it happens

Trigger: Calling /api/v1/query_range with an `end` timestamp strictly before `start`, e.g. start=2026-01-02T00:00:00Z&end=2026-01-01T00:00:00Z, or where client clocks/timezone handling produce an inverted range.

Common situations: Client computing start/end with mixed timezones or RFC3339 vs Unix seconds mixups; dashboards where the 'to' field was set before the 'from' field; off-by-negative duration math when building relative ranges.

Understand the failure class

Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/629eda3147617ffb. Report an issue: GitHub.

Appendix: source

Thrown at pkg/api/query/v1.go:778

	if apiErr != nil {
		return nil, nil, apiErr, func() {}
	}

	if engineParam != PromqlEngineThanos {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("engine type must be 'thanos'")}, func() {}
	}
	queryParam := qapi.parseQueryParam(r)

	start, err := parseTime(r.FormValue("start"))
	if err != nil {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
	}
	end, err := parseTime(r.FormValue("end"))
	if err != nil {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
	}
	if end.Before(start) {
		err := errors.New("end timestamp must not be before start time")
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
	}

	step, apiErr := qapi.parseStep(r, qapi.defaultRangeQueryStep, int64(end.Sub(start)/time.Second))
	if apiErr != nil {
		return nil, nil, apiErr, func() {}
	}

	if step <= 0 {
		err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer")
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
	}

	// 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, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}

View on GitHub (pinned to 35b8b99117)