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
- Wrap the payload in an array: send [{...}] instead of {...}
- If the payload is {"tasks": [...]}, extract and send the list value
- Parse the body client-side and verify isinstance(payload, list) before POSTing
- 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
- Always send a JSON array, even for a single task
- Unwrap wrapper objects like {'tasks': [...]} before sending
- Avoid double-serialization (passing a JSON string as the body)
- Assert isinstance(payload, list) in your import helper
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
- Annotation must have "result" fields
- "result" field in annotation must be list
- Prediction must have "result" fields
- All tasks are empty (None)
- data is empty
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/3b235302555b7974.
Report an issue: GitHub.