diff --git a/tests/fixtures/timeseries/timeseries.json b/tests/fixtures/timeseries/timeseries.json
index fa3d1644fda83cecb8734eb7ebf0a113fd88660d..bd7c59b1699ffe7f33baa9d0e139714d5b77412a 100644
--- a/tests/fixtures/timeseries/timeseries.json
+++ b/tests/fixtures/timeseries/timeseries.json
@@ -11,7 +11,8 @@
     "data_origin": 0,
     "data_origin_type": 0,
     "sampling_height": 7,
-    "additional_metadata": {}
+    "additional_metadata": {},
+    "programme_id": 0
   },
   {
     "station_id": 3,
@@ -32,6 +33,7 @@
 				    "Data level": "2",
 				    "Frameworks": "GAW-WDCRG NOAA-ESRL",
 				    "Station code": "XXX",
-				    "Station name": "Secret" } }
+				    "Station name": "Secret" } },
+    "programme_id": 0
   }
 ]
diff --git a/tests/fixtures/toardb_pytest.psql b/tests/fixtures/toardb_pytest.psql
index 372960d688a8bb1177149bff228bc55cddf13b05..e7da2950203a3e9dfc8ae2855b213763889808ed 100644
--- a/tests/fixtures/toardb_pytest.psql
+++ b/tests/fixtures/toardb_pytest.psql
@@ -25,6 +25,16 @@ CREATE SCHEMA IF NOT EXISTS toar_convoc;
 
 ALTER SCHEMA toar_convoc OWNER TO postgres;
 
+--
+-- Name: services; Type: SCHEMA; Schema: -; Owner: postgres
+--
+
+CREATE SCHEMA IF NOT EXISTS services;
+
+
+ALTER SCHEMA services OWNER TO postgres;
+
+
 --
 -- Name: address_standardizer; Type: EXTENSION; Schema: -; Owner: -
 --
@@ -102,6 +112,18 @@ CREATE EXTENSION IF NOT EXISTS postgis_topology WITH SCHEMA topology;
 COMMENT ON EXTENSION postgis_topology IS 'PostGIS topology spatial types and functions';
 
 
+-- 
+-- List of contributors for request of special services
+-- here: s1: analysis-service
+--
+
+CREATE TABLE IF NOT EXISTS services.s1_contributors (
+    request_id character varying(36) NOT NULL,
+    timeseries_ids bigint[],
+    PRIMARY KEY(request_id)
+);
+
+
 --
 -- Name: toar_controlled_vocabulary; Type: EXTENSION -- faked; Schema: -; Owner: -
 --
@@ -4064,5 +4086,5 @@ ALTER TABLE ONLY public.timeseries
     ADD CONSTRAINT timeseries_variable_id_dd9603f5_fk_variables_id FOREIGN KEY (variable_id) REFERENCES public.variables(id) DEFERRABLE INITIALLY DEFERRED;
 
 ALTER DATABASE postgres
-    SET search_path=public,tiger,toar_convoc;
+    SET search_path=public,tiger,toar_convoc,services;
 
diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py
index 72701316e1e2e2dd1909eb750c715ed3134747b1..b27d5fceb2a74c12a695b1512a4f5312d165439d 100644
--- a/tests/test_timeseries.py
+++ b/tests/test_timeseries.py
@@ -4,7 +4,8 @@
 import pytest
 import json
 from sqlalchemy import insert
-from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table
+from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table, \
+                                     s1_contributors_table
 from toardb.timeseries.models_programme import TimeseriesProgramme
 from toardb.timeseries.models_role import TimeseriesRole
 from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal
@@ -165,6 +166,12 @@ class TestApps:
             for entry in metajson:
                 db.execute(insert(timeseries_timeseries_roles_table).values(timeseries_id=entry["timeseries_id"], role_id=entry["role_id"]))
                 db.execute("COMMIT")
+        infilename = "tests/fixtures/timeseries/timeseries_contributors.json"
+        with open(infilename) as f:
+            metajson=json.load(f)
+            for entry in metajson:
+                db.execute(insert(s1_contributors_table).values(request_id=entry["request_id"], timeseries_ids=entry["timeseries_ids"]))
+                db.execute("COMMIT")
 
 
     def test_get_timeseries(self, client, db):
@@ -1011,8 +1018,82 @@ class TestApps:
         expected_status_code = 400
         assert response.status_code == expected_status_code
         expected_response = 'not a valid format: CMOR'
+        assert response.json() == expected_response 
+
+
+    def test_register_contributors_list(self, client, db):
+        response = client.post("/timeseries/register_timeseries_list_of_contributors/5f0df73a-bd0f-48b9-bb17-d5cd36f89598",
+                               data='''[1,2]''',
+                               headers={"email": "s.schroeder@fz-juelich.de"} )
+        expected_status_code = 200
+        assert response.status_code == expected_status_code
+        expected_response = '5f0df73a-bd0f-48b9-bb17-d5cd36f89598 successfully registered.'
         assert response.json() == expected_response
 
+
+    def test_register_duplicate_contributors_list(self, client, db):
+        response = client.post("/timeseries/register_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598",
+                               data='''[1,2]''',
+                               headers={"email": "s.schroeder@fz-juelich.de"} )
+        expected_status_code = 443
+        assert response.status_code == expected_status_code
+        expected_response = '7f0df73a-bd0f-48b9-bb17-d5cd36f89598 already registered.'
+        assert response.json() == expected_response
+
+
+    def test_request_registered_contributors_list_json(self, client, db):
+        response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=json")
+        expected_status_code = 200
+        assert response.status_code == expected_status_code
+        expected_response = [
+                             {'contact': {'id': 5,
+                                          'organisation': {'city': 'Jülich',
+                                                           'contact_url': 'mailto:toar-data@fz-juelich.de',
+                                                           'country': 'Germany',
+                                                           'homepage': 'https://www.fz-juelich.de',
+                                                           'id': 2,
+                                                           'kind': 'research',
+                                                           'longname': 'Forschungszentrum Jülich',
+                                                           'name': 'FZJ',
+                                                           'postcode': '52425',
+                                                           'street_address': 'Wilhelm-Johnen-Straße'
+                                                          },
+                                          'person': None
+                                         },
+                              'id': 1,
+                              'role': 'resource provider',
+                              'status': 'active'
+                             },
+                             {'contact': {'id': 4,
+                                          'organisation': {'city': 'Dessau-Roßlau',
+                                                           'contact_url': 'mailto:immission@uba.de',
+                                                           'country': 'Germany',
+                                                           'homepage': 'https://www.umweltbundesamt.de',
+                                                           'id': 1,
+                                                           'kind': 'government',
+                                                           'longname': 'Umweltbundesamt',
+                                                           'name': 'UBA',
+                                                           'postcode': '06844',
+                                                           'street_address': 'Wörlitzer Platz 1'
+                                                          },
+                                          'person': None
+                                         },
+                              'id': 2,
+                              'role': 'resource provider',
+                              'status': 'active'
+                             }
+                            ]
+        assert response.json() == expected_response
+
+
+    def test_request_registered_contributors_list_text(self, client, db):
+        response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=text")
+        expected_status_code = 200
+        assert response.status_code == expected_status_code
+        expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt'
+        assert response.json() == expected_response
+
+
     # 3. tests updating timeseries metadata
 
     def test_patch_timeseries_no_description(self, client, db):
diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py
index e8cda0b04866e69a95db1761b87bff95c26f85f8..a2e7332ecf3ac7e97834c173b6b4647fb497c414 100644
--- a/toardb/timeseries/crud.py
+++ b/toardb/timeseries/crud.py
@@ -9,6 +9,7 @@ Create, Read, Update, Delete functionality
 from sqlalchemy import insert, select, and_, func, text
 from sqlalchemy.orm import Session, load_only
 from sqlalchemy.util import _collections
+from sqlalchemy.exc import IntegrityError
 from geoalchemy2.elements import WKBElement, WKTElement
 from fastapi import File, UploadFile
 from fastapi.responses import JSONResponse
@@ -16,7 +17,7 @@ import datetime as dt
 import json
 from . import models
 from .models import TimeseriesChangelog, timeseries_timeseries_roles_table, \
-                    timeseries_timeseries_annotations_table
+                    timeseries_timeseries_annotations_table, s1_contributors_table
 from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal
 from toardb.stationmeta.schemas import get_coordinates_from_geom, get_geom_from_coordinates, get_coordinates_from_string
 from toardb.stationmeta.crud import get_stationmeta_by_id, get_stationmeta_core, station_id_exists, get_stationmeta_changelog
@@ -586,9 +587,7 @@ def get_contributors_string(programmes, roles):
     return result
 
 
-def get_request_contributors(db: Session, format: str = 'text', input_handle: UploadFile = File(...)):
-    f = input_handle.file
-    timeseries_ids = [int(line.strip()) for line in f.readlines()]
+def get_contributors_list(db: Session, timeseries_ids, format: str = 'text'):
     # get time series' programmes
     # join(models.Timeseries, Timeseries.programme_id == TimeseriesProgramme.id) is implicit given due to the foreign key
     programmes = db.query(models.TimeseriesProgramme) \
@@ -612,6 +611,36 @@ def get_request_contributors(db: Session, format: str = 'text', input_handle: Up
     return result
 
 
+def get_request_contributors(db: Session, format: str = 'text', input_handle: UploadFile = File(...)):
+    f = input_handle.file
+    timeseries_ids = [int(line.strip()) for line in f.readlines()]
+    return get_contributors_list(db, timeseries_ids, format)
+
+
+def get_registered_request_contributors(db: Session, rid, format: str = 'text'):
+    timeseries_ids = db.execute(select([s1_contributors_table]).\
+                                where(s1_contributors_table.c.request_id == rid)).mappings().first()['timeseries_ids']
+    return get_contributors_list(db, timeseries_ids, format)
+
+
+def register_request_contributors(db: Session, rid, ids):
+    try:
+        db.execute(insert(s1_contributors_table).values(request_id=rid, timeseries_ids=ids))
+        db.commit()
+        status_code = 200
+        message=f'{rid} successfully registered.'
+    except IntegrityError as e:
+        error_code = e.orig.pgcode
+        if error_code == "23505":
+            status_code = 443
+            message=f'{rid} already registered.'
+        else:
+            status_code = 442
+            message=f'database error: error_code'
+    result = JSONResponse(status_code=status_code, content=message)
+    return result
+
+
 # is this internal, or should this also go to public REST api?
 def get_timeseries_roles(db: Session, timeseries_id: int):
     return db.execute(select([timeseries_timeseries_roles_table]).where(timeseries_timeseries_roles_table.c.timeseries_id == timeseries_id))
diff --git a/toardb/timeseries/models.py b/toardb/timeseries/models.py
index f697fb36f5cdbdf98bf706d56bb027353e985fe7..121bcc7b953392e875d8240e46e32bcf373e5c33 100644
--- a/toardb/timeseries/models.py
+++ b/toardb/timeseries/models.py
@@ -6,6 +6,7 @@ from .models_role import TimeseriesRole, timeseries_timeseries_roles_table
 from .models_annotation import TimeseriesAnnotation, timeseries_timeseries_annotations_table
 from .models_programme import TimeseriesProgramme
 from .models_changelog import TimeseriesChangelog
+from .models_contributor import s1_contributors_table
 from toardb.base import Base
 
 from sqlalchemy import Table, Column, Integer, String
diff --git a/toardb/timeseries/timeseries.py b/toardb/timeseries/timeseries.py
index d5b5e210c48a8a7b84ffc096c7a75e12cd2fb0ba..1a40821059b568e1734c3082cbc36497882e79c8 100644
--- a/toardb/timeseries/timeseries.py
+++ b/toardb/timeseries/timeseries.py
@@ -24,7 +24,8 @@ from toardb.data.models import Data
 from toardb.utils.utils import (
         get_str_from_value,
         get_admin_access_rights,
-        get_timeseries_md_change_access_rights
+        get_timeseries_md_change_access_rights,
+        get_register_contributors_access_rights
 )
 
 
@@ -93,17 +94,33 @@ def get_timeseries(station_id: int, variable_id: int, resource_provider: str = N
         raise HTTPException(status_code=404, detail="Timeseries not found.")
     return db_timeseries
 
+
 @router.get('/timeseries_changelog/{timeseries_id}', response_model=List[schemas.TimeseriesChangelog])
 def get_timeseries_changelog(timeseries_id: int, db: Session = Depends(get_db)):
     db_changelog = crud.get_timeseries_changelog(db, timeseries_id=timeseries_id)
     return db_changelog
 
+
 @router.get('/timeseries_programme/{name}', response_model=List[schemas.TimeseriesProgramme])
 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('/timeseries/request_timeseries_list_of_contributors/{rid}', response_model=Union[str,List[schemas.Contributors]])
+def get_registered_request_contributors(rid: str, format: str = 'text', db: Session = Depends(get_db)):
+    contributors = crud.get_registered_request_contributors(db, rid=rid, format=format)
+    return contributors
+
+
+@router.post('/timeseries/register_timeseries_list_of_contributors/{rid}')
+def register_request_contributors(rid: str,
+                                  ids: List[int],
+                                  access: dict = Depends(get_register_contributors_access_rights),
+                                  db: Session = Depends(get_db)):
+    return crud.register_request_contributors(db, rid=rid, ids=ids)
+
+
 @router.post('/timeseries/request_contributors', response_model=Union[str,List[schemas.Contributors]])
 def get_request_contributors(format: str = 'text', file: UploadFile = File(...), db: Session = Depends(get_db)):
     contributors = crud.get_request_contributors(db, format=format, input_handle=file)
diff --git a/toardb/utils/utils.py b/toardb/utils/utils.py
index 5bdc4578b7a2ea8dd6af676a2f919477fdee08de..e84a4b2ee89aab6c8a64eb98fa12e980f318ce2f 100644
--- a/toardb/utils/utils.py
+++ b/toardb/utils/utils.py
@@ -16,6 +16,8 @@ import requests
 import datetime as dt
 
 from toardb.utils.settings import base_geopeas_url, userinfo_endpoint
+
+# the following statement only if not in testing (pytest) mode!
 from toardb.utils.database import get_db
 from toardb.contacts.models import Contact, Organisation, Person
 from toardb.timeseries.models import Timeseries, TimeseriesRole, timeseries_timeseries_roles_table
@@ -38,7 +40,7 @@ def get_access_rights(request: Request, access_right: str = 'admin'):
     if status_code != 401:
         userinfo = userinfo.json()
         if "eduperson_entitlement" in userinfo and \
-           f"urn:geant:helmholtz.de:res:toar-data:{access_right}#login-dev.helmholtz.de" \
+           f"urn:geant:helmholtz.de:res:toar-data{access_right}#login.helmholtz.de" \
            in userinfo["eduperson_entitlement"]:
             user_name = userinfo["name"]
             user_email = userinfo["email"]
@@ -59,20 +61,24 @@ def get_access_rights(request: Request, access_right: str = 'admin'):
 
 
 def get_admin_access_rights(request: Request):
-    return get_access_rights(request, 'admin')
+    return get_access_rights(request, ':admin')
 
 
 def get_station_md_change_access_rights(request: Request):
-    return get_access_rights(request, 'station-md-change')
+    return get_access_rights(request, ':station-md-change')
 
 
 
 def get_timeseries_md_change_access_rights(request: Request):
-    return get_access_rights(request, 'timeseries-md-change')
+    return get_access_rights(request, ':timeseries-md-change')
 
 
 def get_data_change_access_rights(request: Request):
-    return get_access_rights(request, 'data-change')
+    return get_access_rights(request, ':data-change')
+
+
+def get_register_contributors_access_rights(request: Request):
+    return get_access_rights(request, ':contributors-register')
 
 
 # function to return code for given value