HumanSignal/label-studio · error · ValidationError

not_a_list

not_a_list

Error message

not a list

What it means

Raised by TaskListSerializer.to_internal_value when the submitted payload is not a JSON list/array. The bulk import API only accepts arrays of task objects; any other JSON type (object, string, number) is rejected with the non-field error 'not a list' (code not_a_list).

Source

Thrown at label_studio/tasks/serializers.py:528

        return self.context.get('project')

    @staticmethod
    def format_error(i, detail, item):
        if len(detail) == 1:
            code = f' {detail[0].code}' if detail[0].code != 'invalid' else ''
            return f'Error{code} at item {i}: {detail[0]} :: {item}'
        else:
            errors = ', '.join(detail)
            codes = [d.code for d in detail]
            return f'Errors {codes} at item {i}: {errors} :: {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({api_settings.NON_FIELD_ERRORS_KEY: 'not a list'}, code='not_a_list')

        if not self.allow_empty and len(data) == 0:
            if self.parent and self.partial:
                raise SkipField()
            raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: 'empty'}, code='empty')

        ret, errors = [], []
        self.annotation_count, self.prediction_count = 0, 0
        for i, item in enumerate(data):
            try:
                validated = self.child.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

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Wrap the task(s) in a JSON array: send [{"data": {...}}] instead of {"data": {...}}
  2. If importing a single task, still submit it as a one-element list
  3. Use the Python SDK's create_tasks/import_tasks helpers which accept a list and format correctly
  4. Check that your client isn't sending multipart/form or raw strings where the endpoint expects a JSON array

Example fix

// before
await fetch(`/api/projects/${id}/import`, { method: 'POST', body: JSON.stringify({ data: { text: 'x' } }) });
// after
await fetch(`/api/projects/${id}/import`, { method: 'POST', body: JSON.stringify([{ data: { text: 'x' } }]) });
Defensive patterns

Strategy: type-guard

Validate before calling

if (!Array.isArray(tasks)) throw new Error('import payload must be a JSON array of task objects');

Type guard

function isTaskList(v) {
  return Array.isArray(v) && v.every(t => typeof t === 'object' && t !== null && 'data' in t);
}

Try / catch

try {
  await importTasks(projectId, payload);
} catch (e) {
  if (JSON.stringify(e.response?.data || {}).includes('not a list')) {
    payload = Array.isArray(payload) ? payload : [payload]; // wrap single object
    return importTasks(projectId, payload);
  } throw e;
}

Prevention

When it happens

Trigger: POST to the task import endpoints with a body like {"data": {...}} (a bare task object instead of a list), a plain string, or a number, so isinstance(data, list) fails.

Common situations: Clients posting a single task object instead of wrapping it in an array; sending CSV-as-string content directly as JSON; SDK misuse where the tasks argument is a dict; older integrations written for a different endpoint shape.

Related errors


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