diff --git a/petab/lint.py b/petab/lint.py
index fe353ac..ba9ad8f 100644
--- a/petab/lint.py
+++ b/petab/lint.py
@@ -108,6 +108,10 @@ def check_condition_df(
 
     check_ids(df.index.values, kind='condition')
 
+    if not df.index.is_unique:
+        raise AssertionError("Non-unique condition IDs: "
+                             f"{df.index.values[df.index.duplicated()]}")
+
     for column_name in req_cols:
         if not np.issubdtype(df[column_name].dtype, np.number):
             assert_no_leading_trailing_whitespace(

diff --git a/tests/test_lint.py b/tests/test_lint.py
index 2756226..eccbed2 100644
--- a/tests/test_lint.py
+++ b/tests/test_lint.py
@@ -494,3 +494,36 @@ def test_check_observable_df():
     bad_observable_df.index = ['obs1', 'obs1']
     with pytest.raises(AssertionError):
         lint.check_observable_df(bad_observable_df)
+
+
+def test_condition_ids_are_unique():
+    condition_df = pd.DataFrame(data={
+        CONDITION_ID: ['condition1', 'condition1'],
+        'parameter1': [1.0, 2.0]
+    })
+    condition_df.set_index(CONDITION_ID, inplace=True)
+
+    with pytest.raises(AssertionError):
+        lint.check_condition_df(condition_df)
+
+    condition_df.index = ['condition0', 'condition1']
+    condition_df.index.name = 'conditionId'
+    lint.check_condition_df(condition_df)
+
+
+def test_parameter_ids_are_unique():
+    parameter_df = pd.DataFrame({
+        PARAMETER_ID: ['par0', 'par0'],
+        PARAMETER_SCALE: [LIN, LIN],
+        ESTIMATE: [1, 1],
+        LOWER_BOUND: [1e-5, 1e-6],
+        UPPER_BOUND: [1e5, 1e6]
+
+    }).set_index(PARAMETER_ID)
+
+    with pytest.raises(AssertionError):
+        lint.check_parameter_df(parameter_df)
+
+    parameter_df.index = ['par0', 'par1']
+    parameter_df.index.name = 'parameterId'
+    lint.check_parameter_df(parameter_df)

no lint error for non-unique condition ids
`petab.lint.check_condition_df` does not check whether index entries are unique, but this is required according to documentation.
tests/test_lint.py::test_condition_ids_are_unique
tests/test_lint.py::test_assert_measured_observables_present_in_model tests/test_lint.py::test_condition_table_is_parameter_free tests/test_lint.py::test_measurement_table_has_timepoint_specific_mappings tests/test_lint.py::test_observable_table_has_nontrivial_noise_formula tests/test_lint.py::test_assert_overrides_match_parameter_count tests/test_lint.py::test_assert_no_leading_trailing_whitespace tests/test_lint.py::test_assert_model_parameters_in_condition_or_parameter_table tests/test_lint.py::test_assert_noise_distributions_valid tests/test_lint.py::test_check_measurement_df tests/test_lint.py::test_check_parameter_bounds tests/test_lint.py::test_assert_parameter_prior_type_is_valid tests/test_lint.py::test_assert_parameter_prior_parameters_are_valid tests/test_lint.py::test_assert_measurement_conditions_present_in_condition_table tests/test_lint.py::test_check_condition_df tests/test_lint.py::test_check_ids tests/test_lint.py::test_check_parameter_df tests/test_lint.py::test_check_observable_df tests/test_lint.py::test_parameter_ids_are_unique