diff --git a/README.md b/README.md
index 21f8995ce38dfce975d548f06ad16249cf9967ce..d391815ef1b2660071cf9994d140e6083ebbf800 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,18 @@
 
 ## Apertif Task Database for LOFAR Data Valorization
 
-Test Environment:
+### Test Environment on sdc.astron.nl:
+
+main GUI:
   * https://sdc.astron.nl:5554/atdb/
+
+admin interface:
   * https://sdc.astron.nl:5554/atdb/admin/
 
+REST API 
+  * workflows: http://localhost:8000/atdb/workflows/
+  * tasks: http://localhost:8000/atdb/tasks/
+
 ## Micro Services (in separate repo)
   * https://git.astron.nl/astron-sdc/ldv-services
 
diff --git a/atdb/taskdatabase/models.py b/atdb/taskdatabase/models.py
index ba847b76c00e9c742921a14190a138ddde2e2224..cd389229a0f03b98ec9a2ab2136753d7350d5dd8 100644
--- a/atdb/taskdatabase/models.py
+++ b/atdb/taskdatabase/models.py
@@ -7,23 +7,32 @@ from django.db.models import Sum
 datetime_format_string = '%Y-%m-%dT%H:%M:%SZ'
 
 class Workflow(models.Model):
-    repository = models.CharField(max_length=100, blank=True, null=True, default="unknown")
+    workflow_uri = models.CharField(max_length=30, blank=True, null=True)
+    repository = models.CharField(max_length=100, blank=True, null=True)
     commit_id = models.CharField(max_length=30, blank=True, null=True)
-    path = models.CharField(max_length=100, blank=True, null=True, default="unknown")
+    path = models.CharField(max_length=100, blank=True, null=True)
 
-class LogEntry(models.Model):
-    step_name = models.CharField(max_length=30, blank=True, null=True, default="unknown")
+    def __str__(self):
+        return str(self.id)+' - '+str(self.workflow_uri)
 
 
 class Task(models.Model):
     taskID = models.CharField(db_index=True, max_length=30, blank=True, null=True)
     task_type = models.CharField(max_length=20, default="task")
 
+    # note: the apparent naming reversal is intentional. Predecessors are somebody elses successors.
+    predecessors = models.ForeignKey('self', related_name='task_successors', on_delete=models.SET_NULL, null=True,blank=True)
+    successors = models.ForeignKey('self', related_name='task_predecessors', on_delete=models.SET_NULL, null=True,blank=True)
+
+    workflow = models.ForeignKey(Workflow, related_name='tasks', on_delete=models.SET_NULL, null=True)
+
     project = models.CharField(max_length=100, blank=True, null=True, default="unknown")
     sas_id = models.CharField(max_length=30, blank=True, null=True)
     priority = models.IntegerField(default=0)
     purge_policy = models.CharField(max_length=5, default="no", blank=True, null=True)
-    workflow_id = models.CharField(max_length=12, blank=True, null=True)
+    desired_workflow_id = models.CharField(max_length=12, blank=True, null=True)
+    desired_workflow_uri = models.CharField(max_length=100, blank=True, null=True)
+    workflow = models.ForeignKey(Workflow, related_name='tasks', on_delete=models.SET_NULL, null=True,blank=True)
 
     inputs = models.JSONField(null=True, blank=True)
     outputs = models.JSONField(null=True, blank=True)
@@ -31,20 +40,33 @@ class Task(models.Model):
     skip = models.BooleanField(default=False)
     creationTime = models.DateTimeField(default=datetime.utcnow, blank=True)
 
-    new_status = models.CharField(max_length=50, default="defined", null=True)
-    my_status = models.CharField(db_index=True, max_length=50,default="defined")
-
+    desired_status = models.CharField(max_length=50, default="defining", null=True)
+    status = models.CharField(db_index=True, default="unknown", max_length=50,blank=True, null=True)
 
     def __str__(self):
-        return str(self.id)
+        return str(self.id) + ' - ' + str(self.taskID) + ' - ' + str(self.sas_id)
 
     # this translates a view-name (from urls.py) back to a url, to avoid hardcoded url's in the html templates
-    # bad : <td><a href="/atdb/observations/{{ observation.id }}/" target="_blank">{{ observation.taskID }} </a> </td>
-    # good: <td><a href="{{ observation.get_absolute_url }}" target="_blank">{{ observation.taskID }} </a> </td>
+    # bad : <td><a href="/atdb/taaks/{{ task.id }}/" target="_blank">{{ task.taskID }} </a> </td>
+    # good: <td><a href="{{ task.get_absolute_url }}" target="_blank">{{ task.taskID }} </a> </td>
     def get_absolute_url(self):
         return reverse('task-detail-view-api', kwargs={'pk': self.pk})
 
 
+class LogEntry(models.Model):
+    task = models.ForeignKey(Task, related_name='tasks', on_delete=models.CASCADE, null=False)
+    cpu_cycles = models.IntegerField(null=True,blank=True)
+    wall_clock_time = models.IntegerField(null=True,blank=True)
+    url_to_log_file = models.CharField(max_length=100, blank=True, null=True)
+    step_name = models.CharField(max_length=30, blank=True, null=True)
+    start_time = models.DateTimeField(blank=True, null=True)
+    end_time = models.DateTimeField(blank=True, null=True)
+    desired_status = models.CharField(max_length=50, default="defined", blank=True, null=True)
+    status = models.CharField(max_length=50,default="defined", blank=True, null=True)
+
+    def __str__(self):
+        return str(self.id)+' - ('+str(self.task__taskID)+')'
+
 class Status(models.Model):
     name = models.CharField(max_length=50, default="unknown")
     timestamp = models.DateTimeField(default=datetime.utcnow, blank=True)
diff --git a/atdb/taskdatabase/serializers.py b/atdb/taskdatabase/serializers.py
index cf8b2be45b053b90a81813961b3860a05eca6bf7..2ef09247763c5a407f5edf46807ac1a48d92844f 100644
--- a/atdb/taskdatabase/serializers.py
+++ b/atdb/taskdatabase/serializers.py
@@ -12,8 +12,10 @@ class TaskSerializer(serializers.ModelSerializer):
     class Meta:
         model = Task
         fields = ('id','task_type','taskID',
-                  'project','sas_id','priority','purge_policy','skip','workflow_id',
-                  'my_status','new_status',
+                  'predecessors','successors',
+                  'project','sas_id','priority','purge_policy','skip',
+                  'desired_workflow_id','desired_workflow_uri','workflow',
+                  'status','desired_status','desired_workflow_uri',
                   'inputs','outputs','status_history')
 
 
@@ -26,6 +28,11 @@ class StatusSerializer(serializers.ModelSerializer):
 
 class WorkflowSerializer(serializers.ModelSerializer):
 
+#    tasks = serializers.StringRelatedField(
+#        many=True,
+#        required=False,
+#    )
+
     class Meta:
         model = Workflow
         fields = "__all__"
diff --git a/atdb/taskdatabase/services/signals.py b/atdb/taskdatabase/services/signals.py
index 2096d2dcff65724785260968edf999147f7f1604..264e88f92b9b349ad150981da3084ef5ce7ff07b 100644
--- a/atdb/taskdatabase/services/signals.py
+++ b/atdb/taskdatabase/services/signals.py
@@ -5,7 +5,7 @@ from django.core.signals import request_started, request_finished
 from django.contrib.auth.models import User
 from django.dispatch import receiver
 from django.contrib.contenttypes.models import ContentType
-from taskdatabase.models import Task, Status
+from taskdatabase.models import Task, Workflow, LogEntry, Status
 from . import jobs
 
 """
@@ -48,17 +48,33 @@ def handle_pre_save(sender, **kwargs):
         return None
 
     # handle status change
-    my_status = str(myTaskObject.my_status)
-    new_status = str(myTaskObject.new_status)
-    if (new_status!=None) and (my_status!=new_status):
+    status = str(myTaskObject.status)
+    desired_status = str(myTaskObject.desired_status)
+
+    if (desired_status!=None) and (status!=desired_status):
 
         # set the new status
-        myTaskObject.my_status = new_status
+        myTaskObject.status = desired_status
 
         # add the new to the status history by brewing a status object out of it
-        myStatus = Status(name=new_status, task=myTaskObject)
+        myStatus = Status(name=desired_status, task=myTaskObject)
         myStatus.save()
 
+    # connect the task to a workflow after posting a (flat) task through the REST API
+    desired_workflow_id = myTaskObject.desired_workflow_id
+    desired_workflow_uri = myTaskObject.desired_workflow_uri
+
+    # first try to find the workflow by desired workflow_id
+    desired_workflow = Workflow.objects.get(id=desired_workflow_id)
+    if (desired_workflow == None):
+        # then try workflow_uri
+        desired_workflow = Workflow.objects.get(uri=desired_workflow_uri)
+
+    # first check if works needs to be done at all
+    if (myTaskObject.workflow != desired_workflow):
+        # set the new status
+        myTaskObject.workflow = desired_workflow
+
     # temporarily disconnect the post_save handler to save the dataproduct (again) and avoiding recursion.
     # I don't use pre_save, because then the 'created' key is not available, which is the most handy way to
     # determine if this dataproduct already exists. (I could also check the database, but this is easier).
@@ -67,8 +83,8 @@ def handle_pre_save(sender, **kwargs):
     connect_signals()
 
     # dispatch a job if the status has changed.
-    if (new_status != None) and (my_status != new_status):
-        jobs.dispatchJob(myTaskObject, new_status)
+    if (desired_status != None) and (status != desired_status):
+        jobs.dispatchJob(myTaskObject, desired_status)
 
 
 @receiver(post_save, sender=Task)
@@ -79,7 +95,7 @@ def post_save_task_handler(sender, **kwargs):
 
 def handle_post_save(sender, **kwargs):
     """
-     pre_save handler for both Task. To create and write its initial status
+     post_save handler for Task. To create and write its initial status
     :param (in) sender: The model class that sends the trigger
     :param (in) kwargs: The instance of the object that sends the trigger.
     """
@@ -91,10 +107,10 @@ def handle_post_save(sender, **kwargs):
         logger.info("save new "+str(myTaskObject.task_type))
 
         # set status
-        myTaskObject.my_status = myTaskObject.new_status
+        myTaskObject.status = myTaskObject.desired_status
 
         # add the new to the status history by brewing a status object out of it
-        myStatus = Status(name=myTaskObject.new_status, task=myTaskObject)
+        myStatus = Status(name=myTaskObject.desired_status, task=myTaskObject)
         myStatus.save()
 
     # temporarily disconnect the post_save handler to save the dataproduct (again) and avoiding recursion.
diff --git a/atdb/taskdatabase/templates/taskdatabase/base.html b/atdb/taskdatabase/templates/taskdatabase/base.html
index d3f7fc9ff6bda37e661da7bcaa69fc2c984ae315..6831dc1ab833a7d26f73ae86616c54a7e08f2def 100644
--- a/atdb/taskdatabase/templates/taskdatabase/base.html
+++ b/atdb/taskdatabase/templates/taskdatabase/base.html
@@ -8,7 +8,7 @@
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 
-    <title>{% block myBlockTitle %}ATDB{% endblock %}</title>
+    <title>{% block myBlockTitle %}ATDB-LDV{% endblock %}</title>
 
     <!-- loads the path to static files -->
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
diff --git a/atdb/taskdatabase/templates/taskdatabase/index.html b/atdb/taskdatabase/templates/taskdatabase/index.html
index 857c52a52ff07646c63b10a0d5048f0aca5e0a46..650241c1692ecfc5b66d8e0ca330eac90944ccb4 100644
--- a/atdb/taskdatabase/templates/taskdatabase/index.html
+++ b/atdb/taskdatabase/templates/taskdatabase/index.html
@@ -44,7 +44,7 @@
     </div>
     {% include 'taskdatabase/pagination.html' %}
 </div>
-    <p class="footer"> Version 1.0.0 (15 jan 2021 - 17:30)
+    <p class="footer"> Version 1.0.0 (17 jan 2021 - 09:00)
     <script type="text/javascript">
         (function(seconds) {
             var refresh,
diff --git a/atdb/taskdatabase/urls.py b/atdb/taskdatabase/urls.py
index 56ebc80aa782be20ff91c31dec3dd0ffcbbfbf97..6b4e813347759aa71794076836e47ac1c396eadb 100644
--- a/atdb/taskdatabase/urls.py
+++ b/atdb/taskdatabase/urls.py
@@ -24,7 +24,7 @@ urlpatterns = [
          name='get-next-taskid-view'),
 
     # --- controller resources ---
-    path('tasks/<int:pk>/setstatus/<new_status>/<page>',
+    path('tasks/<int:pk>/setstatus/<desired_status>/<page>',
          views.TaskSetStatus,
          name='task-setstatus-view'),
 
diff --git a/atdb/taskdatabase/views.py b/atdb/taskdatabase/views.py
index b5bfefe98cfddc75a2fc817656b2060f25abcf53..9d629398fb83d9ec645a474ead7ec6f69e556abd 100644
--- a/atdb/taskdatabase/views.py
+++ b/atdb/taskdatabase/views.py
@@ -30,7 +30,7 @@ class TaskFilter(filters.FilterSet):
         fields = {
             'project': ['exact', 'icontains'],
             'sas_id': ['exact', 'icontains'],
-            'my_status': ['exact', 'icontains', 'in', 'startswith'],
+            'status': ['exact', 'icontains', 'in', 'startswith'],
             'taskID': ['gt', 'lt', 'gte', 'lte','exact', 'icontains', 'startswith','in'],
             'purge_policy': ['exact'],
             'priority': ['exact'],
@@ -70,18 +70,18 @@ class IndexView(ListView):
     context_object_name = 'my_tasks'
 
     def get_queryset(self):
-        my_status = self.request.GET.get('my_status')
-        not_my_status = self.request.GET.get('not_my_status')
+        status = self.request.GET.get('status')
+        not_status = self.request.GET.get('not_status')
         search_box = self.request.GET.get('search_box', None)
 
         if (search_box is not None):
             tasks = get_searched_tasks(search_box)
         else:
              tasks = Task.objects.order_by('-creationTime')
-        if (my_status is not None):
-            tasks = get_filtered_tasks(my_status)
-        if (not_my_status is not None):
-            tasks = get_unfiltered_tasks(not_my_status)
+        if (status is not None):
+            tasks = get_filtered_tasks(status)
+        if (not_status is not None):
+            tasks = get_unfiltered_tasks(not_status)
 
         paginator = Paginator(tasks, config.TASKS_PER_PAGE)  # Show 50 tasks per page
 
@@ -100,17 +100,17 @@ class IndexView(ListView):
 
 # an attempt to get a filtering mechanism in the GUI
 # filter on a single status
-# http://localhost:8000/atdb/query?my_status=scheduled
-def get_filtered_tasks(my_status):
+# http://localhost:8000/atdb/query?status=scheduled
+def get_filtered_tasks(status):
     q = Task.objects.order_by('-creationTime')
-    q = q.filter(my_status=my_status)
-    #q = q.exclude(my_status__icontains='removed')
+    q = q.filter(status=status)
+    #q = q.exclude(status__icontains='removed')
     return q
 
-# http://localhost:8000/atdb/query?not_my_status=removed
-def get_unfiltered_tasks(my_status):
+# http://localhost:8000/atdb/query?not_status=removed
+def get_unfiltered_tasks(status):
     q = Task.objects.order_by('-creationTime')
-    q = q.exclude(my_status=my_status)
+    q = q.exclude(status=status)
     return q
 
 def get_searched_tasks(search):
@@ -118,7 +118,7 @@ def get_searched_tasks(search):
         Q(taskID__contains=search) |
         Q(sas_id__contains=search) |
         Q(task_type__icontains=search) |
-        Q(my_status__icontains=search) |
+        Q(status__icontains=search) |
         Q(project__icontains=search)).order_by('-creationTime')
     return tasks
 
@@ -189,7 +189,7 @@ class LogEntryDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView):
 
 
 # --- controller resources, triggered by a button in the GUI or directoy with a URL ---
-# set task status to 'new_status' - called from the GUI
+# set task status to 'desired_status' - called from the GUI
 
 
 def Skip(request,pk,skip_it,page):
@@ -200,10 +200,10 @@ def Skip(request,pk,skip_it,page):
     return redirect('/atdb/?page='+page)
 
 
-def TaskSetStatus(request,pk,new_status,page):
+def TaskSetStatus(request,pk,desired_status,page):
     model = Task
     task = Task.objects.get(pk=pk)
-    task.new_status = new_status
+    task.desired_status = desired_status
     task.save()
     return redirect('/atdb/?page='+page)