thanos-io/thanos · error · ApiError

ID cannot be empty

Error message

ID cannot be empty

What it means

markBlock requires the form parameter 'id' identifying the block ULID. When the request omits it (or it is an empty string), the API rejects the request with an ErrorBadData ApiError 'ID cannot be empty'.

Solutions

  1. Send the id form field with the block ULID: curl -X POST -d 'id=01ARZ3NDEKTSV4RRFFQ69G5FAV' ... .
  2. Verify the upstream step that produces the block ID actually returned a value before calling the API.
  3. Add client-side validation that id is a non-empty ULID before issuing the request.

Example fix

// before
curl -X POST http://thanos:9090/api/v1/blocks/mark -d 'action=deletion'
// after
curl -X POST http://thanos:9090/api/v1/blocks/mark -d 'action=deletion' -d 'id=01ARZ3NDEKTSV4RRFFQ69G5FAV'
Defensive patterns

Strategy: validation

Validate before calling

if blockID == "" {
    return errors.New("cannot call /blocks/mark without a block ULID id")
}
if _, err := ulid.Parse(blockID); err != nil {
    return fmt.Errorf("invalid ULID %q: %w", blockID, err)
}

Prevention

When it happens

Trigger: POST /api/v1/blocks/mark without the id form field, or with id= (empty value).

Common situations: curl calls missing -d 'id=...'; automation building the form body with an empty variable because the block ID lookup failed upstream.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at pkg/api/blocks/v1.go:105

func (bapi *BlocksAPI) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger, ins extpromhttp.InstrumentationMiddleware, logMiddleware *logging.HTTPServerMiddleware) {
	bapi.baseAPI.Register(r, tracer, logger, ins, logMiddleware)

	instr := api.GetInstr(tracer, logger, ins, logMiddleware, bapi.disableCORS)

	r.Get("/blocks", instr("blocks", bapi.blocks))
	r.Post("/blocks/mark", instr("blocks_mark", bapi.markBlock))
}

func (bapi *BlocksAPI) markBlock(r *http.Request) (any, []error, *api.ApiError, func()) {
	if bapi.disableAdminOperations {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("Admin operations are disabled")}, func() {}
	}
	idParam := r.FormValue("id")
	actionParam := r.FormValue("action")
	detailParam := r.FormValue("detail")

	if idParam == "" {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("ID cannot be empty")}, func() {}
	}

	if actionParam == "" {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("Action cannot be empty")}, func() {}
	}

	id, err := ulid.Parse(idParam)
	if err != nil {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("ULID %q is not valid: %v", idParam, err)}, func() {}
	}

	actionType := parse(actionParam)
	switch actionType {
	case Deletion:
		err := block.MarkForDeletion(r.Context(), bapi.logger, bapi.bkt, id, detailParam, promauto.With(nil).NewCounter(prometheus.CounterOpts{}))
		if err != nil {
			return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
		}

View on GitHub (pinned to 35b8b99117)