thanos-io/thanos · error · api.ApiError

could not unmarshal parameter

Error message

could not unmarshal parameter %s

What it means

parseShardInfo reads the `shard_info` query parameter and JSON-unmarshals it into storepb.ShardInfo. When the parameter is present but is not valid JSON (or does not match the ShardInfo schema), the API returns an HTTP 400 (api.ErrorBadData) wrapping the unmarshal error with the parameter name.

Solutions

  1. URL-encode the shard_info JSON value when building the request (e.g. %7B%22shard_index%22%3A0%7D).
  2. Verify the JSON matches storepb.ShardInfo fields (shard_index, total_shards as numbers).
  3. Omit the shard_info parameter if sharding is not in use (empty value is accepted and treated as nil).

Example fix

// before
GET /api/v1/query?query=up&shard_info={"shard_index":0,"total_shards":2}
// after
GET /api/v1/query?query=up&shard_info=%7B%22shard_index%22%3A0%2C%22total_shards%22%3A2%7D
Defensive patterns

Strategy: validation

Validate before calling

if (shardInfo) {
  const parsed = JSON.parse(JSON.stringify(shardInfo)); // throws on invalid input
  if (!Number.isInteger(parsed.shard_index) || !Number.isInteger(parsed.total_shards)) {
    throw new Error('shard_info must contain integer shard_index and total_shards');
  }
  url.searchParams.set('shard_info', JSON.stringify(parsed)); // auto URL-encodes
}

Try / catch

try {
  const res = await fetch(url);
  const body = await res.json();
  if (body.status === 'error' && /could not unmarshal parameter shard_info/.test(body.error)) {
    // log the raw shard_info value and re-serialize it
  }
} catch (err) {}

Prevention

When it happens

Trigger: Calling /api/v1/query or /api/v1/query_range (or their explain variants) with `shard_info` set to malformed JSON, e.g. shard_info={"shard_index":}, or a non-object value like shard_info=abc.

Common situations: Downstream Thanos Store/Query gateways forwarding shard info between query nodes with URL-encoding issues (raw `{`/`}` not percent-encoded); clients manually constructing shard_info JSON with wrong field types (string instead of int).

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — 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/4d861c124a1d8e32. Report an issue: GitHub.

Appendix: source

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

	}
	// Default step is used this way to make it consistent with UI.
	d := time.Duration(math.Max(float64(rangeSeconds/250), float64(defaultRangeQueryStep/time.Second))) * time.Second
	return d, nil
}

func (qapi *QueryAPI) parseShardInfo(r *http.Request) (*storepb.ShardInfo, *api.ApiError) {
	data := r.FormValue(ShardInfoParam)
	if data == "" {
		return nil, nil
	}

	if len(data) == 0 {
		return nil, nil
	}

	var info storepb.ShardInfo
	if err := json.Unmarshal([]byte(data), &info); err != nil {
		return nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "could not unmarshal parameter %s", ShardInfoParam)}
	}

	return &info, nil
}

func (qapi *QueryAPI) getQueryExplain(query promql.Query) (*engine.ExplainOutputNode, *api.ApiError) {
	if eq, ok := query.(engine.ExplainableQuery); ok {
		return eq.Explain(), nil
	}
	return nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("Query not explainable")}
}

func (qapi *QueryAPI) parseQueryAnalyzeParam(r *http.Request) bool {
	return (r.FormValue(QueryAnalyzeParam) == "true" || r.FormValue(QueryAnalyzeParam) == "1")
}

func analyzeQueryOutput(query promql.Query, engineType PromqlEngineType, tracker *fanout.Tracker) (queryTelemetry, error) {
	if eq, ok := query.(engine.ExplainableQuery); ok {

View on GitHub (pinned to 35b8b99117)