HumanSignal/label-studio · error · ValidationError
Error validating annotation: {validation_errors}
Error message
Error validating annotation: {validation_errors} What it means
This error is raised by AnnotationSerializer.validate when the project's custom_interface_validator returns non-empty validation errors for the annotation's result data. Label Studio allows projects to define a custom validation interface that checks annotation results (e.g., per-region constraints) before saving; any findings are surfaced as a single ValidationError wrapping the validator's messages.
Source
Thrown at label_studio/tasks/serializers.py:236
def validate(self, data):
"""Validate annotation result against project config and custom interface output_schema."""
if 'result' not in data or data.get('was_cancelled') is True:
return super().validate(data)
project = self._resolve_project_for_validation(data)
custom_interface_validator = load_func(getattr(settings, 'CUSTOM_INTERFACE_ANNOTATION_VALIDATOR', None))
if custom_interface_validator and project:
task = data.get('task') or self.context.get('task') or getattr(self.instance, 'task', None)
request = self.context.get('request')
user = getattr(request, 'user', None) if request is not None else None
# Integrity binds to the annotation author, not the acting user:
# a reviewer editing a contributor's annotation must validate
# against the contributor's uploads.
if self.instance is not None:
user = getattr(self.instance, 'completed_by', None) or user
validation_errors = custom_interface_validator(project, data.get('result', []), task=task, user=user)
if validation_errors:
raise ValidationError(f'Error validating annotation: {validation_errors}')
return super().validate(data)
def get_created_username(self, annotation) -> str:
user = annotation.completed_by
if not user:
return ''
request = self.context.get('request')
requester = getattr(request, 'user', None) if request is not None else None
if AnnotatorReviewerFirewall.should_anonymize(user=user, requester=requester):
return AnnotatorReviewerFirewall.role_label(user=user, requester=requester)
project = self.context.get('project') or getattr(annotation, 'project', None)
if is_user_deleted(user, context=self.context, project=project):
return f'Deleted User {user.id} deleted-{user.id}-user@example.com, {user.id}'
name = user.first_nameView on GitHub (pinned to 0b49e9b539)
Solutions
- Inspect the {validation_errors} payload in the message to see exactly which results/regions failed the custom interface validator
- Compare the submitted 'result' array against the project's current labeling config and fix the offending result items before resubmitting
- If a reviewer is editing, verify the original contributor's uploads satisfy the validator — the validator runs as the contributor, not the reviewer
- If the custom validator logic itself is wrong (e.g., stale rules after a config change), update or relax the project's custom interface validator
- As a workaround for bulk data, disable or adjust the custom validation interface for the project in Settings
Example fix
// before
const payload = { result: [{ from_name: 'label', value: { labels: ['wrong_label'] } }] };
// after
const payload = { result: [{ from_name: 'label', value: { labels: ['expected_label'] } }] }; // match custom validator expectations Defensive patterns
Strategy: validation
Validate before calling
// client-side pre-check against project constraints
function isAnnotationValid(project, result, user) {
if (!Array.isArray(result) || result.length === 0) return false;
return result.every(r =>
r && typeof r.from_name === 'string' && r.type &&
project.labels?.[r.from_name] // from_name must exist in current labeling config
);
}
if (!isAnnotationValid(project, payload.result, user)) {
throw new Error('result violates project custom interface validation');
} Type guard
function isAnnotationPayload(p) {
return typeof p === 'object' && p !== null && Array.isArray(p.result);
} Try / catch
try {
const annotation = await api.post(`/api/tasks/${taskId}/annotations/`, payload);
} catch (e) {
if (e.response?.status === 400 && JSON.stringify(e.response.data).includes('Error validating annotation')) {
console.warn('Custom validation failed:', e.response.data);
} else throw e;
} Prevention
- Keep the labeling config stable or re-validate existing annotations after config changes
- Log the full validation_errors payload from the 400 response before retrying
- Mirror the custom validator's rules in client-side pre-submit checks
- When reviewers edit annotations, remember validation runs against the contributor's context
When it happens
Trigger: POST/PATCH to the annotations API (/api/tasks/<id>/annotations/ or /api/annotations/<id>/) where project.build_url or project settings define a custom_interface_validator and the submitted 'result' array violates the project's custom validation rules. Note: for a reviewer editing an existing annotation, validation is run against the original contributor (instance.completed_by) rather than the reviewer.
Common situations: Custom label config changed after annotations were submitted so old results no longer validate; client plugins/scripts producing results that don't match the custom validator's expectations; reviewers editing annotations and the validator checking the contributor's uploads; exporting/re-importing annotations with fields the custom validator rejects.
Related errors
- Created annotations are incompatible with provided labeling
- There are {count} annotation(s) created with tag "{control_t
- This task cannot be skipped.
- Maximum task number is {settings.TASKS_MAX_NUMBER}, current
- Maximum total size of all files is {settings.TASKS_MAX_FILE_
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/01fc4c6e86dda9c6.
Report an issue: GitHub.