diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc
deleted file mode 100644
index ea14c7004bc2b9b99b7dbd4d824115c2f8692d23..0000000000000000000000000000000000000000
Binary files a/digipus/__pycache__/settings.cpython-36.pyc and /dev/null differ
diff --git a/digipus/settings.py b/digipus/settings.py
index e988dc91ba8785b03fe10f1ce566d960740c9002..f41bb346df16ab08ec55031f2050d334dba53cc3 100644
--- a/digipus/settings.py
+++ b/digipus/settings.py
@@ -46,6 +46,7 @@ INSTALLED_APPS = [
     "register.apps.RegisterConfig",
     "administration.apps.AdministrationConfig",
     'crispy_forms',
+    "traffic_statistics",
 ]
 
 MIDDLEWARE = [
diff --git a/digipus/urls.py b/digipus/urls.py
index 263c49e6909847f597bf7dfe6dbea237d9295f34..6f5ec5477ac62cc199a3c424d5a3970ca36507cc 100644
--- a/digipus/urls.py
+++ b/digipus/urls.py
@@ -24,4 +24,5 @@ urlpatterns = [
     path("", include("authentication.urls"), name="auth"),
     path("", include("app.urls"), name="app"),
     path("administration/", include("administration.urls")),
+    path("statistics/", include("traffic_statistics.urls")),
 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/traffic_statistics/__init__.py b/traffic_statistics/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/traffic_statistics/apps.py b/traffic_statistics/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..82fc14779d3017f7360b53b374f080ed291ac1a3
--- /dev/null
+++ b/traffic_statistics/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class TrafficStatisticsConfig(AppConfig):
+    name = "traffic_statistics"
diff --git a/traffic_statistics/constants.py b/traffic_statistics/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..edff22f3267a2b98e68c3e67c33c416e7bdfd652
--- /dev/null
+++ b/traffic_statistics/constants.py
@@ -0,0 +1,19 @@
+# URL Parameters
+
+DATE_FORMAT = "%d-%m-%Y"
+DATE_FORMAT_FORMAL = "dd-mm-yyyy"
+
+PARAM_START_DATE = "start_date"
+PARAM_END_DATE = "end_date"
+
+# Errors
+
+ERROR_NULL_DATE_PARAM = "{} parameter must be given and cannot be empty"
+ERROR_INVALID_DATE_PARAM = "{} parameter is invalid, accepted format: " + DATE_FORMAT_FORMAL
+
+# Fields
+
+FIELD_LIKES = "likes"
+FIELD_COMMENTS = "comments"
+FIELD_VIEWS = "views"
+FIELD_DOWNLOADS = "downloads"
diff --git a/traffic_statistics/tests.py b/traffic_statistics/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b78a6344a60523a4fc80de053b0e5e06791822b
--- /dev/null
+++ b/traffic_statistics/tests.py
@@ -0,0 +1,151 @@
+import json
+from datetime import date, datetime, time, timedelta
+from django.apps import apps
+from django.test import TestCase
+from django.urls import resolve
+from django.utils.timezone import localdate, make_aware
+from random import randrange
+
+from app.models import (
+    Comment,
+    DownloadStatistics,
+    Like,
+    ViewStatistics,
+)
+from traffic_statistics.apps import TrafficStatisticsConfig
+from traffic_statistics.constants import (
+    DATE_FORMAT,
+    PARAM_END_DATE,
+    PARAM_START_DATE,
+
+    ERROR_INVALID_DATE_PARAM,
+    ERROR_NULL_DATE_PARAM,
+
+    FIELD_COMMENTS,
+    FIELD_DOWNLOADS,
+    FIELD_LIKES,
+    FIELD_VIEWS,
+)
+from traffic_statistics.views import StatisticsAPIView
+
+
+def generate_traffic_data(traffic_per_dates):
+
+    def random_time_of_day(date):
+        # to test the daily grouping on any time of the day
+        initial_datetime = datetime.combine(date, time())
+
+        SECS_IN_A_DAY = 24 * 60 * 60
+        generated_datetime = initial_datetime + timedelta(
+            seconds=randrange(SECS_IN_A_DAY)
+        )
+        return generated_datetime
+
+    traffic_objects = [Comment, DownloadStatistics, Like, ViewStatistics]
+    labels = [FIELD_COMMENTS, FIELD_DOWNLOADS, FIELD_LIKES, FIELD_VIEWS]
+
+    traffic_count = {}
+
+    for traffic_date, counts in traffic_per_dates:
+
+        traffic_date_str = traffic_date.strftime(DATE_FORMAT)
+        traffic_count[traffic_date_str] = {}
+
+        i = 0
+        for traffic_object, label in zip(traffic_objects, labels):
+            count = counts[i]
+            for _ in range(count):
+                timestamp = make_aware(random_time_of_day(traffic_date))
+                traffic_object.objects.create(timestamp=timestamp)
+
+            traffic_count[traffic_date_str][label] = count
+
+    return traffic_count
+
+
+class StatisticsAPITest(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        cls.base_url = "/statistics/api/"
+
+    def test_app_name_config(self):
+        self.assertEqual(TrafficStatisticsConfig.name, "traffic_statistics")
+        self.assertEqual(
+            apps.get_app_config("traffic_statistics").name, "traffic_statistics")
+
+    def test_url_resolves_to_statistics_api_view(self):
+        found = resolve(self.base_url)
+        self.assertEqual(found.func.__name__, StatisticsAPIView.as_view().__name__)
+
+    def _run_error_assertion(self, url, error):
+        response = self.client.get(url)
+        data = json.loads(response.content)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(data["error"], error)
+
+    def test_api_returns_error_on_unprovided_date_params(self):
+        # both params are empty
+        self._run_error_assertion(
+            self.base_url,
+            ERROR_NULL_DATE_PARAM.format(PARAM_START_DATE),
+        )
+        # start date param is empty
+        self._run_error_assertion(
+            self.base_url + "?end_date=01-10-2020",
+            ERROR_NULL_DATE_PARAM.format(PARAM_START_DATE),
+        )
+        # end date param is empty
+        self._run_error_assertion(
+            self.base_url + "?start_date=01-10-2020",
+            ERROR_NULL_DATE_PARAM.format(PARAM_END_DATE),
+        )
+
+    def test_api_returns_error_on_invalid_date_params_format(self):
+        # invalid start date
+        self._run_error_assertion(
+            self.base_url + "?start_date=20-20-2020&end_date=01-10-2020",
+            ERROR_INVALID_DATE_PARAM.format(PARAM_START_DATE),
+        )
+        # invalid end date
+        self._run_error_assertion(
+            self.base_url + "?start_date=30-09-2020&end_date=2020-10-01",
+            ERROR_INVALID_DATE_PARAM.format(PARAM_END_DATE),
+        )
+
+    def test_api_success_on_valid_date_params(self):
+        response = self.client.get(
+            self.base_url + "?start_date=01-01-2020&end_date=01-01-2020"
+        )
+        self.assertEqual(response.status_code, 200)
+
+    def test_api_returns_correct_daily_traffic_count(self):
+        traffic_dates = [
+            # (date, [comments, downloads, likes, views])
+            (date(year=2020, month=9, day=27), [0, 1, 2, 3]),
+            (date(year=2020, month=9, day=28), [1, 2, 3, 0]),
+            (date(year=2020, month=9, day=29), [2, 3, 0, 1]),
+            (date(year=2020, month=9, day=30), [3, 0, 1, 2]),
+        ]
+
+        traffic_count = generate_traffic_data(traffic_dates)
+        start_date = traffic_dates[0][0].strftime(DATE_FORMAT)
+        end_date = traffic_dates[3][0].strftime(DATE_FORMAT)
+
+        response = self.client.get(
+            self.base_url + f"?start_date={start_date}&end_date={end_date}")
+        self.assertEqual(response.status_code, 200)
+        self.assertJSONEqual(response.content, traffic_count)
+
+    def test_api_returns_up_to_today_at_max(self):
+        today = localdate()
+        tomorrow = today + timedelta(days=1)
+
+        tomorrow_str = tomorrow.strftime(DATE_FORMAT)
+
+        response = self.client.get(
+            self.base_url + f"?start_date={tomorrow_str}&end_date={tomorrow_str}")
+        data = json.loads(response.content)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertFalse(tomorrow_str in data.keys())
diff --git a/traffic_statistics/urls.py b/traffic_statistics/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..54ea79b2b394e132ad5669563b7bf2df4965ea91
--- /dev/null
+++ b/traffic_statistics/urls.py
@@ -0,0 +1,9 @@
+from django.urls import path
+
+from traffic_statistics.views import StatisticsAPIView
+
+app_name = "traffic_statistics"
+
+urlpatterns = [
+    path("api/", StatisticsAPIView.as_view()),
+]
diff --git a/traffic_statistics/utils/__init__.py b/traffic_statistics/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/traffic_statistics/utils/requests.py b/traffic_statistics/utils/requests.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c3688f2102276e0fdf21925cab52dbee1b977ba
--- /dev/null
+++ b/traffic_statistics/utils/requests.py
@@ -0,0 +1,30 @@
+from datetime import datetime
+
+from traffic_statistics.constants import (
+    DATE_FORMAT,
+    PARAM_END_DATE,
+    PARAM_START_DATE,
+
+    ERROR_INVALID_DATE_PARAM,
+    ERROR_NULL_DATE_PARAM,
+)
+
+
+def validate_date_param(date_str_param, param_name):
+    if date_str_param is None:
+        raise ValueError(ERROR_NULL_DATE_PARAM.format(param_name))
+
+    try:
+        return datetime.strptime(date_str_param, DATE_FORMAT).date()
+    except ValueError:
+        raise ValueError(ERROR_INVALID_DATE_PARAM.format(param_name))
+
+
+def extract_date_range_params(request):
+    start_date_param = request.GET.get(PARAM_START_DATE)
+    end_date_param = request.GET.get(PARAM_END_DATE)
+
+    start_date = validate_date_param(start_date_param, PARAM_START_DATE)
+    end_date = validate_date_param(end_date_param, PARAM_END_DATE)
+
+    return start_date, end_date
diff --git a/traffic_statistics/utils/statistics.py b/traffic_statistics/utils/statistics.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb25008a492ab6ebf490bd8009618245bba037d8
--- /dev/null
+++ b/traffic_statistics/utils/statistics.py
@@ -0,0 +1,39 @@
+from datetime import timedelta
+from django.db.models import Count
+from django.db.models.functions import TruncDate
+
+from traffic_statistics.constants import (
+    DATE_FORMAT,
+    FIELD_COMMENTS,
+    FIELD_DOWNLOADS,
+    FIELD_LIKES,
+    FIELD_VIEWS,
+)
+
+
+def get_daily_traffic_count(model, start_date, end_date):
+    queryset = model.objects \
+        .annotate(date=TruncDate('timestamp')) \
+        .filter(date__gte=start_date, date__lte=end_date) \
+        .values('date') \
+        .annotate(count=Count('date')) \
+        .values_list('date', 'count')
+    return queryset
+
+
+def generate_initial_traffic_count_dict(start_date, end_date):
+    date_range_dict = {}
+
+    iter_date = start_date
+    while iter_date <= end_date:
+        date_str = iter_date.strftime(DATE_FORMAT)
+
+        date_range_dict[date_str] = {
+            FIELD_COMMENTS: 0,
+            FIELD_DOWNLOADS: 0,
+            FIELD_LIKES: 0,
+            FIELD_VIEWS: 0,
+        }
+        iter_date += timedelta(days=1)
+
+    return date_range_dict
diff --git a/traffic_statistics/views.py b/traffic_statistics/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..6dfdea2791b9a3fb96027b43d47be0684eea154c
--- /dev/null
+++ b/traffic_statistics/views.py
@@ -0,0 +1,55 @@
+from django.http import JsonResponse
+from django.views.generic import ListView
+from django.utils.timezone import localdate
+
+from app.models import (
+    Comment,
+    DownloadStatistics,
+    Like,
+    ViewStatistics,
+)
+from traffic_statistics.constants import (
+    DATE_FORMAT,
+    FIELD_COMMENTS,
+    FIELD_DOWNLOADS,
+    FIELD_LIKES,
+    FIELD_VIEWS,
+)
+from traffic_statistics.utils.requests import extract_date_range_params
+from traffic_statistics.utils.statistics import (
+    generate_initial_traffic_count_dict,
+    get_daily_traffic_count,
+)
+
+
+class StatisticsAPIView(ListView):
+
+    def get(self, *args, **kwargs):
+        try:
+            start_date, end_date = extract_date_range_params(self.request)
+        except Exception as e:
+            return JsonResponse(data={"error": str(e)}, status=400)
+
+        today = localdate()
+        start_date = min(start_date, today)
+        end_date = min(end_date, today)
+
+        statistics_dict = generate_initial_traffic_count_dict(start_date, end_date)
+
+        daily_traffic_counts = [
+            get_daily_traffic_count(Like, start_date, end_date),
+            get_daily_traffic_count(Comment, start_date, end_date),
+            get_daily_traffic_count(ViewStatistics, start_date, end_date),
+            get_daily_traffic_count(DownloadStatistics, start_date, end_date),
+        ]
+        labels = [FIELD_LIKES, FIELD_COMMENTS, FIELD_VIEWS, FIELD_DOWNLOADS]
+
+        for (daily_all_traffic_count, label) in zip(daily_traffic_counts, labels):
+
+            for daily_traffic_count in daily_all_traffic_count:
+                date, count = daily_traffic_count
+
+                date_str = date.strftime(DATE_FORMAT)
+                statistics_dict[date_str][label] = count
+
+        return JsonResponse(data=statistics_dict, status=200)