HumanSignal/label-studio · error · ValidationError

data is not a list

Error message

data is not a list

What it means

TaskValidator.to_internal_value raises this when the import payload is neither None nor a list — e.g. a dict, string, or number. The import API expects tasks as a JSON array (even for a single task), so any other top-level type is rejected.

Source

Thrown at label_studio/tasks/validation.py:219

        return task

    @staticmethod
    def format_error(i, detail, item):
        if len(detail) == 1:
            code = (str(detail[0].code + ' ')) if detail[0].code != 'invalid' else ''
            return 'Error {code} at item {i}: {detail} :: {item}'.format(code=code, i=i, detail=detail[0], item=item)
        else:
            errors = ', '.join(detail)
            codes = str([d.code for d in detail])
            return 'Errors {codes} at item {i}: {errors} :: {item}'.format(codes=codes, i=i, errors=errors, item=item)

    def to_internal_value(self, data):
        """Body of run_validation for all data items"""
        if data is None:
            raise ValidationError('All tasks are empty (None)')

        if not isinstance(data, list):
            raise ValidationError('data is not a list')

        if len(data) == 0:
            raise ValidationError('data is empty')

        ret, errors = [], []
        self.annotation_count, self.prediction_count = 0, 0
        for i, item in enumerate(data):
            try:
                validated = self.validate(item)
            except ValidationError as exc:
                error = self.format_error(i, exc.detail, item)
                errors.append(error)
                # do not print to user too many errors
                if len(errors) >= 100:
                    errors[99] = '...'
                    break
            else:
                ret.append(validated)

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Wrap the payload in an array: send [{...}] instead of {...}
  2. If the payload is {"tasks": [...]}, extract and send the list value
  3. Parse the body client-side and verify isinstance(payload, list) before POSTing
  4. If sending CSV, use the proper import endpoint/endpoint params so the server converts it to a list

Example fix

// before
{"data": {"text": "hi"}}
// after
[{"data": {"text": "hi"}}]
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(tasks, list):
    tasks = [tasks]  # wrap single task dict into a list

Type guard

def is_task_list(tasks):
    return isinstance(tasks, list) and all(isinstance(t, (dict, object)) for t in tasks)

Try / catch

try:
    client.import_tasks(id=project_id, tasks=tasks)
except LabelStudioError as e:
    if 'data is not a list' in str(e):
        client.import_tasks(id=project_id, tasks=[tasks] if isinstance(tasks, dict) else list(tasks))
    else:
        raise

Prevention

When it happens

Trigger: POSTing a single task as an object {"data": {...}} instead of an array; sending a raw string of task text; sending JSON where the top level is a dict like {"tasks": [...]} that wasn't unwrapped.

Common situations: Developers assuming the API accepts one task as a bare object; CSV/JSON converters emitting a dict keyed by ID; clients double-serializing so the body becomes a JSON string.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/3b235302555b7974. Report an issue: GitHub.