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
- URL-encode the shard_info JSON value when building the request (e.g. %7B%22shard_index%22%3A0%7D).
- Verify the JSON matches storepb.ShardInfo fields (shard_index, total_shards as numbers).
- 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
- Build shard_info with JSON.stringify and let the HTTP client URL-encode it; never hand-write the JSON into URLs.
- Keep shard_index/total_shards as JSON numbers, not strings.
- Omit the parameter when sharding is not used.
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
- ' ' parameter
- ' ' bad engine
- negative ' ' is not accepted. Try a positive integer
- engine type must be 'thanos'
- end timestamp must not be before start time
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)