diff --git a/toardb/contacts/contacts.py b/toardb/contacts/contacts.py
index 804c32c2cb121a7ca0e678f7c32d178294200be0..0a57194b7a103761469a8b2916cc13d5babbac1d 100644
--- a/toardb/contacts/contacts.py
+++ b/toardb/contacts/contacts.py
@@ -5,8 +5,9 @@
 Simple API for contacts management
 """
 
-from typing import List, Union
-from fastapi import APIRouter, Depends, HTTPException, Body
+from typing import List, Union, Literal
+from fastapi import APIRouter, Depends, HTTPException, Body, Request
+
 from sqlalchemy.orm import Session
 from . import crud, schemas
 from toardb.utils.database import ToarDbSession, get_db
@@ -18,9 +19,9 @@ router = APIRouter()
 # 1. persons
 
 #get all entries of table persons
-@router.get('/contacts/persons/', response_model=List[schemas.Person])
-def get_all_persons(offset: int = 0, limit: int = 10, db: Session = Depends(get_db)):
-    persons = crud.get_all_persons(db, offset=offset, limit=limit)
+@router.get('/contacts/persons/', response_model=List[schemas.Person], response_model_exclude_none=True, response_model_exclude_unset=True)
+def get_all_persons(request: Request, db: Session = Depends(get_db)):
+    persons = crud.get_all_persons(db, path_params=request.path_params, query_params=request.query_params)
     return persons
 
 #get all metadata of one person
@@ -50,7 +51,9 @@ def create_person(person: schemas.PersonCreate = Body(..., embed = True), db: Se
 
 #get all entries of table organisations
 @router.get('/contacts/organisations/', response_model=List[schemas.Organisation])
-def get_all_organisations(offset: int = 0, limit: int = 10, db: Session = Depends(get_db)):
+def get_all_organisations(offset: int = 0, limit: int | Literal["None"] = 10, db: Session = Depends(get_db)):
+    if limit == "None":
+        limit = None
     organisations = crud.get_all_organisations(db, offset=offset, limit=limit)
     return organisations
 
@@ -79,7 +82,9 @@ def create_organisation(organisation: schemas.OrganisationCreate = Body(..., emb
 # 3. combined (person + organisation)
 #get all entries of table contacts
 @router.get('/contacts/', response_model=List[schemas.Contact])
-def get_all_contacts(offset: int = 0, limit: int = 10, db: Session = Depends(get_db)):
+def get_all_contacts(offset: int = 0, limit: int | Literal["None"] = 10, db: Session = Depends(get_db)):
+    if limit == "None":
+        limit = None
     contacts = crud.get_all_contacts(db, offset=offset, limit=limit)
     return contacts
 
diff --git a/toardb/contacts/crud.py b/toardb/contacts/crud.py
index 6fffaaeaadbba2cfb64302b0d89ba3d9953f7b54..fc8c3b2337bb4e442665be877c6b916b35f55bd0 100644
--- a/toardb/contacts/crud.py
+++ b/toardb/contacts/crud.py
@@ -6,11 +6,12 @@ Create, Read, Update, Delete functionality
 
 """
 
+from sqlalchemy import text
 from sqlalchemy.orm import Session
 from fastapi.responses import JSONResponse
 from . import models
 from .schemas import PersonCreate, OrganisationCreate
-from toardb.utils.utils import get_value_from_str
+from toardb.utils.utils import get_value_from_str, create_filter
 import toardb
 
 
@@ -48,8 +49,30 @@ def get_person(db: Session, person_id: int):
     return db.query(models.Person).filter(models.Person.id == person_id).filter(models.Person.isprivate == False).first()
 
 
-def get_all_persons(db: Session, limit: int, offset: int = 0):
-    return db.query(models.Person).filter(models.Person.id != 0).filter(models.Person.isprivate == False).order_by(models.Person.id).offset(offset).limit(limit).all()
+def get_all_persons(db: Session, path_params, query_params):
+    try:
+        limit, offset, fields, format, filters = create_filter(query_params, "persons")
+        p_filter = filters["p_filter"]
+    except (KeyError, ValueError) as e:
+        status_code=400
+        return JSONResponse(status_code=status_code, content=str(e))
+
+    if fields:
+        fields2 = fields.split(',')
+        db_objects_l = db.query(*map(text,fields2)).select_from(models.Person). \
+                           filter(models.Person.id != 0).filter(models.Person.isprivate == False). \
+                           filter(text(p_filter)). \
+                           order_by(models.Person.id). \
+                           limit(limit).offset(offset)
+        db_objects = []
+        for db_object_immut in db_objects_l:
+            db_object = dict(zip(fields.split(','),db_object_immut))
+            db_objects.append(db_object)
+    else:
+        db_objects = db.query(models.Person).filter(models.Person.id != 0).filter(models.Person.isprivate == False). \
+              filter(text(p_filter)). \
+              order_by(models.Person.id).offset(offset).limit(limit).all()
+    return db_objects
 
 
 def get_person_by_name(db: Session, name: str):
diff --git a/toardb/contacts/schemas.py b/toardb/contacts/schemas.py
index 7d90d6ed9bd69e6ab3045856e88bae835925a8af..738b9e681d1347bd98bad66fe471b011de94b190 100644
--- a/toardb/contacts/schemas.py
+++ b/toardb/contacts/schemas.py
@@ -62,11 +62,11 @@ class Organisation(OrganisationBase):
 
 class PersonBase(BaseModel):
     id: int = Field(None, description="for internal use only")
-    name: str = Field(..., description="Name of person")
-    email: str = Field(..., description="Email address of person")
-    phone: str = Field(..., description="Phone number of person")
+    name: str = Field(None, description="Name of person")
+    email: str = Field(None, description="Email address of person")
+    phone: str = Field(None, description="Phone number of person")
     orcid: str = Field(None, description="ORCID-iD of person")
-    isprivate: bool = Field(..., description="Set this flag to true if the contact details shall not be exposed publicly")
+    isprivate: bool = Field(None, description="Set this flag to true if the contact details shall not be exposed publicly")
 
     def __str__(self):
         return f"{self.name} <{self.email}>"
@@ -80,7 +80,7 @@ class PersonCreate(PersonBase):
 
 
 class Person(PersonBase):
-    id: int = Field(..., description="for internal use only")
+    id: int = Field(None, description="for internal use only")
 
     class Config:
         orm_mode = True
diff --git a/toardb/contacts/test_contacts.py b/toardb/contacts/test_contacts.py
index 8d63c0329d4ae94d23355d5a1d67993bf378b6f2..82dec3badf66060f27372bd209307d627414a3b2 100644
--- a/toardb/contacts/test_contacts.py
+++ b/toardb/contacts/test_contacts.py
@@ -179,11 +179,11 @@ class TestApps:
 
 
     def test_get_special_person(self, client, db):
-        response = client.get("/contacts/persons/id/3")
+        response = client.get("/contacts/persons/?id=3")
         expected_status_code = 200
         assert response.status_code == expected_status_code
-        expected_resp = {'id': 3, 'name': 'Sabine Schröder', 'email': 's.schroeder@fz-juelich.de',
-                         'phone': '+49-2461-61-6397', 'orcid': '0000-0002-0309-8010', 'isprivate': False}
+        expected_resp = [{'id': 3, 'name': 'Sabine Schröder', 'email': 's.schroeder@fz-juelich.de',
+                         'phone': '+49-2461-61-6397', 'orcid': '0000-0002-0309-8010', 'isprivate': False}]
         assert response.json() == expected_resp
 
 
@@ -196,11 +196,27 @@ class TestApps:
 
 
     def test_get_special_person_by_name(self, client, db):
-        response = client.get("/contacts/persons/Sabine Schröder")
+        response = client.get("/contacts/persons/?name=Sabine Schröder")
+        expected_status_code = 200
+        assert response.status_code == expected_status_code
+        expected_resp = [{'id': 3, 'name': 'Sabine Schröder', 'email': 's.schroeder@fz-juelich.de',
+                         'phone': '+49-2461-61-6397', 'orcid': '0000-0002-0309-8010', 'isprivate': False}]
+        assert response.json() == expected_resp
+
+
+    def test_get_special_person_by_name_using_fields(self, client, db):
+        response = client.get("/contacts/persons/?name=Sabine Schröder&fields=orcid")
         expected_status_code = 200
         assert response.status_code == expected_status_code
-        expected_resp = {'id': 3, 'name': 'Sabine Schröder', 'email': 's.schroeder@fz-juelich.de',
-                         'phone': '+49-2461-61-6397', 'orcid': '0000-0002-0309-8010', 'isprivate': False}
+        expected_resp = [{'orcid': '0000-0002-0309-8010'}]
+        assert response.json() == expected_resp
+
+
+    def test_get_special_person_using_wrong_fieldname(self, client, db):
+        response = client.get("/contacts/persons/?name=Sabine Schröder&fields=orcid,bla")
+        expected_status_code = 400
+        assert response.status_code == expected_status_code
+        expected_resp = "Wrong field given: bla"
         assert response.json() == expected_resp
 
 
diff --git a/toardb/data/crud.py b/toardb/data/crud.py
index a0c1525743101dd387d4f91ebceef5b61513fd6b..792664b1c2ce85fda4ef9d5395e2160a72d0245c 100644
--- a/toardb/data/crud.py
+++ b/toardb/data/crud.py
@@ -125,7 +125,8 @@ def get_data(db: Session, timeseries_id: int, path_params, query_params):
         flags = ",".join([item.strip() for v in query_params.getlist("flags") for item in v.split(',')])
         if not flags:
             flags = None
-        limit, offset, fields, format, t_filter, t_r_filter, s_c_filter, s_g_filter, d_filter = create_filter(query_params, "data")
+        limit, offset, fields, format, filters = create_filter(query_params, "data")
+        d_filter = filters["d_filter"]
     except KeyError as e:
         status_code=400
         return JSONResponse(status_code=status_code, content=str(e))
@@ -180,9 +181,10 @@ def get_data_with_staging(db: Session, timeseries_id: int, flags: str, format: s
             dummy = record.variable
             dummy = record.programme
             dummy = record.changelog
-            attribution, citation = get_citation(db, timeseries_id=timeseries_id).values()
+            attribution, citation, license_txt = get_citation(db, timeseries_id=timeseries_id).values()
             record_dict = record.__dict__
             record_dict['citation'] = citation
+            record_dict['license'] =license_txt
             if attribution != None:
                 record_dict['attribution'] = attribution
             return TimeseriesWithCitation(**record_dict)
diff --git a/toardb/data/toarqc_config/leaf_area_index_realtime.json b/toardb/data/toarqc_config/leaf_area_index_realtime.json
new file mode 100644
index 0000000000000000000000000000000000000000..6cdb18dbd596da7ea8c239e64352ecfd86712551
--- /dev/null
+++ b/toardb/data/toarqc_config/leaf_area_index_realtime.json
@@ -0,0 +1,9 @@
+[
+  {
+    "range_test":
+      {
+        "low_thres": 0,
+        "high_thres": 7
+      }
+  }
+]
diff --git a/toardb/data/toarqc_config/leaf_area_index_realtime.yaml b/toardb/data/toarqc_config/leaf_area_index_realtime.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..62bcfb5603b4ffefeb94e2760bbc8444f9db4ca4
--- /dev/null
+++ b/toardb/data/toarqc_config/leaf_area_index_realtime.yaml
@@ -0,0 +1,5 @@
+---
+- range_test:
+    low_thres: 0
+    high_thres: 7
+...
diff --git a/toardb/data/toarqc_config/leaf_area_index_standard.json b/toardb/data/toarqc_config/leaf_area_index_standard.json
new file mode 100644
index 0000000000000000000000000000000000000000..6cdb18dbd596da7ea8c239e64352ecfd86712551
--- /dev/null
+++ b/toardb/data/toarqc_config/leaf_area_index_standard.json
@@ -0,0 +1,9 @@
+[
+  {
+    "range_test":
+      {
+        "low_thres": 0,
+        "high_thres": 7
+      }
+  }
+]
diff --git a/toardb/data/toarqc_config/leaf_area_index_standard.yaml b/toardb/data/toarqc_config/leaf_area_index_standard.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..62bcfb5603b4ffefeb94e2760bbc8444f9db4ca4
--- /dev/null
+++ b/toardb/data/toarqc_config/leaf_area_index_standard.yaml
@@ -0,0 +1,5 @@
+---
+- range_test:
+    low_thres: 0
+    high_thres: 7
+...
diff --git a/toardb/data/toarqc_config/tdew2m_realtime.json b/toardb/data/toarqc_config/tdew2m_realtime.json
new file mode 100644
index 0000000000000000000000000000000000000000..cbf69ffd76a7f3cb536644924f8591b6422a2a30
--- /dev/null
+++ b/toardb/data/toarqc_config/tdew2m_realtime.json
@@ -0,0 +1,9 @@
+[
+  {
+    "range_test":
+      {
+        "low_thres": -85,
+        "high_thres": 60
+      }
+  }
+]
diff --git a/toardb/data/toarqc_config/tdew2m_realtime.yaml b/toardb/data/toarqc_config/tdew2m_realtime.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..359dfde66d10b71c80817d1a1074b1a07de6abf5
--- /dev/null
+++ b/toardb/data/toarqc_config/tdew2m_realtime.yaml
@@ -0,0 +1,5 @@
+---
+- range_test:
+    low_thres: -85
+    high_thres: 60
+...
diff --git a/toardb/data/toarqc_config/tdew2m_standard.json b/toardb/data/toarqc_config/tdew2m_standard.json
new file mode 100644
index 0000000000000000000000000000000000000000..cbf69ffd76a7f3cb536644924f8591b6422a2a30
--- /dev/null
+++ b/toardb/data/toarqc_config/tdew2m_standard.json
@@ -0,0 +1,9 @@
+[
+  {
+    "range_test":
+      {
+        "low_thres": -85,
+        "high_thres": 60
+      }
+  }
+]
diff --git a/toardb/data/toarqc_config/tdew2m_standard.yaml b/toardb/data/toarqc_config/tdew2m_standard.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..359dfde66d10b71c80817d1a1074b1a07de6abf5
--- /dev/null
+++ b/toardb/data/toarqc_config/tdew2m_standard.yaml
@@ -0,0 +1,5 @@
+---
+- range_test:
+    low_thres: -85
+    high_thres: 60
+...
diff --git a/toardb/stationmeta/crud.py b/toardb/stationmeta/crud.py
index 9b154f328ed5f818f29d0172a8f5d88102417540..e1dbe7addb42c7f2b1e274e5f9b43a44b13e869f 100644
--- a/toardb/stationmeta/crud.py
+++ b/toardb/stationmeta/crud.py
@@ -102,8 +102,10 @@ def get_all_stationmeta_core(db: Session, limit: int, offset: int = 0):
 # same method as above (is the above still needed?)
 def get_all_stationmeta(db: Session, path_params, query_params):
     try:
-        limit, offset, fields, format, t_filter, t_r_filter, s_c_filter, s_g_filter, d_filter = create_filter(query_params, "stationmeta")
-    except KeyError as e:
+        limit, offset, fields, format, filters = create_filter(query_params, "stationmeta")
+        s_c_filter = filters["s_c_filter"]
+        s_g_filter = filters["s_g_filter"]
+    except (KeyError, ValueError) as e:
         status_code=400
         return JSONResponse(status_code=status_code, content=str(e))
 
diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py
index e55274f4cd554b3c3ecc6efbe4d4f0e833d88ff4..3e3c792b3091d1ea398bddc0555ec90c484c38b8 100644
--- a/toardb/timeseries/crud.py
+++ b/toardb/timeseries/crud.py
@@ -27,6 +27,26 @@ from toardb.utils.utils import get_value_from_str, get_str_from_value, create_fi
 import toardb
 
 
+def clean_additional_metadata(ad_met_dict):
+    if not isinstance(ad_met_dict,dict):
+        tmp = ad_met_dict.replace('"','\\"')
+        return tmp.replace("'",'"')
+    # there is a mismatch with additional_metadata
+    additional_metadata = ad_met_dict
+    for key, value in additional_metadata.items():
+        if isinstance(value,dict):
+            for key2, value2 in value.items():
+                if isinstance(value2,str):
+                    additional_metadata[key][key2] = value2.replace("'","$apostroph$")
+        else:
+            if isinstance(value,str):
+                additional_metadata[key] = value.replace("'","$apostroph$")
+    additional_metadata = str(additional_metadata).replace('"','\\"')
+    additional_metadata = str(additional_metadata).replace("'",'"')
+    additional_metadata = str(additional_metadata).replace("$apostroph$","'")
+    return additional_metadata
+
+
 def get_timeseries(db: Session, timeseries_id: int, fields: str = None):
     if fields:
         fields = ','.join('"{}"'.format(word) for word in fields.split(','))
@@ -35,7 +55,7 @@ def get_timeseries(db: Session, timeseries_id: int, fields: str = None):
         resultproxy = db.execute(f'SELECT {fields} FROM timeseries WHERE id={timeseries_id} LIMIT 1')
         db_object_dict = [ rowproxy._asdict() for rowproxy in resultproxy ][0]
         if fields.find('additional_metadata') != -1:
-            db_object_dict['additional_metadata'] = str(db_object_dict['additional_metadata']).replace("'",'"')
+            db_object_dict['additional_metadata'] = clean_additional_metadata(db_object_dict['additional_metadata'])
         db_object = models.Timeseries(**db_object_dict)
     else:
         db_object = db.query(models.Timeseries).filter(models.Timeseries.id == timeseries_id).first()
@@ -48,14 +68,14 @@ def get_timeseries(db: Session, timeseries_id: int, fields: str = None):
         if db_object:
             try:
                 # there is a mismatch with additional_metadata
-                db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"')
+                db_object.additional_metadata = clean_additional_metadata(db_object.additional_metadata)
             except:
                 pass
             try:
                 # there is also a mismatch with coordinates and additional_metadata from station object
                 if isinstance(db_object.station.coordinates, (WKBElement, WKTElement)):
                     db_object.station.coordinates = get_coordinates_from_geom(db_object.station.coordinates)
-                db_object.station.additional_metadata = str(db_object.station.additional_metadata).replace("'",'"')
+                db_object.station.additional_metadata = clean_additional_metadata(db_object.station.additional_metadata)
             except:
                 pass
     return db_object
@@ -70,16 +90,22 @@ def get_citation(db: Session, timeseries_id: int, datetime: dt.datetime = None):
     db_object = db.query(models.Timeseries).filter(models.Timeseries.id == timeseries_id).first()
     # there is a mismatch with additional_metadata
     PI = "unknown"
+    originators = False
     attribution = None
     if db_object:
         pi_role = get_value_from_str(toardb.toardb.RC_vocabulary,'PrincipalInvestigator')
         originator_role = get_value_from_str(toardb.toardb.RC_vocabulary,'Originator')
+        contributor_role = get_value_from_str(toardb.toardb.RC_vocabulary,'Contributor')
         list_of_originators = []
         for db_role in db_object.roles:
             if (db_role.role == pi_role):
                 db_contact = get_contact(db, contact_id = db_role.contact_id)
                 PI = db_contact.name
             elif (db_role.role == originator_role):
+                originators = True
+                db_contact = get_contact(db, contact_id = db_role.contact_id)
+                list_of_originators.append(db_contact.name)
+            elif (db_role.role == contributor_role):
                 db_contact = get_contact(db, contact_id = db_role.contact_id)
                 list_of_originators.append(db_contact.name)
         list_of_data_originators = ", ".join(list_of_originators)
@@ -97,7 +123,7 @@ def get_citation(db: Session, timeseries_id: int, datetime: dt.datetime = None):
         dataset_version = db_object.provider_version
     citation = f"{PI}: time series of {var} at {station}, accessed from the TOAR database on {datetime}"
     if attribution and list_of_data_originators:
-        attribution = attribution.format(list_of_data_originators=list_of_data_originators)
+        attribution = attribution.format(orga_or_origs="data originators" if originators else "contributing organisations", list_of_data_originators=list_of_data_originators)
     if dataset_version != 'N/A':
         citation += f", original dataset version {dataset_version}"
     license_txt = "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"
@@ -111,7 +137,7 @@ def adapt_db_object(fields, fields1, db_object_immut):
     except:
         pass
     try:
-        db_object['additional_metadata'] = str(db_object['additional_metadata']).replace("'",'"')
+        db_object['additional_metadata'] = clean_additional_metadata(db_object['additional_metadata'])
     except:
         pass
     if "changelog" in fields:
@@ -126,8 +152,12 @@ def adapt_db_object(fields, fields1, db_object_immut):
 
 def search_all(db, path_params, query_params):
     try: 
-        limit, offset, fields, format, t_filter, t_r_filter, s_c_filter, s_g_filter, d_filter = create_filter(query_params, "search")
-    except KeyError as e:
+        limit, offset, fields, format, filters = create_filter(query_params, "search")
+        t_filter = filters["t_filter"]
+        t_r_filter = filters["t_r_filter"]
+        s_c_filter = filters["s_c_filter"]
+        s_g_filter = filters["s_g_filter"]
+    except (KeyError, ValueError) as e:
         status_code=400
         return JSONResponse(status_code=status_code, content=str(e))
 
@@ -208,12 +238,12 @@ def search_all(db, path_params, query_params):
                                order_by(models.Timeseries.id). \
                                limit(limit).offset(offset).all()
         for db_object in db_objects:
-            # there is a mismatch with additional_metadata
-            db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"')
             # there is also a mismatch with coordinates and additional_metadata from station object
             if isinstance(db_object.station.coordinates, (WKBElement, WKTElement)):
                 db_object.station.coordinates = get_coordinates_from_geom(db_object.station.coordinates)
-            db_object.station.additional_metadata = str(db_object.station.additional_metadata).replace("'",'"')
+            # there is a mismatch with additional_metadata
+            db_object.station.additional_metadata = clean_additional_metadata(db_object.station.additional_metadata)
+            db_object.additional_metadata = clean_additional_metadata(db_object.additional_metadata)
             # only for internal use!
             del db_object.data_license_accepted
             del db_object.dataset_approved_by_provider
@@ -223,24 +253,40 @@ def search_all(db, path_params, query_params):
 #def get_all_timeseries(db: Session, limit: int, offset: int, station_code: str):
 def get_all_timeseries(db, path_params, query_params):
     try: 
-        limit, offset, fields, format, t_filter, tr_filter, s_c_filter, s_g_filter, d_filter = create_filter(query_params, "timeseries")
-    except KeyError as e:
+        limit, offset, fields, format, filters = create_filter(query_params, "timeseries")
+        t_filter = filters["t_filter"]
+        s_c_filter = filters["s_c_filter"]
+    except (KeyError, ValueError) as e:
         status_code=400
         return JSONResponse(status_code=status_code, content=str(e))
-    db_objects = db.query(models.Timeseries).filter(text(t_filter)).order_by(models.Timeseries.id). \
-                           limit(limit).offset(offset).all()
-    for db_object in db_objects:
-        # there is a mismatch with additional_metadata
-        db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"')
-        # there is also a mismatch with coordinates and additional_metadata from station object
-        if isinstance(db_object.station.coordinates, (WKBElement, WKTElement)):
-            db_object.station.coordinates = get_coordinates_from_geom(db_object.station.coordinates)
-        db_object.station.additional_metadata = str(db_object.station.additional_metadata).replace("'",'"')
-        # only for internal use!
-        del db_object.data_license_accepted
-        del db_object.dataset_approved_by_provider
 
-    return db_objects
+    if fields:
+        fields2 = fields.split(',')
+        db_objects_l = db.query(*map(text,fields2)).select_from(models.Timeseries).filter(text(s_c_filter)). \
+                           order_by(models.Timeseries.id). \
+                           limit(limit).offset(offset)
+        db_objects = []
+        for db_object_immut in db_objects_l:
+            db_object = dict(zip(fields.split(','),db_object_immut))
+            db_objects.append(db_object)
+    else:
+        db_objects = db.query(models.Timeseries).filter(text(t_filter)).order_by(models.Timeseries.id). \
+                               limit(limit).offset(offset).all()
+        for db_object in db_objects:
+            # there is also a mismatch with coordinates and additional_metadata from station object
+            if isinstance(db_object.station.coordinates, (WKBElement, WKTElement)):
+                db_object.station.coordinates = get_coordinates_from_geom(db_object.station.coordinates)
+            # there is a mismatch with additional_metadata
+            db_object.additional_metadata = clean_additional_metadata(db_object.additional_metadata)
+            db_object.station.additional_metadata = clean_additional_metadata(db_object.station.additional_metadata)
+            # only for internal use!
+            del db_object.data_license_accepted
+            del db_object.dataset_approved_by_provider
+
+    if limit:
+        return db_objects[:limit]
+    else:
+        return db_objects
 
 
 def get_timeseries_by_unique_constraints(db: Session, station_id: int, variable_id: int, resource_provider: str = None,
@@ -259,6 +305,11 @@ def get_timeseries_by_unique_constraints(db: Session, station_id: int, variable_
     Criterion 14.9: data filtering procedures or other special dataset identifiers (use database field 'label')
     """
 
+#   print("in get_timeseries_by_unique_constraints")
+#   print(f"station_id: {station_id}, variable_id: {variable_id}, resource_provider: {resource_provider}, ", \
+#         f"sampling_frequency: {sampling_frequency}, provider_version: {provider_version}, data_origin_type: {data_origin_type}, ", \
+#         f"data_origin: {sampling_frequency}, sampling_height: {sampling_height}, label: {label}")
+
     # filter for criterion 14.1 and 14.2
     ret_db_object = db.query(models.Timeseries).filter(models.Timeseries.station_id == station_id) \
                                                .filter(models.Timeseries.variable_id == variable_id).all()
@@ -280,7 +331,7 @@ def get_timeseries_by_unique_constraints(db: Session, station_id: int, variable_
             for role in db_object.roles:
                 # resource provider is always an organisation!
                 organisation = get_contact(db, contact_id=role.contact_id)
-                if ((organisation.longname == resource_provider) and (role_num == role.role)):
+                if ((role_num == role.role) and (organisation.longname == resource_provider)):
                     found = True
             if not found:
                 ret_db_object.pop(counter)
@@ -402,11 +453,11 @@ def get_timeseries_by_unique_constraints(db: Session, station_id: int, variable_
         if len(ret_db_object) == 1:
             ret_db_object = ret_db_object[0]
             # there is a mismatch with additional_metadata
-            ret_db_object.additional_metadata = str(ret_db_object.additional_metadata).replace("'",'"')
+            ret_db_object.additional_metadata = clean_additional_metadata(ret_db_object.additional_metadata)
             # there is also a mismatch with coordinates and additional_metadata from station object
             if isinstance(ret_db_object.station.coordinates, (WKBElement, WKTElement)):
                 ret_db_object.station.coordinates = get_coordinates_from_geom(ret_db_object.station.coordinates)
-            ret_db_object.station.additional_metadata = str(ret_db_object.station.additional_metadata).replace("'",'"')
+            ret_db_object.station.additional_metadata = clean_additional_metadata(ret_db_object.station.additional_metadata)
         else:
             status_code=405
             message=f"Timeseries not unique, more criteria need to be defined."
@@ -474,6 +525,12 @@ def create_timeseries(db: Session, timeseries: TimeseriesCreate):
         message=f"Station (station_id: {timeseries.station_id}) not found in database."
         return JSONResponse(status_code=status_code, content=message)
     if timeseries_dict['additional_metadata']:
+        for key, value in timeseries_dict['additional_metadata'].items():
+            if isinstance(value,dict):
+                for key2, value2 in value.items():
+                    timeseries_dict['additional_metadata'][key][key2] = value2.replace("''","'")
+            else:
+                timeseries_dict['additional_metadata'][key] = value.replace("''","'")
         if 'absorption_cross_section' in timeseries_dict['additional_metadata']:
             value = get_value_from_str(toardb.toardb.CS_vocabulary,timeseries_dict['additional_metadata']['absorption_cross_section'])
             timeseries_dict['additional_metadata']['absorption_cross_section'] = value
@@ -519,7 +576,8 @@ def create_timeseries(db: Session, timeseries: TimeseriesCreate):
     # there is a mismatch with additional_metadata
     # in upload command, we have now: "additional_metadata": "{}"
     # but return from this method gives (=database): "additional_metadata": {}
-    db_timeseries.additional_metadata = json.loads(str(db_timeseries.additional_metadata).replace("'",'"'))
+#   print(db_timeseries.additional_metadata)
+#   db_timeseries.additional_metadata = json.loads(str(db_timeseries.additional_metadata).replace("'",'"'))
     db_timeseries.sampling_frequency = get_value_from_str(toardb.toardb.SF_vocabulary,db_timeseries.sampling_frequency)
     db_timeseries.aggregation = get_value_from_str(toardb.toardb.AT_vocabulary,db_timeseries.aggregation)
     db_timeseries.data_origin_type = get_value_from_str(toardb.toardb.OT_vocabulary,db_timeseries.data_origin_type)
@@ -581,6 +639,12 @@ def patch_timeseries(db: Session, description: str, timeseries_id: int, timeseri
     # check for controlled vocabulary in additional metadata
     # do this in dictionary that always contains additional_metadata entry
     if timeseries_dict['additional_metadata']:
+        for key, value in timeseries_dict['additional_metadata'].items():
+            if isinstance(value,dict):
+                for key2, value2 in value.items():
+                    timeseries_dict['additional_metadata'][key][key2] = value2.replace("''","'")
+            else:
+                timeseries_dict['additional_metadata'][key] = value.replace("''","'")
         if 'absorption_cross_section' in timeseries_dict['additional_metadata']:
             value = get_value_from_str(toardb.toardb.CS_vocabulary,timeseries_dict['additional_metadata']['absorption_cross_section'])
             timeseries_dict['additional_metadata']['absorption_cross_section'] = value
@@ -593,14 +657,18 @@ def patch_timeseries(db: Session, description: str, timeseries_id: int, timeseri
     # delete empty fields from timeseries_dict already at this place, to be able to
     # distinguish between "single value correction in metadata" and "comprehensive metadata revision"
     # (see controlled vocabulary "CL_vocabulary")
+    roles_data = timeseries_dict.pop('roles', None)
+    annotations_data = timeseries_dict.pop('annotations', None)
     timeseries_dict2 = {k: v for k, v in timeseries_dict.items() if v is not None}
     number_of_elements = len(timeseries_dict2)
+    if roles_data:
+        number_of_elements +=1
+    if annotations_data:
+        number_of_elements +=1
     if (number_of_elements == 1):
         type_of_change = get_value_from_str(toardb.toardb.CL_vocabulary,"SingleValue")
     else:
         type_of_change = get_value_from_str(toardb.toardb.CL_vocabulary,"Comprehensive")
-    roles_data = timeseries_dict.pop('roles', None)
-    annotations_data = timeseries_dict.pop('annotations', None)
     db_obj = models.Timeseries(**timeseries_dict2)
 #   also the sqlalchemy get will call get_timeseries here!!!
 #   --> therefore call it right away
@@ -617,15 +685,10 @@ def patch_timeseries(db: Session, description: str, timeseries_id: int, timeseri
         db_timeseries.station.coordinates = get_geom_from_coordinates(db_timeseries.station.coordinates)
     except:
         pass
-    try:
-        db_timeseries.station.additional_metadata = json.loads(str(db_timeseries.station.additional_metadata).replace("'",'"'))
-    except:
-        pass
-    # also problems with additional metadata (correctly reported in changelog)...
-    try:
-        db_timeseries.additional_metadata = json.loads(db_timeseries.additional_metadata)
-    except:
-        pass
+#   try:
+#       db_timeseries.station.additional_metadata = json.loads(str(db_timeseries.station.additional_metadata).replace("'",'"'))
+#   except:
+#       pass
     # prepare changelog entry/entries
     no_log = (description == 'NOLOG')
     if not no_log:
@@ -641,8 +704,8 @@ def patch_timeseries(db: Session, description: str, timeseries_id: int, timeseri
     # there is a mismatch with additional_metadata
     # in upload command, we have now: "additional_metadata": "{}"
     # but return from this method gives (=database): "additional_metadata": {}
-    if timeseries_dict['additional_metadata']:
-        db_timeseries.additional_metadata = json.loads(str(timeseries_dict['additional_metadata']).replace("'",'"'))
+#   if timeseries_dict['additional_metadata']:
+#       db_timeseries.additional_metadata = clean_additional_metadata(db_timeseries.additional_metadata)
     db.add(db_timeseries)
     result = db.commit()
     # store roles and update association table
@@ -661,7 +724,7 @@ def patch_timeseries(db: Session, description: str, timeseries_id: int, timeseri
                 old_roles.append(old_value)
             old_values['roles'] = old_roles
         for r in roles_data:
-            db_role = models.StationmetaRole(**r)
+            db_role = models.TimeseriesRole(**r)
             db_role.role = get_value_from_str(toardb.toardb.RC_vocabulary,db_role.role)
             db_role.status = get_value_from_str(toardb.toardb.RS_vocabulary,db_role.status)
             # check whether role is already present in database
diff --git a/toardb/timeseries/fixtures/timeseries.json b/toardb/timeseries/fixtures/timeseries.json
index 99e0649c1705b31495819782dba833985fb57a2c..7c4cfc7d053fa64a0e349b1558434f51485c2579 100644
--- a/toardb/timeseries/fixtures/timeseries.json
+++ b/toardb/timeseries/fixtures/timeseries.json
@@ -25,6 +25,6 @@
     "data_origin": 0,
     "data_origin_type": 0,
     "sampling_height": 7,
-    "additional_metadata": "{}" 
+    "additional_metadata": "{\"absorption_cross_section\": 0}" 
   }
 ]
diff --git a/toardb/timeseries/schemas.py b/toardb/timeseries/schemas.py
index 2b7819969c5866c89b3cd3c640d001ed3f02d968..c657ec4d16aacf5d29c742c9f5c73fcbebc70546 100644
--- a/toardb/timeseries/schemas.py
+++ b/toardb/timeseries/schemas.py
@@ -121,9 +121,9 @@ class TimeseriesCore(TimeseriesCoreBase):
 
 class TimeseriesRoleBase(BaseModel):
     id: int = None
-    role: str = Field(..., description="Role of contact (see controlled vocabulary: Role Code)")
-    status: str = Field(..., description="Status of contact (see controlled vocabulary: Role Status)")
-    contact: Contact = Field(..., description="Contact for this role")
+    role: str = Field(None, description="Role of contact (see controlled vocabulary: Role Code)")
+    status: str = Field(None, description="Status of contact (see controlled vocabulary: Role Status)")
+    contact: Contact = Field(None, description="Contact for this role")
 
     @validator('role')
     def check_role(cls, v):
@@ -317,6 +317,8 @@ class TimeseriesPatch(TimeseriesCoreCreate):
     data_end_date: dt.datetime = None
     sampling_height: float = None
 #   roles: List[TimeseriesRole] = None
+    # just to get things working
+    roles: list = None
 #   annotations: List[TimeseriesAnnotation] = None
 #   variable: Variable = None
 #   station: StationmetaCoreBase = None
@@ -335,8 +337,8 @@ class Timeseries(TimeseriesBase):
     # hot fix
     coordinates: Coordinates = None
     name: str = None
-    variable_id: int = None
-    station_id: int = None
+#   variable_id: int = None
+#   station_id: int = None
     codes: List[str] = None
     station_country: str = None
     type: str = None
diff --git a/toardb/timeseries/test_search.py b/toardb/timeseries/test_search.py
index 6a9c32bbd0076aa14ff9fb68c18af1a594d62155..8a4ae679e79a512a384e259a79ce544555d7f4cf 100644
--- a/toardb/timeseries/test_search.py
+++ b/toardb/timeseries/test_search.py
@@ -181,7 +181,6 @@ class TestApps:
                           'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
                                        'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
                                        'chemical_formula': 'C7H8', 'id': 7},
-                          'variable_id': 7,
                           'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi',
                                       'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
                                       'coordinate_validation_status': 'not checked',
@@ -220,6 +219,5 @@ class TestApps:
                                                      'toar1_category': 'unclassified',
                                                      'wheat_production_year2000': -999.0},
                                       'changelog': []},
-                          'station_id': 2,
                           'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}}]
         assert response.json() == expected_resp
diff --git a/toardb/timeseries/test_timeseries.py b/toardb/timeseries/test_timeseries.py
index 3ee64a4a5efb06a9ae65cb429455462d4e85d2f4..0559a0575670b1577c86391239876e8a1b082e1b 100644
--- a/toardb/timeseries/test_timeseries.py
+++ b/toardb/timeseries/test_timeseries.py
@@ -181,7 +181,6 @@ class TestApps:
                           'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
                                        'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
                                        'chemical_formula': 'C7H8', 'id': 7},
-                          'variable_id': 7,
                           'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi',
                                       'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
                                       'coordinate_validation_status': 'not checked',
@@ -220,9 +219,8 @@ class TestApps:
                                                      'toar1_category': 'unclassified',
                                                      'wheat_production_year2000': -999.0},
                                       'changelog': []},
-                          'station_id': 2,
                           'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}},
-                         {'additional_metadata': {},
+                         {'additional_metadata': {'absorption_cross_section': 'Hearn 1961'},
                           'aggregation': 'mean',
                           'data_end_date': '2016-12-31T14:30:00+00:00',
                           'data_origin': 'instrument',
@@ -290,7 +288,6 @@ class TestApps:
                                       'timezone': 'Asia/Shanghai',
                                       'type': 'unknown',
                                       'type_of_area': 'unknown'},
-                          'station_id': 3,
                           'variable': {'cf_standardname': 'mole_fraction_of_ozone_in_air',
                                        'chemical_formula': 'O3',
                                        'displayname': 'Ozone',
@@ -298,7 +295,7 @@ class TestApps:
                                        'longname': 'ozone',
                                        'name': 'o3',
                                        'units': 'nmol mol-1'},
-                          'variable_id': 5}] 
+                          }] 
         assert response.json() == expected_resp
 
 
@@ -318,7 +315,6 @@ class TestApps:
                          'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
                                       'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
                                       'chemical_formula': 'C7H8', 'id': 7},
-                         'variable_id': 7,
                          'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi',
                                      'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
                                      'coordinate_validation_status': 'not checked',
@@ -357,7 +353,6 @@ class TestApps:
                                                     'toar1_category': 'unclassified',
                                                     'wheat_production_year2000': -999.0},
                                      'changelog': []},
-                         'station_id': 2,
                          'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}}
         assert response.json() == expected_resp
 
@@ -526,11 +521,9 @@ class TestApps:
                                        'toar1_category': 'unclassified',
                                        'wheat_production_year2000': -999.0},
                         'changelog': []},
-            'station_id': 2,
             'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
                         'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
                         'chemical_formula': 'C7H8', 'id': 7},
-            'variable_id': 7,
             'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}
             }]
         assert response.json() == expected_response
diff --git a/toardb/timeseries/timeseries.py b/toardb/timeseries/timeseries.py
index c821b17816f44950719155e170e75be2dc00b2c6..5da3b33df8aa4bee83b9cc98b38d34bf0200da50 100644
--- a/toardb/timeseries/timeseries.py
+++ b/toardb/timeseries/timeseries.py
@@ -82,26 +82,6 @@ def get_timeseries_programme(name: str, db: Session = Depends(get_db)):
     db_programme = crud.get_timeseries_programme(db, name=name)
     return db_programme
 
-@router.get('/check_analysis_params/')
-def check_analysis_params(request: Request):
-    # determine allowed query parameters (first *only* from Timeseries)
-    timeseries_params = {column.name for column in inspect(Timeseries).c} | {"has_role"}
-    gis_params = {"bounding_box", "altitude_range"}
-    core_params = {column.name for column in inspect(StationmetaCore).c}
-    global_params = {column.name for column in inspect(StationmetaGlobal).c if column.name not in ['id','station_id']}
-    data_params = {column.name for column in inspect(Data).c}
-    allowed_params = {"limit", "offset"} | gis_params | core_params | global_params | timeseries_params | data_params
-
-    status_code=200
-    message = "unknown argument(s):"
-    for param in request.query_params:
-        if param not in allowed_params: #inform user, that an unknown parameter name was used (this could be a typo and falsify the result!)
-            message = message + f" {param},"
-            status_code=422
-    message = message[:-1] + '.'
-    if status_code == 200:
-        message = ''
-    return JSONResponse(status_code=status_code, content=message)
 
 #some more gets to be tested:
 #
diff --git a/toardb/toardb.py b/toardb/toardb.py
index 60bbb234d14962dc1b3f2e5f03d89396d16b656d..dc36e4febad696bf9c50d9746abc593a19b5bce9 100644
--- a/toardb/toardb.py
+++ b/toardb/toardb.py
@@ -58,14 +58,14 @@ CT_vocabulary = 0
 controlled_fields = 0
 
 app = FastAPI()
-app.mount("/static", StaticFiles(directory="static_fastapi"), name="static_fastapi")
+app.mount("/static", StaticFiles(directory="static/_static"), name="_static")
 templates = Jinja2Templates(directory="templates")
 
 @app.middleware("http")
 async def response_to_csv(request: Request, call_next):
     response = await call_next(request)
     if ((response.status_code != 200) or
-        (request['path'].startswith(('/data/timeseries/','/ontology/','/controlled_vocabulary/'))) or
+        (request['path'].startswith(('/data/timeseries/','/data/timeseries_with_staging/','/ontology/','/controlled_vocabulary/'))) or
         (request.query_params.get('format') != 'csv')):
         return response
     # from: https://stackoverflow.com/a/71883126
diff --git a/toardb/utils/utils.py b/toardb/utils/utils.py
index 921728eabc70824a03757711c1483fe510556997..3678f44fba3c4bcc22cb03c193151e52e5079b7c 100644
--- a/toardb/utils/utils.py
+++ b/toardb/utils/utils.py
@@ -16,10 +16,11 @@ import requests
 import datetime as dt
 
 from toardb.utils.settings import base_geodata_url
-from toardb.contacts.models import Contact, Organisation
+from toardb.contacts.models import Contact, Organisation, Person
 from toardb.timeseries.models import Timeseries, TimeseriesRole, timeseries_timeseries_roles_table
 from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal
 from toardb.data.models import Data
+from toardb.variables.models import Variable
 import toardb
 
 # function to return code for given value
@@ -85,10 +86,15 @@ def create_filter(query_params, endpoint):
 
     # determine allowed query parameters (first *only* from Timeseries)
     timeseries_params = {column.name for column in inspect(Timeseries).c} | {"has_role"}
+    roles_params = {column.name for column in inspect(TimeseriesRole).c}
     gis_params = {"bounding_box", "altitude_range"}
     core_params = {column.name for column in inspect(StationmetaCore).c} 
     global_params = {column.name for column in inspect(StationmetaGlobal).c if column.name not in ['id','station_id']}
     data_params = {column.name for column in inspect(Data).c} | {"daterange", "format"}
+    ambig_params = {"station_id", "station_changelog", "station_country"}
+    variable_params = {column.name for column in inspect(Variable).c}
+    person_params = {column.name for column in inspect(Person).c}
+    allrel_params = {"limit", "offset", "fields", "format"}
 
     # pagination
     offset= int(query_params.get("offset", 0))
@@ -107,61 +113,41 @@ def create_filter(query_params, endpoint):
         raise KeyError(f"An unknown argument was received: fields.")
     format = query_params.get("format", 'json')
 
-    allowed_params = {"limit", "offset", "fields", "format"}
+    allowed_params = allrel_params.copy()
     if endpoint in {'stationmeta'}:
         allowed_params |= gis_params | core_params | global_params
     elif endpoint in {'timeseries'}:
-        allowed_params |= timeseries_params
+        allowed_params |= timeseries_params | roles_params
     elif endpoint in {'search'}:
-        allowed_params |= gis_params | core_params | global_params | timeseries_params
+        allowed_params |= gis_params | core_params | global_params | timeseries_params | roles_params | ambig_params
     elif endpoint in {'data'}:
         allowed_params = {"limit", "offset" } | data_params
-    elif endpoint in {'analysis'}:
-        allowed_params |= gis_params | core_params | global_params | timeseries_params | data_params
+    elif endpoint in {'variables'}:
+        allowed_params |= variable_params
+    elif endpoint in {'persons'}:
+        allowed_params |= person_params
     else:
         raise ValueError(f"Wrong endpoint given: {endpoint}")
+    if fields:
+        for field in fields.split(','):
+            if field not in allowed_params:
+                raise ValueError(f"Wrong field given: {field}")
 
     t_filter = []
     t_r_filter = []
     s_c_filter = []
     s_g_filter = []
     d_filter = []
+    v_filter = []
+    p_filter = []
     # query_params is a multi-dict!
     for param in query_params:
         if param not in allowed_params: #inform user, that an unknown parameter name was used (this could be a typo and falsify the result!)
             raise KeyError(f"An unknown argument was received: {param}.")
-        if param in {"limit", "offset"}:
+        if param in allrel_params:
             continue
         values = [item.strip() for v in query_params.getlist(param) for item in v.split(',')]
-        if param in timeseries_params:
-            #check for parameters of the controlled vocabulary
-            if param == "sampling_frequency":
-                values = translate_convoc_list(values, toardb.toardb.SF_vocabulary, "sampling_frequency")
-            elif param == "aggregation":
-                values = translate_convoc_list(values, toardb.toardb.AT_vocabulary, "aggregation")
-            elif param == "data_origin_type":
-                values = translate_convoc_list(values, toardb.toardb.OT_vocabulary, "data origin type")
-            elif param == "data_origin":
-                values = translate_convoc_list(values, toardb.toardb.DO_vocabulary, "data origin")
-            if param == "has_role":
-                operator = "IN"
-                join_operator = "OR"
-                if (values[0][0] == '~'):
-                    operator = "NOT IN"
-                    join_operator = "AND"
-                    values[0] = values[0][1:]
-                t_r_filter.append(f"organisations.longname {operator} {values}")
-                t_r_filter.append(f"organisations.name {operator} {values}")
-                t_r_filter.append(f"organisations.city {operator} {values}")
-                t_r_filter.append(f"organisations.homepage {operator} {values}")
-                t_r_filter.append(f"organisations.contact_url {operator} {values}")
-                t_r_filter.append(f"persons.email {operator} {values}")
-                t_r_filter.append(f"persons.name {operator} {values}")
-                t_r_filter.append(f"persons.orcid {operator} {values}")
-                t_r_filter = f" {join_operator} ".join(t_r_filter)
-            else:
-                t_filter.append(f"timeseries.{param} IN {values}")
-        elif param in core_params:
+        if endpoint in ["stationmeta", "timeseries", "search"] and param in core_params:
             #check for parameters of the controlled vocabulary
             if param == "timezone":
                 values = translate_convoc_list(values, toardb.toardb.TZ_vocabulary, "timezone")
@@ -184,7 +170,7 @@ def create_filter(query_params, endpoint):
                 s_c_filter.append(f"LOWER(stationmeta_core.name) LIKE '%{values[0].lower()}%'")
             else:
                 s_c_filter.append(f"stationmeta_core.{param} IN {values}")
-        elif param in global_params:
+        elif endpoint in ["stationmeta", "search"] and param in global_params:
             if param == "climatic_zone_year2016":
                 values = translate_convoc_list(values, toardb.toardb.CZ_vocabulary, "climatic zone year2016")
             elif param == "toar1_category":
@@ -196,13 +182,41 @@ def create_filter(query_params, endpoint):
             elif param == "dominant_ecoregion_year2017":
                 values = translate_convoc_list(values, toardb.toardb.ER_vocabulary, "ECO region type")
             s_g_filter.append(f"stationmeta_global.{param} IN {values}")
-        elif param in gis_params:
+        elif endpoint in ["stationmeta", "search"] and param in gis_params:
             if param == "bounding_box":
                 min_lat, min_lon, max_lat, max_lon = values
                 bbox= f'SRID=4326;POLYGON (({min_lon} {min_lat}, {min_lon} {max_lat}, {max_lon} {max_lat}, {max_lon} {min_lat}, {min_lon} {min_lat}))'
                 s_c_filter.append(f"ST_CONTAINS(ST_GeomFromEWKT('{bbox}'), coordinates)")
             else:
                 s_c_filter.append(f"ST_Z(coordinates) BETWEEN {values[0]} AND {values[1]}")
+        elif endpoint in ["timeseries", "search"]:
+            #check for parameters of the controlled vocabulary
+            if param == "sampling_frequency":
+                values = translate_convoc_list(values, toardb.toardb.SF_vocabulary, "sampling_frequency")
+            elif param == "aggregation":
+                values = translate_convoc_list(values, toardb.toardb.AT_vocabulary, "aggregation")
+            elif param == "data_origin_type":
+                values = translate_convoc_list(values, toardb.toardb.OT_vocabulary, "data origin type")
+            elif param == "data_origin":
+                values = translate_convoc_list(values, toardb.toardb.DO_vocabulary, "data origin")
+            if param == "has_role":
+                operator = "IN"
+                join_operator = "OR"
+                if (values[0][0] == '~'):
+                    operator = "NOT IN"
+                    join_operator = "AND"
+                    values[0] = values[0][1:]
+                t_r_filter.append(f"organisations.longname {operator} {values}")
+                t_r_filter.append(f"organisations.name {operator} {values}")
+                t_r_filter.append(f"organisations.city {operator} {values}")
+                t_r_filter.append(f"organisations.homepage {operator} {values}")
+                t_r_filter.append(f"organisations.contact_url {operator} {values}")
+                t_r_filter.append(f"persons.email {operator} {values}")
+                t_r_filter.append(f"persons.name {operator} {values}")
+                t_r_filter.append(f"persons.orcid {operator} {values}")
+                t_r_filter = f" {join_operator} ".join(t_r_filter)
+            else:
+                t_filter.append(f"timeseries.{param} IN {values}")
         elif param in data_params:
             if param == "daterange":
                 start_date = dt.datetime.fromisoformat(values[0])
@@ -213,6 +227,10 @@ def create_filter(query_params, endpoint):
                 continue
             else:
                 d_filter.append(f"data.{param} IN {values}")
+        elif endpoint == "variables":
+            v_filter.append(f"variables.{param} IN {values}")
+        elif param in person_params:
+            p_filter.append(f"persons.{param} IN {values}")
 
 
     t_filter = " AND ".join(t_filter).replace('[','(').replace(']',')')
@@ -222,7 +240,17 @@ def create_filter(query_params, endpoint):
     s_c_filter = " AND ".join(s_c_filter).replace('[','(').replace(']',')')
     s_g_filter = " AND ".join(s_g_filter).replace('[','(').replace(']',')')
     d_filter = " AND ".join(d_filter).replace('[','(').replace(']',')')
-    return limit, offset, fields, format, t_filter, t_r_filter, s_c_filter, s_g_filter, d_filter
+    v_filter = " AND ".join(v_filter).replace('[','(').replace(']',')')
+    p_filter = " AND ".join(p_filter).replace('[','(').replace(']',')')
+    filters = {}
+    filters["t_filter"] = t_filter
+    filters["t_r_filter"] = t_r_filter
+    filters["s_c_filter"] = s_c_filter
+    filters["s_g_filter"] = s_g_filter
+    filters["d_filter"] = d_filter
+    filters["v_filter"] = v_filter
+    filters["p_filter"] = p_filter
+    return limit, offset, fields, format, filters
 
 
 ###
diff --git a/toardb/variables/crud.py b/toardb/variables/crud.py
index 4d90790543e47be149ec8f2a698aece2ea3cc964..dfcc59f32bfb6e55bc2e4772d79cdbbd81aae735 100644
--- a/toardb/variables/crud.py
+++ b/toardb/variables/crud.py
@@ -6,8 +6,11 @@ Create, Read, Update, Delete functionality
 
 """
 
+from fastapi.responses import JSONResponse
+from sqlalchemy import text
 from sqlalchemy.orm import Session
 from . import models, schemas
+from toardb.utils.utils import create_filter
 
 def get_variable(db: Session, variable_id: int):
     return db.query(models.Variable).filter(models.Variable.id == variable_id).first()
@@ -17,10 +20,28 @@ def get_variable_by_name(db: Session, name: str):
     return db.query(models.Variable).filter(models.Variable.name == name.lower()).first()
 
 
-def get_variables(db: Session, limit: int, offset: int = 0):
-    if limit == "None":
-        limit = None
-    return db.query(models.Variable).order_by(models.Variable.id).offset(offset).limit(limit).all()
+def get_variables(db: Session, path_params, query_params):
+    try:
+        limit, offset, fields, format, filters = create_filter(query_params, "variables")
+        v_filter = filters["v_filter"]
+    except (KeyError, ValueError) as e:
+        status_code=400
+        return JSONResponse(status_code=status_code, content=str(e))
+
+    if fields:
+        fields2 = fields.split(',')
+        db_objects_l = db.query(*map(text,fields2)).select_from(models.Variable). \
+                           filter(text(v_filter)). \
+                           order_by(models.Variable.id). \
+                           limit(limit).offset(offset)
+        db_objects = []
+        for db_object_immut in db_objects_l:
+            db_object = dict(zip(fields.split(','),db_object_immut))
+            db_objects.append(db_object)
+    else:
+        db_objects = db.query(models.Variable).filter(text(v_filter)). \
+                        order_by(models.Variable.id).offset(offset).limit(limit).all()
+    return db_objects
 
 
 def create_variable(db: Session, variable: schemas.VariableCreate):
diff --git a/toardb/variables/schemas.py b/toardb/variables/schemas.py
index e5a415a069c7b650e6b13368326540b2280ba095..6909cd5fbd346eec464ec6e87b16824bc98b5bd8 100644
--- a/toardb/variables/schemas.py
+++ b/toardb/variables/schemas.py
@@ -12,18 +12,18 @@ from pydantic import BaseModel, Field
 
 
 class VariableBase(BaseModel):
-    name: str = Field(..., description="Name. Short variable-like name of the variable")
-    longname: str = Field(..., description="Longname. Long (explicit) name of the variable")
-    displayname: str = Field(..., description="Displayname. Display name of the variable")
-    cf_standardname: str = Field(..., description="Cf standardname. CF standard name of the variable if defined")
-    units: str = Field(..., description="Units. Physical units of variable")
-    chemical_formula: str = Field(..., description="Chemical formula. Chemical formula of variable(if applicable)")
+    name: str = Field(None, description="Name. Short variable-like name of the variable")
+    longname: str = Field(None, description="Longname. Long (explicit) name of the variable")
+    displayname: str = Field(None, description="Displayname. Display name of the variable")
+    cf_standardname: str = Field(None, description="Cf standardname. CF standard name of the variable if defined")
+    units: str = Field(None, description="Units. Physical units of variable")
+    chemical_formula: str = Field(None, description="Chemical formula. Chemical formula of variable(if applicable)")
 
 class VariableCreate(VariableBase):
     pass
 
 class Variable(VariableBase):
-    id: int
+    id: int = None
 
     class Config:
         orm_mode = True
diff --git a/toardb/variables/test_variables.py b/toardb/variables/test_variables.py
index df277db850ab65b037eb1a89a2c6f3fd88a4892d..dae70ecd28d8ad326d848c9bf85c38870bee8d70 100644
--- a/toardb/variables/test_variables.py
+++ b/toardb/variables/test_variables.py
@@ -57,8 +57,8 @@ class TestApps:
         assert response.json() == expected_resp
 
 
-    def test_get_all_variables_nolimit(self, client, db):
-        response = client.get("/variables/?limit=100")
+    def test_get_all_variables_default_limit(self, client, db):
+        response = client.get("/variables/")
         expected_status_code = 200
         assert response.status_code == expected_status_code
         expected_resp = [{'name': 'benzene', 'longname': 'benzene', 'displayname': 'Benzene',
@@ -86,8 +86,8 @@ class TestApps:
                           'cf_standardname': 'mole_fraction_of_propane_in_air', 'units': 'nmol mol-1', 'chemical_formula': 'C3H8', 'id': 10}]
         assert response.json() == expected_resp
 
-    def test_get_all_variables_nolimit(self, client, db):
-        response = client.get("/variables/?limit=100")
+    def test_get_all_variables_unlimited(self, client, db):
+        response = client.get("/variables/?limit=None")
         expected_status_code = 200
         assert response.status_code == expected_status_code
         expected_resp = [{'name': 'benzene', 'longname': 'benzene', 'displayname': 'Benzene',
@@ -162,6 +162,54 @@ class TestApps:
         assert response.json() == expected_resp
 
 
+    def test_get_all_variables_using_fields(self, client, db):
+        response = client.get("/variables/?limit=None&fields=name,units")
+        expected_status_code = 200
+        assert response.status_code == expected_status_code
+        expected_resp = [{'name': 'benzene', 'units': 'nmol mol-1'},
+                         {'name': 'co', 'units': 'nmol mol-1'},
+                         {'name': 'no', 'units': 'nmol mol-1'},
+                         {'name': 'pm1', 'units': 'µg m-3'},
+                         {'name': 'o3', 'units': 'nmol mol-1'},
+                         {'name': 'no2', 'units': 'nmol mol-1'},
+                         {'name': 'toluene', 'units': 'nmol mol-1'},
+                         {'name': 'so2', 'units': 'nmol mol-1'},
+                         {'name': 'ethane', 'units': 'nmol mol-1'},
+                         {'name': 'propane', 'units': 'nmol mol-1'},
+                         {'name': 'ox', 'units': 'nmol mol-1'},
+                         {'name': 'aswdir', 'units': 'W/m**2'},
+                         {'name': 'pm10', 'units': 'µg m-3'},
+                         {'name': 'rn', 'units': 'mBq m-3'},
+                         {'name': 'mpxylene', 'units': 'nmol mol-1'},
+                         {'name': 'oxylene', 'units': 'nmol mol-1'},
+                         {'name': 'ch4', 'units': 'nmol mol-1'},
+                         {'name': 'wdir', 'units': 'degrees'},
+                         {'name': 'pm2p5', 'units': 'µg m-3'},
+                         {'name': 'nox', 'units': 'nmol mol-1'},
+                         {'name': 'temp', 'units': 'degC'},
+                         {'name': 'wspeed', 'units': 'm s-1'},
+                         {'name': 'press', 'units': 'hPa'},
+                         {'name': 'cloudcover', 'units': '%'},
+                         {'name': 'pblheight', 'units': 'm'},
+                         {'name': 'relhum', 'units': '%'},
+                         {'name': 'totprecip', 'units': 'kg m-2'},
+                         {'name': 'u', 'units': 'm s-1'},
+                         {'name': 'v', 'units': 'm s-1'},
+                         {'name': 'albedo', 'units': '%'},
+                         {'name': 'aswdifu', 'units': 'W/m**2'},
+                         {'name': 'humidity', 'units': 'g kg-1'},
+                         {'name': 'irradiance', 'units': 'W m-2'}]
+        assert response.json() == expected_resp
+
+
+    def test_get_all_variables_wrong_fieldname(self, client, db):
+        response = client.get("/variables/?limit=None&fields=name,units,bla")
+        expected_status_code = 400
+        assert response.status_code == expected_status_code
+        expected_resp = "Wrong field given: bla"
+        assert response.json() == expected_resp
+
+
     def test_get_special(self, client, db):
         response = client.get("/variables/id/2")
         expected_status_code = 200
@@ -190,6 +238,43 @@ class TestApps:
         assert response.json() == expected_resp
 
 
+    def test_get_special_by_units(self, client, db):
+        response = client.get("/variables/?units=nmol mol-1")
+        expected_status_code = 200
+        assert response.status_code == expected_status_code
+        expected_resp = [{'name': 'benzene', 'longname': 'benzene', 'displayname': 'Benzene',
+                          'cf_standardname': 'mole_fraction_of_benzene_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'C6H6', 'id': 1},
+                         {'name': 'co', 'longname': 'carbon monoxide', 'displayname': 'CO',
+                          'cf_standardname': 'mole_fraction_of_carbon_monoxide_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'CO', 'id': 2},
+                         {'name': 'no', 'longname': 'nitrogen monoxide', 'displayname': 'NO',
+                          'cf_standardname': 'mole_fraction_of_nitrogen_monoxide_in_air',
+                          'units': 'nmol mol-1', 'chemical_formula': 'NO', 'id': 3},
+                         {'name': 'o3', 'longname': 'ozone', 'displayname': 'Ozone',
+                          'cf_standardname': 'mole_fraction_of_ozone_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'O3', 'id': 5},
+                         {'name': 'no2', 'longname': 'nitrogen dioxide', 'displayname': 'NO2',
+                          'cf_standardname': 'mole_fraction_of_nitrogen_dioxide_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'NO2', 'id': 6},
+                         {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
+                          'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'C7H8', 'id': 7},
+                         {'name': 'so2', 'longname': 'Sulphur dioxide', 'displayname': 'SO2',
+                          'cf_standardname': 'mole_fraction_of_sulfur_dioxide_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'SO2', 'id': 8},
+                         {'name': 'ethane', 'longname': 'Ethane', 'displayname': 'Ethane',
+                          'cf_standardname': 'mole_fraction_of_ethane_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'C2H6', 'id': 9},
+                         {'name': 'propane', 'longname': 'Propane', 'displayname': 'Propane',
+                          'cf_standardname': 'mole_fraction_of_propane_in_air', 'units': 'nmol mol-1',
+                          'chemical_formula': 'C3H8', 'id': 10},
+                         {'name': 'ox', 'longname': 'Ox', 'displayname': 'Ox',
+                          'cf_standardname': '', 'units': 'nmol mol-1',
+                          'chemical_formula': '', 'id': 11}]
+        assert response.json() == expected_resp
+
+
     def test_get_special_by_name_not_existing(self, client, db):
         response = client.get("/variables/sabinen")
         expected_status_code = 404
diff --git a/toardb/variables/variables.py b/toardb/variables/variables.py
index b97a4b72ad06b8b6e7c3dfacaba02adcd351e3c7..2d387774a19eb53d5e2c85c579535dea71cc70a7 100644
--- a/toardb/variables/variables.py
+++ b/toardb/variables/variables.py
@@ -6,7 +6,7 @@ Simple test API for variable management
 """
 
 from typing import List
-from fastapi import APIRouter, Depends, HTTPException
+from fastapi import APIRouter, Depends, HTTPException, Request
 from sqlalchemy.orm import Session
 from toardb.variables import crud, schemas
 from toardb.utils.database import ToarDbSession, get_db
@@ -21,9 +21,9 @@ def create_variable(variable: schemas.VariableCreate, db: Session = Depends(get_
         raise HTTPException(status_code=400, detail="Variable already registered.")
     return crud.create_variable(db=db, variable=variable)
 
-@router.get('/variables/', response_model=List[schemas.Variable])
-def get_variables(offset: int = 0, limit: int|str = 10, db: Session = Depends(get_db)):
-    variables = crud.get_variables(db, offset=offset, limit=limit)
+@router.get('/variables/', response_model=List[schemas.Variable], response_model_exclude_none=True, response_model_exclude_unset=True)
+def get_variables(request: Request, db: Session = Depends(get_db)):
+    variables = crud.get_variables(db, path_params=request.path_params, query_params=request.query_params)
     return variables
 
 @router.get('/variables/{name}', response_model=schemas.Variable)