{"record":{"id":"d12389b345deefdb","repo":"HumanSignal/label-studio","slug":"detail-pydantic-validation-message","errorCode":null,"errorMessage":"detail: {pydantic validation message}","messagePattern":"detail: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/fsm/api.py","lineNumber":186,"sourceCode":"            triggers_on_update = getattr(transition_class, '_triggers_on_update', False)\n            if triggers_on_create or triggers_on_update:\n                raise ValidationError(\n                    {'transition_name': ['Transition is auto-triggered and cannot be executed manually']}\n                )\n\n        # Execute transition\n        StateManager = get_state_manager()\n        try:\n            state_record = StateManager.execute_transition(\n                entity=entity,\n                transition_name=transition_name,\n                transition_data=transition_data,\n                user=request.user,\n                organization_id=getattr(request.user, 'active_organization_id', None),\n            )\n        except PydanticValidationError as e:\n            # Pydantic schema validation errors from transition instantiation\n            raise ValidationError({'detail': extract_message(e)})\n        except TransitionValidationError as e:\n            # Explicit validation failure\n            logger.warning(\n                f'Transition validation failed with context: {e.context} and error: {e} for entity: {entity.id}'\n            )\n            raise ValidationError({'detail': extract_message(e)})\n        # Handle feature-flag disabled path (no state record created)\n        if state_record is None:\n            response_payload = {\n                'success': True,\n                'new_state': None,\n                'state_record': None,\n            }\n        else:\n            response_payload = {\n                'success': True,\n                'new_state': state_record.state,\n                # Pass model instance; nested serializer will handle representation","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/fsm/api.py#L168-L204","documentation":"When FSMEntityTransitionAPI.post executes a transition, the transition's payload is instantiated against a pydantic schema. If that instantiation raises pydantic's ValidationError, the API converts it into a DRF 400 ValidationError with the message extracted by extract_message. This is a request-payload shape/type failure for the transition_data you supplied, not an internal failure.","triggerScenarios":"POSTing to the FSM transition endpoint where transition_data does not match the transition class's pydantic input schema: missing required fields, wrong types, extra fields when extra=\"forbid\", or values failing field constraints.","commonSituations":"Sending flat fields instead of the nested transition_data object; passing string values where pydantic expects int/enum; after upgrading, the transition schema gained a new required field that older clients don't send; typos in transition_data keys.","solutions":["Read the returned pydantic message and fix transition_data to match the transition's pydantic schema (correct keys, types, required fields).","Inspect the transition class's pydantic model in the code to see the exact expected payload shape.","Validate the payload client-side with the same pydantic model (or its JSON schema) before calling the API.","Check for a version mismatch where the deployed transition schema is newer than the client; update the client payload accordingly."],"exampleFix":"// before (400: 'transition_data.comment: field required')\n{\"transition_name\": \"reject_annotation\", \"transition_data\": {\"reason\": \"bad\"}}\n\n// after\n{\"transition_name\": \"reject_annotation\", \"transition_data\": {\"comment\": \"bad label\", \"reason\": \"bad\"}}","handlingStrategy":"validation","validationCode":"from fsm.registry import transition_registry\n\ntransition_class = transition_registry.get_transition(entity_name, transition_name)\nvalidated = transition_class.InputSchema(**transition_data)  # raises locally, same message pydantic would give\npayload = {'transition_name': transition_name, 'transition_data': validated.model_dump()}","typeGuard":"def has_valid_transition_data(transition_class, transition_data: dict) -> bool:\n    schema = getattr(transition_class, 'InputSchema', None) or getattr(transition_class, 'input_schema', None)\n    if schema is None:\n        return True\n    try:\n        schema(**transition_data)\n        return True\n    except Exception:\n        return False","tryCatchPattern":"try:\n    resp = client.execute_transition(entity_type, entity_id, transition_name, transition_data)\nexcept Exception as e:\n    if 'detail' in str(e):  # pydantic message surfaced by the API\n        logger.error('invalid transition_data for %s: %s', transition_name, e)\n    raise","preventionTips":["Validate payloads against the transition's pydantic schema client-side before calling the API","Generate request payloads from the schema (JSON schema / model_dump), not hand-written dicts","Keep client schema copies in sync when transition models change"],"tags":["pydantic","validation","api","fsm"],"backgroundTag":"schema-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}