thanos-io/thanos · error · api.ApiError

engine type must be 'thanos'

Error message

engine type must be 'thanos'

What it means

The query_explain endpoint only supports the Thanos engine because explain requires engine.ExplainableQuery. If the `engine` request parameter parses successfully but is anything other than 'thanos', queryExplain returns an HTTP 400 (api.ErrorBadData) with this message.

Solutions

  1. Set `engine=thanos` in the query_explain request.
  2. Remove the engine parameter only if 'thanos' is the configured default; otherwise pass it explicitly.
  3. Stop calling query_explain if you must use the Prometheus engine — explain is unsupported there.

Example fix

// before
GET /api/v1/query_explain?query=up&engine=prometheus
// after
GET /api/v1/query_explain?query=up&engine=thanos
Defensive patterns

Strategy: validation

Validate before calling

if (engine !== undefined && engine !== 'thanos') {
  throw new Error("query_explain only supports engine='thanos'");
}

Type guard

const isThanosEngine = (e) => e === undefined || e === 'thanos';

Try / catch

const body = await res.json();
if (body.status === 'error' && /engine type must be 'thanos'/.test(body.error)) {
  // correct the engine parameter and retry once
}

Prevention

When it happens

Trigger: Calling /api/v1/query_explain with `engine=prometheus` or any other value than 'thanos'.

Common situations: Copy-pasting query URLs between the regular query endpoint (which accepts prometheus) and the explain endpoint; clients with a configurable engine defaulting to prometheus while also calling explain.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

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

				Chunks:         s.Chunks,
				Samples:        s.Samples,
			})
		}
	}
	for _, c := range a.Children {
		analysis.Children = append(analysis.Children, processAnalysis(c, tracker))
	}
	return analysis
}

func (qapi *QueryAPI) queryExplain(r *http.Request) (any, []error, *api.ApiError, func()) {
	engineParam, apiErr := qapi.parseEngineParam(r)
	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)

	ts, err := parseTimeParam(r, "time", qapi.baseAPI.Now())
	if err != nil {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
	}

	ctx := r.Context()
	if to := r.FormValue("timeout"); to != "" {
		var cancel context.CancelFunc
		timeout, err := parseDuration(to)
		if err != nil {
			return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
		}

		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()

View on GitHub (pinned to 35b8b99117)