diff --git a/backend/apps/github/api/issue.py b/backend/apps/github/api/issue.py
deleted file mode 100644
index b957c9e027..0000000000
--- a/backend/apps/github/api/issue.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""Issue API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.github.models.issue import Issue
-
-
-# Serializers define the API representation.
-class IssueSerializer(serializers.HyperlinkedModelSerializer):
-    """Issue serializer."""
-
-    class Meta:
-        model = Issue
-        fields = (
-            "title",
-            "body",
-            "state",
-            "url",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class IssueViewSet(viewsets.ReadOnlyModelViewSet):
-    """Issue view set."""
-
-    queryset = Issue.objects.all()
-    serializer_class = IssueSerializer
diff --git a/backend/apps/github/api/label.py b/backend/apps/github/api/label.py
deleted file mode 100644
index 288dec142a..0000000000
--- a/backend/apps/github/api/label.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Label API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.github.models.label import Label
-
-
-# Serializers define the API representation.
-class LabelSerializer(serializers.HyperlinkedModelSerializer):
-    """Label serializer."""
-
-    class Meta:
-        model = Label
-        fields = (
-            "name",
-            "description",
-            "color",
-        )
-
-
-# ViewSets define the view behavior.
-class LabelViewSet(viewsets.ReadOnlyModelViewSet):
-    """Label view set."""
-
-    queryset = Label.objects.all()
-    serializer_class = LabelSerializer
diff --git a/backend/apps/github/api/organization.py b/backend/apps/github/api/organization.py
deleted file mode 100644
index d34f88c614..0000000000
--- a/backend/apps/github/api/organization.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""Organization API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.github.models.organization import Organization
-
-
-# Serializers define the API representation.
-class OrganizationSerializer(serializers.HyperlinkedModelSerializer):
-    """Organization serializer."""
-
-    class Meta:
-        model = Organization
-        fields = (
-            "name",
-            "login",
-            "company",
-            "location",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class OrganizationViewSet(viewsets.ReadOnlyModelViewSet):
-    """Organization view set."""
-
-    queryset = Organization.objects.filter(is_owasp_related_organization=True)
-    serializer_class = OrganizationSerializer
diff --git a/backend/apps/github/api/release.py b/backend/apps/github/api/release.py
deleted file mode 100644
index 0e99c51831..0000000000
--- a/backend/apps/github/api/release.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Release API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.github.models.release import Release
-
-
-# Serializers define the API representation.
-class ReleaseSerializer(serializers.HyperlinkedModelSerializer):
-    """Release serializer."""
-
-    class Meta:
-        model = Release
-        fields = (
-            "name",
-            "tag_name",
-            "description",
-            "created_at",
-            "published_at",
-        )
-
-
-# ViewSets define the view behavior.
-class ReleaseViewSet(viewsets.ReadOnlyModelViewSet):
-    """Release view set."""
-
-    queryset = Release.objects.all()
-    serializer_class = ReleaseSerializer
diff --git a/backend/apps/github/api/repository.py b/backend/apps/github/api/repository.py
deleted file mode 100644
index 56a8e421b0..0000000000
--- a/backend/apps/github/api/repository.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""Repository API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.github.models.repository import Repository
-
-
-# Serializers define the API representation.
-class RepositorySerializer(serializers.HyperlinkedModelSerializer):
-    """Repository serializer."""
-
-    class Meta:
-        model = Repository
-        fields = (
-            "name",
-            "description",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class RepositoryViewSet(viewsets.ReadOnlyModelViewSet):
-    """Repository view set."""
-
-    queryset = Repository.objects.all()
-    serializer_class = RepositorySerializer
diff --git a/backend/apps/github/api/urls.py b/backend/apps/github/api/urls.py
deleted file mode 100644
index 60673d1ae3..0000000000
--- a/backend/apps/github/api/urls.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""GitHub API URLs."""
-
-from rest_framework import routers
-
-from apps.github.api.issue import IssueViewSet
-from apps.github.api.label import LabelViewSet
-from apps.github.api.organization import OrganizationViewSet
-from apps.github.api.release import ReleaseViewSet
-from apps.github.api.repository import RepositoryViewSet
-from apps.github.api.user import UserViewSet
-
-router = routers.SimpleRouter()
-
-router.register(r"github/issues", IssueViewSet)
-router.register(r"github/labels", LabelViewSet)
-router.register(r"github/organizations", OrganizationViewSet)
-router.register(r"github/releases", ReleaseViewSet)
-router.register(r"github/repositories", RepositoryViewSet)
-router.register(r"github/users", UserViewSet)
diff --git a/backend/apps/github/api/user.py b/backend/apps/github/api/user.py
deleted file mode 100644
index aa3cbe3ec3..0000000000
--- a/backend/apps/github/api/user.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""User API."""
-
-from rest_framework import serializers, viewsets
-from rest_framework.decorators import action
-from rest_framework.response import Response
-
-from apps.github.models.user import User
-
-
-# Serializers define the API representation.
-class UserSerializer(serializers.HyperlinkedModelSerializer):
-    """User serializer."""
-
-    class Meta:
-        model = User
-        fields = (
-            "avatar_url",
-            "bio",
-            "company",
-            "email",
-            "followers_count",
-            "following_count",
-            "location",
-            "login",
-            "name",
-            "public_repositories_count",
-            "title",
-            "twitter_username",
-            "url",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class UserViewSet(viewsets.ReadOnlyModelViewSet):
-    """User view set."""
-
-    queryset = User.objects.all()
-    serializer_class = UserSerializer
-
-    @action(detail=False, methods=["get"], url_path="login/(?P<login>[^/.]+)")
-    def get_user_by_login(self, request, login=None):
-        """Retrieve a user by their login.
-
-        Args:
-            request (Request): The HTTP request object.
-            login (str, optional): The login of the user to retrieve.
-
-        Returns:
-            Response: The serialized user data or a 404 error if the user is not found.
-
-        """
-        try:
-            user = User.objects.get(login=login)
-            serializer = self.get_serializer(user)
-            data = serializer.data
-            return Response(data)
-        except User.DoesNotExist:
-            return Response({"detail": "User not found."}, status=404)
diff --git a/backend/apps/github/api/v1/__init__.py b/backend/apps/github/api/v1/__init__.py
new file mode 100644
index 0000000000..a9466986f2
--- /dev/null
+++ b/backend/apps/github/api/v1/__init__.py
@@ -0,0 +1 @@
+"""Version 1 API."""
diff --git a/backend/apps/github/api/v1/issue.py b/backend/apps/github/api/v1/issue.py
new file mode 100644
index 0000000000..a58085a64e
--- /dev/null
+++ b/backend/apps/github/api/v1/issue.py
@@ -0,0 +1,33 @@
+"""Issue API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.github.models.issue import Issue
+
+router = Router()
+
+
+class IssueSchema(Schema):
+    """Schema for Issue."""
+
+    body: str
+    created_at: datetime
+    title: str
+    state: str
+    updated_at: datetime
+    url: str
+
+
+@router.get("/", response={200: list[IssueSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_issues(request: HttpRequest) -> list[IssueSchema] | dict:
+    """Get all issues."""
+    issues = Issue.objects.all()
+    if not issues.exists():
+        raise HttpError(404, "Issues not found")
+    return issues
diff --git a/backend/apps/github/api/v1/label.py b/backend/apps/github/api/v1/label.py
new file mode 100644
index 0000000000..be8af689ee
--- /dev/null
+++ b/backend/apps/github/api/v1/label.py
@@ -0,0 +1,28 @@
+"""Label API."""
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.github.models.label import Label
+
+router = Router()
+
+
+class LabelSchema(Schema):
+    """Schema for Label."""
+
+    color: str
+    description: str
+    name: str
+
+
+@router.get("/", response={200: list[LabelSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_label(request: HttpRequest) -> list[LabelSchema] | dict:
+    """Get all labels."""
+    labels = Label.objects.all()
+    if not labels.exists():
+        raise HttpError(404, "Labels not found")
+    return labels
diff --git a/backend/apps/github/api/v1/organization.py b/backend/apps/github/api/v1/organization.py
new file mode 100644
index 0000000000..a4ce989f3b
--- /dev/null
+++ b/backend/apps/github/api/v1/organization.py
@@ -0,0 +1,33 @@
+"""Organization API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.github.models.organization import Organization
+
+router = Router()
+
+
+class OrganizationSchema(Schema):
+    """Schema for Organization."""
+
+    company: str
+    created_at: datetime
+    location: str
+    login: str
+    name: str
+    updated_at: datetime
+
+
+@router.get("/", response={200: list[OrganizationSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_organization(request: HttpRequest) -> list[OrganizationSchema] | dict:
+    """Get all organizations."""
+    organizations = Organization.objects.filter(is_owasp_related_organization=True)
+    if not organizations.exists():
+        raise HttpError(404, "Organizations not found")
+    return organizations
diff --git a/backend/apps/github/api/v1/release.py b/backend/apps/github/api/v1/release.py
new file mode 100644
index 0000000000..d9549aa906
--- /dev/null
+++ b/backend/apps/github/api/v1/release.py
@@ -0,0 +1,32 @@
+"""Release API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.github.models.release import Release
+
+router = Router()
+
+
+class ReleaseSchema(Schema):
+    """Schema for Release."""
+
+    created_at: datetime
+    description: str
+    name: str
+    published_at: datetime
+    tag_name: str
+
+
+@router.get("/", response={200: list[ReleaseSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_release(request: HttpRequest) -> list[ReleaseSchema]:
+    """Get all releases."""
+    releases = Release.objects.all()
+    if not releases.exists():
+        raise HttpError(404, "Releases not found")
+    return releases
diff --git a/backend/apps/github/api/v1/repository.py b/backend/apps/github/api/v1/repository.py
new file mode 100644
index 0000000000..020014f7c6
--- /dev/null
+++ b/backend/apps/github/api/v1/repository.py
@@ -0,0 +1,31 @@
+"""Repository API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.github.models.repository import Repository
+
+router = Router()
+
+
+class RepositorySchema(Schema):
+    """Schema for Repository."""
+
+    created_at: datetime
+    description: str
+    name: str
+    updated_at: datetime
+
+
+@router.get("/", response={200: list[RepositorySchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_repository(request: HttpRequest) -> list[RepositorySchema]:
+    """Get all repositories."""
+    repositories = Repository.objects.all()
+    if not repositories.exists():
+        raise HttpError(404, "Repositories not found")
+    return repositories
diff --git a/backend/apps/github/api/v1/urls.py b/backend/apps/github/api/v1/urls.py
new file mode 100644
index 0000000000..c381f770c7
--- /dev/null
+++ b/backend/apps/github/api/v1/urls.py
@@ -0,0 +1,19 @@
+"""GitHub API URLs."""
+
+from ninja import Router
+
+from apps.github.api.v1.issue import router as issue_router
+from apps.github.api.v1.label import router as label_router
+from apps.github.api.v1.organization import router as organization_router
+from apps.github.api.v1.release import router as release_router
+from apps.github.api.v1.repository import router as repository_router
+from apps.github.api.v1.user import router as user_router
+
+router = Router()
+
+router.add_router(r"/issues", issue_router)
+router.add_router(r"/labels", label_router)
+router.add_router(r"/organizations", organization_router)
+router.add_router(r"/releases", release_router)
+router.add_router(r"/repositories", repository_router)
+router.add_router(r"/users", user_router)
diff --git a/backend/apps/github/api/v1/user.py b/backend/apps/github/api/v1/user.py
new file mode 100644
index 0000000000..5222c21538
--- /dev/null
+++ b/backend/apps/github/api/v1/user.py
@@ -0,0 +1,51 @@
+"""User API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.github.models.user import User
+
+router = Router()
+
+
+class UserSchema(Schema):
+    """Schema for User."""
+
+    avatar_url: str
+    bio: str
+    company: str
+    created_at: datetime
+    email: str
+    followers_count: int
+    following_count: int
+    location: str
+    login: str
+    name: str
+    public_repositories_count: int
+    title: str
+    twitter_username: str
+    updated_at: datetime
+    url: str
+
+
+@router.get("/", response={200: list[UserSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_users(request: HttpRequest) -> list[UserSchema]:
+    """Get all users."""
+    users = User.objects.all()
+    if not users.exists():
+        raise HttpError(404, "Users not found")
+    return users
+
+
+@router.get("/{login}", response={200: UserSchema, 404: dict})
+def get_user(request: HttpRequest, login: str) -> UserSchema:
+    """Get user by login."""
+    user = User.objects.filter(login=login).first()
+    if not user:
+        raise HttpError(404, "User not found")
+    return user
diff --git a/backend/apps/owasp/api/chapter.py b/backend/apps/owasp/api/chapter.py
deleted file mode 100644
index bd91b5e7c4..0000000000
--- a/backend/apps/owasp/api/chapter.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Chapter API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.owasp.models.chapter import Chapter
-
-
-# Serializers define the API representation.
-class ChapterSerializer(serializers.HyperlinkedModelSerializer):
-    """Chapter serializer."""
-
-    class Meta:
-        model = Chapter
-        fields = (
-            "name",
-            "country",
-            "region",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class ChapterViewSet(viewsets.ReadOnlyModelViewSet):
-    """Chapter view set."""
-
-    queryset = Chapter.objects.all()
-    serializer_class = ChapterSerializer
diff --git a/backend/apps/owasp/api/committee.py b/backend/apps/owasp/api/committee.py
deleted file mode 100644
index 7e30c720f8..0000000000
--- a/backend/apps/owasp/api/committee.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""Committee API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.owasp.models.committee import Committee
-
-
-# Serializers define the API representation.
-class CommitteeSerializer(serializers.HyperlinkedModelSerializer):
-    """Committee serializer."""
-
-    class Meta:
-        model = Committee
-        fields = (
-            "name",
-            "description",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class CommitteeViewSet(viewsets.ReadOnlyModelViewSet):
-    """Committee view set."""
-
-    queryset = Committee.objects.all()
-    serializer_class = CommitteeSerializer
diff --git a/backend/apps/owasp/api/event.py b/backend/apps/owasp/api/event.py
deleted file mode 100644
index 8683c0c195..0000000000
--- a/backend/apps/owasp/api/event.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Event API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.owasp.models.event import Event
-
-
-# Serializers define the API representation.
-class EventSerializer(serializers.HyperlinkedModelSerializer):
-    """Event serializer."""
-
-    class Meta:
-        model = Event
-        fields = (
-            "name",
-            "description",
-            "url",
-        )
-
-
-# ViewSets define the view behavior.
-class EventViewSet(viewsets.ReadOnlyModelViewSet):
-    """Event view set."""
-
-    queryset = Event.objects.all()
-    serializer_class = EventSerializer
diff --git a/backend/apps/owasp/api/project.py b/backend/apps/owasp/api/project.py
deleted file mode 100644
index 74d869a7bc..0000000000
--- a/backend/apps/owasp/api/project.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Project API."""
-
-from rest_framework import serializers, viewsets
-
-from apps.owasp.models.project import Project
-
-
-# Serializers define the API representation.
-class ProjectSerializer(serializers.HyperlinkedModelSerializer):
-    """Project serializer."""
-
-    class Meta:
-        model = Project
-        fields = (
-            "name",
-            "description",
-            "level",
-            "created_at",
-            "updated_at",
-        )
-
-
-# ViewSets define the view behavior.
-class ProjectViewSet(viewsets.ReadOnlyModelViewSet):
-    """Project view set."""
-
-    queryset = Project.objects.all()
-    serializer_class = ProjectSerializer
diff --git a/backend/apps/owasp/api/urls.py b/backend/apps/owasp/api/urls.py
deleted file mode 100644
index 31fd1ff4c1..0000000000
--- a/backend/apps/owasp/api/urls.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""GitHub API URLs."""
-
-from rest_framework import routers
-
-from apps.owasp.api.chapter import ChapterViewSet
-from apps.owasp.api.committee import CommitteeViewSet
-from apps.owasp.api.event import EventViewSet
-from apps.owasp.api.project import ProjectViewSet
-
-router = routers.SimpleRouter()
-
-router.register(r"owasp/chapters", ChapterViewSet)
-router.register(r"owasp/committees", CommitteeViewSet)
-router.register(r"owasp/events", EventViewSet)
-router.register(r"owasp/projects", ProjectViewSet)
diff --git a/backend/apps/owasp/api/v1/__init__.py b/backend/apps/owasp/api/v1/__init__.py
new file mode 100644
index 0000000000..a9466986f2
--- /dev/null
+++ b/backend/apps/owasp/api/v1/__init__.py
@@ -0,0 +1 @@
+"""Version 1 API."""
diff --git a/backend/apps/owasp/api/v1/chapter.py b/backend/apps/owasp/api/v1/chapter.py
new file mode 100644
index 0000000000..7a28b1c41c
--- /dev/null
+++ b/backend/apps/owasp/api/v1/chapter.py
@@ -0,0 +1,32 @@
+"""Chapter API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.owasp.models.chapter import Chapter
+
+router = Router()
+
+
+class ChapterSchema(Schema):
+    """Schema for Chapter."""
+
+    country: str
+    created_at: datetime
+    name: str
+    region: str
+    updated_at: datetime
+
+
+@router.get("/", response={200: list[ChapterSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_chapters(request: HttpRequest) -> list[ChapterSchema]:
+    """Get all chapters."""
+    chapters = Chapter.objects.all()
+    if not chapters.exists():
+        raise HttpError(404, "Chapters not found")
+    return chapters
diff --git a/backend/apps/owasp/api/v1/committee.py b/backend/apps/owasp/api/v1/committee.py
new file mode 100644
index 0000000000..615dc59db4
--- /dev/null
+++ b/backend/apps/owasp/api/v1/committee.py
@@ -0,0 +1,31 @@
+"""Committee API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.owasp.models.committee import Committee
+
+router = Router()
+
+
+class CommitteeSchema(Schema):
+    """Schema for Committee."""
+
+    name: str
+    description: str
+    created_at: datetime
+    updated_at: datetime
+
+
+@router.get("/", response={200: list[CommitteeSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_committees(request: HttpRequest) -> list[CommitteeSchema]:
+    """Get all committees."""
+    committees = Committee.objects.all()
+    if not committees.exists():
+        raise HttpError(404, "Committees not found")
+    return committees
diff --git a/backend/apps/owasp/api/v1/event.py b/backend/apps/owasp/api/v1/event.py
new file mode 100644
index 0000000000..f917a88a0f
--- /dev/null
+++ b/backend/apps/owasp/api/v1/event.py
@@ -0,0 +1,28 @@
+"""Event API."""
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.owasp.models.event import Event
+
+router = Router()
+
+
+class EventSchema(Schema):
+    """Schema for Event."""
+
+    description: str
+    name: str
+    url: str
+
+
+@router.get("/", response={200: list[EventSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_events(request: HttpRequest) -> list[EventSchema]:
+    """Get all events."""
+    events = Event.objects.all()
+    if not events.exists():
+        raise HttpError(404, "Events not found")
+    return events
diff --git a/backend/apps/owasp/api/v1/project.py b/backend/apps/owasp/api/v1/project.py
new file mode 100644
index 0000000000..5225e23a7c
--- /dev/null
+++ b/backend/apps/owasp/api/v1/project.py
@@ -0,0 +1,32 @@
+"""Project API."""
+
+from datetime import datetime
+
+from django.http import HttpRequest
+from ninja import Router, Schema
+from ninja.errors import HttpError
+from ninja.pagination import PageNumberPagination, paginate
+
+from apps.owasp.models.project import Project
+
+router = Router()
+
+
+class ProjectSchema(Schema):
+    """Schema for Project."""
+
+    created_at: datetime
+    description: str
+    level: str
+    name: str
+    updated_at: datetime
+
+
+@router.get("/", response={200: list[ProjectSchema], 404: dict})
+@paginate(PageNumberPagination, page_size=100)
+def list_projects(request: HttpRequest) -> list[ProjectSchema]:
+    """Get all projects."""
+    projects = Project.objects.all()
+    if not projects.exists():
+        raise HttpError(404, "Projects not found")
+    return projects
diff --git a/backend/apps/owasp/api/v1/urls.py b/backend/apps/owasp/api/v1/urls.py
new file mode 100644
index 0000000000..49c10310b4
--- /dev/null
+++ b/backend/apps/owasp/api/v1/urls.py
@@ -0,0 +1,15 @@
+"""GitHub API URLs."""
+
+from ninja import Router
+
+from apps.owasp.api.v1.chapter import router as chapter_router
+from apps.owasp.api.v1.committee import router as committee_router
+from apps.owasp.api.v1.event import router as event_router
+from apps.owasp.api.v1.project import router as project_router
+
+router = Router()
+
+router.add_router(r"/chapters", chapter_router)
+router.add_router(r"/committees", committee_router)
+router.add_router(r"/events", event_router)
+router.add_router(r"/projects", project_router)
diff --git a/backend/poetry.lock b/backend/poetry.lock
index a06c589f02..9181269bb7 100644
--- a/backend/poetry.lock
+++ b/backend/poetry.lock
@@ -799,19 +799,25 @@ asgiref = ">=3.6"
 django = ">=4.2"
 
 [[package]]
-name = "django-filter"
-version = "25.1"
-description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
+name = "django-ninja"
+version = "1.4.3"
+description = "Django Ninja - Fast Django REST framework"
 optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.7"
 groups = ["main"]
 files = [
-    {file = "django_filter-25.1-py3-none-any.whl", hash = "sha256:4fa48677cf5857b9b1347fed23e355ea792464e0fe07244d1fdfb8a806215b80"},
-    {file = "django_filter-25.1.tar.gz", hash = "sha256:1ec9eef48fa8da1c0ac9b411744b16c3f4c31176c867886e4c48da369c407153"},
+    {file = "django_ninja-1.4.3-py3-none-any.whl", hash = "sha256:f3204137a059437b95677049474220611f1cf9efedba9213556474b75168fa01"},
+    {file = "django_ninja-1.4.3.tar.gz", hash = "sha256:e46d477ca60c228d2a5eb3cc912094928ea830d364501f966661eeada67cb038"},
 ]
 
 [package.dependencies]
-Django = ">=4.2"
+Django = ">=3.1"
+pydantic = ">=2.0,<3.0.0"
+
+[package.extras]
+dev = ["pre-commit"]
+doc = ["markdown-include", "mkdocs", "mkdocs-material", "mkdocstrings"]
+test = ["django-stubs", "mypy (==1.7.1)", "psycopg2-binary", "pytest", "pytest-asyncio", "pytest-cov", "pytest-django", "ruff (==0.5.7)"]
 
 [[package]]
 name = "django-redis"
@@ -857,21 +863,6 @@ libcloud = ["apache-libcloud"]
 s3 = ["boto3 (>=1.4.4)"]
 sftp = ["paramiko (>=1.15)"]
 
-[[package]]
-name = "djangorestframework"
-version = "3.16.0"
-description = "Web APIs for Django, made easy."
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
-    {file = "djangorestframework-3.16.0-py3-none-any.whl", hash = "sha256:bea7e9f6b96a8584c5224bfb2e4348dfb3f8b5e34edbecb98da258e892089361"},
-    {file = "djangorestframework-3.16.0.tar.gz", hash = "sha256:f022ff46613584de994c0c6a4aebbace5fd700555fbe9d33b865ebf173eba6c9"},
-]
-
-[package.dependencies]
-django = ">=4.2"
-
 [[package]]
 name = "djlint"
 version = "1.36.4"
@@ -1291,14 +1282,14 @@ zstd = ["zstandard (>=0.18.0)"]
 
 [[package]]
 name = "httpx-sse"
-version = "0.4.0"
+version = "0.4.1"
 description = "Consume Server-Sent Event (SSE) messages with HTTPX."
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["main"]
 files = [
-    {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"},
-    {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"},
+    {file = "httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37"},
+    {file = "httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e"},
 ]
 
 [[package]]
@@ -1637,21 +1628,21 @@ langchain-core = ">=0.3.51,<1.0.0"
 
 [[package]]
 name = "langsmith"
-version = "0.4.1"
+version = "0.4.3"
 description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
 optional = false
 python-versions = ">=3.9"
 groups = ["main"]
 files = [
-    {file = "langsmith-0.4.1-py3-none-any.whl", hash = "sha256:19c4c40bbb6735cb1136c453b2edcde265ca5ba1b108b7e0e3583ec4bda28625"},
-    {file = "langsmith-0.4.1.tar.gz", hash = "sha256:ae8ec403fb2b9cabcfc3b0c54556d65555598c85879dac83b009576927f7eb1d"},
+    {file = "langsmith-0.4.3-py3-none-any.whl", hash = "sha256:b8ed57fb21fb3370bc7e4e8c4a3003017040336df694a66a34afe6f9872e68da"},
+    {file = "langsmith-0.4.3.tar.gz", hash = "sha256:151d8cbf3d26a49f67bd720462eae20d3282196958f86b59d1ac1aad484c52f1"},
 ]
 
 [package.dependencies]
 httpx = ">=0.23.0,<1"
 orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
 packaging = ">=23.2"
-pydantic = {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}
+pydantic = ">=1,<3"
 requests = ">=2,<3"
 requests-toolbelt = ">=1.0.0,<2.0.0"
 zstandard = ">=0.23.0,<0.24.0"
@@ -2647,14 +2638,14 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
 
 [[package]]
 name = "pydantic-settings"
-version = "2.10.0"
+version = "2.10.1"
 description = "Settings management using Pydantic"
 optional = false
 python-versions = ">=3.9"
 groups = ["main"]
 files = [
-    {file = "pydantic_settings-2.10.0-py3-none-any.whl", hash = "sha256:33781dfa1c7405d5ed2b6f150830a93bb58462a847357bd8f162f8bacb77c027"},
-    {file = "pydantic_settings-2.10.0.tar.gz", hash = "sha256:7a12e0767ba283954f3fd3fefdd0df3af21b28aa849c40c35811d52d682fa876"},
+    {file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"},
+    {file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"},
 ]
 
 [package.dependencies]
@@ -4011,4 +4002,4 @@ cffi = ["cffi (>=1.11)"]
 [metadata]
 lock-version = "2.1"
 python-versions = "^3.13"
-content-hash = "50c9bfb1cba43fb146a8818efcb20b79636efd34c8e86b93cca0b9a7603598d1"
+content-hash = "f9fb65bde2722d0de24cb1ac91d3d519efb2ec397ffd5e13ca7bd6a83239d724"
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 8c2a68c138..bd9311b1b7 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -26,10 +26,9 @@ algoliasearch-django = "^4.0.0"
 django = "^5.1"
 django-configurations = "^2.5.1"
 django-cors-headers = "^4.7.0"
-django-filter = "^25.1"
+django-ninja = "^1.4.3"
 django-redis = "^5.4.0"
 django-storages = { extras = ["s3"], version = "^1.14.4" }
-djangorestframework = "^3.15.2"
 emoji= "^2.14.1"
 geopy = "^2.4.1"
 gunicorn = "^23.0.0"
@@ -147,7 +146,7 @@ select = ["ALL"]
 [tool.ruff.lint.per-file-ignores]
 "**/__init__.py" = ["D104", "F401"]
 "**/admin.py" = ["D100", "D101", "D104"]
-"**/api/*.py" = ["D106"]
+"**/api/*.py" = ["ARG001", "D106"]
 "**/apps.py" = ["D100", "D101", "D104"]
 "**/graphql/**/nodes.py" = ["D106"]
 "**/graphql/nodes/*.py" = ["D106"]
diff --git a/backend/settings/api_v1.py b/backend/settings/api_v1.py
new file mode 100644
index 0000000000..b3679048f2
--- /dev/null
+++ b/backend/settings/api_v1.py
@@ -0,0 +1,20 @@
+"""OWASP Nest API v1 configuration."""
+
+from ninja import NinjaAPI
+from ninja.throttling import AnonRateThrottle, AuthRateThrottle
+
+from apps.github.api.v1.urls import router as github_router
+from apps.owasp.api.v1.urls import router as owasp_router
+
+api = NinjaAPI(
+    description="API for OWASP related entities",
+    title="OWASP Nest API",
+    version="1.0.0",
+    throttle=[
+        AnonRateThrottle("1/s"),
+        AuthRateThrottle("10/s"),
+    ],
+)
+
+api.add_router("github", github_router)
+api.add_router("owasp", owasp_router)
diff --git a/backend/settings/base.py b/backend/settings/base.py
index 1caf388bfa..8075d60870 100644
--- a/backend/settings/base.py
+++ b/backend/settings/base.py
@@ -38,7 +38,6 @@ class Base(Configuration):
     THIRD_PARTY_APPS = (
         "algoliasearch_django",
         "corsheaders",
-        "rest_framework",
         "storages",
     )
 
@@ -83,16 +82,6 @@ class Base(Configuration):
         "django.contrib.messages.middleware.MessageMiddleware",
     ]
 
-    REST_FRAMEWORK = {
-        # Use Django's standard `django.contrib.auth` permissions,
-        # or allow read-only access for unauthenticated users.
-        "DEFAULT_PERMISSION_CLASSES": [
-            "rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly"
-        ],
-        "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
-        "PAGE_SIZE": 100,
-    }
-
     ROOT_URLCONF = "settings.urls"
 
     TEMPLATES = [
diff --git a/backend/settings/urls.py b/backend/settings/urls.py
index c19f50a804..772e8ef046 100644
--- a/backend/settings/urls.py
+++ b/backend/settings/urls.py
@@ -7,27 +7,21 @@
 from django.conf import settings
 from django.conf.urls.static import static
 from django.contrib import admin
-from django.urls import include, path
+from django.urls import path
 from django.views.decorators.csrf import csrf_protect
-from rest_framework import routers
 from strawberry.django.views import GraphQLView
 
 from apps.core.api.algolia import algolia_search
 from apps.core.api.csrf import get_csrf_token
-from apps.github.api.urls import router as github_router
-from apps.owasp.api.urls import router as owasp_router
 from apps.slack.apps import SlackConfig
+from settings.api_v1 import api as api_v1
 from settings.graphql import schema
 
-router = routers.DefaultRouter()
-router.registry.extend(github_router.registry)
-router.registry.extend(owasp_router.registry)
-
 urlpatterns = [
     path("csrf/", get_csrf_token),
     path("idx/", csrf_protect(algolia_search)),
     path("graphql/", csrf_protect(GraphQLView.as_view(schema=schema, graphiql=settings.DEBUG))),
-    path("api/v1/", include(router.urls)),
+    path("api/v1/", api_v1.urls),
     path("a/", admin.site.urls),
 ]
 
diff --git a/backend/tests/apps/github/api/issue_test.py b/backend/tests/apps/github/api/issue_test.py
index cf45f9199e..85b87951fc 100644
--- a/backend/tests/apps/github/api/issue_test.py
+++ b/backend/tests/apps/github/api/issue_test.py
@@ -1,9 +1,11 @@
+from datetime import datetime
+
 import pytest
 
-from apps.github.api.issue import IssueSerializer
+from apps.github.api.v1.issue import IssueSchema
 
 
-class TestIssueSerializer:
+class TestIssueSchema:
     @pytest.mark.parametrize(
         "issue_data",
         [
@@ -25,14 +27,13 @@ class TestIssueSerializer:
             },
         ],
     )
-    def test_issue_serializer(self, issue_data):
-        serializer = IssueSerializer(data=issue_data)
-        assert serializer.is_valid()
-        validated_data = serializer.validated_data
-        validated_data["created_at"] = (
-            validated_data["created_at"].isoformat().replace("+00:00", "Z")
-        )
-        validated_data["updated_at"] = (
-            validated_data["updated_at"].isoformat().replace("+00:00", "Z")
-        )
-        assert validated_data == issue_data
+    def test_issue_schema(self, issue_data):
+        schema = IssueSchema(**issue_data)
+
+        assert schema.title == issue_data["title"]
+        assert schema.body == issue_data["body"]
+        assert schema.state == issue_data["state"]
+        assert schema.url == issue_data["url"]
+
+        assert schema.created_at == datetime.fromisoformat(issue_data["created_at"])
+        assert schema.updated_at == datetime.fromisoformat(issue_data["updated_at"])
diff --git a/backend/tests/apps/github/api/label_test.py b/backend/tests/apps/github/api/label_test.py
index 1ba6bd045d..8266834ab5 100644
--- a/backend/tests/apps/github/api/label_test.py
+++ b/backend/tests/apps/github/api/label_test.py
@@ -1,28 +1,26 @@
 import pytest
 
-from apps.github.api.label import LabelSerializer
+from apps.github.api.v1.label import LabelSchema
 
 
-class TestLabelSerializer:
+class TestLabelSchema:
     @pytest.mark.parametrize(
         "label_data",
         [
             {
-                "name": "bug",
-                "description": "Indicates a bug in the project",
                 "color": "f29513",
+                "description": "Indicates a bug in the project",
+                "name": "bug",
             },
             {
-                "name": "enhancement",
-                "description": "Indicates a new feature or enhancement",
                 "color": "a2eeef",
+                "description": "Indicates a new feature or enhancement",
+                "name": "enhancement",
             },
         ],
     )
-    def test_label_serializer(self, label_data):
-        serializer = LabelSerializer(data=label_data)
-        assert serializer.is_valid(), serializer.errors
-        validated_data = serializer.validated_data
-        assert validated_data["name"] == label_data["name"]
-        assert validated_data["description"] == label_data["description"]
-        assert validated_data["color"] == label_data["color"]
+    def test_label_schema(self, label_data):
+        label = LabelSchema(**label_data)
+        assert label.name == label_data["name"]
+        assert label.description == label_data["description"]
+        assert label.color == label_data["color"]
diff --git a/backend/tests/apps/github/api/organization_test.py b/backend/tests/apps/github/api/organization_test.py
index 07775ad7a9..95f57edc32 100644
--- a/backend/tests/apps/github/api/organization_test.py
+++ b/backend/tests/apps/github/api/organization_test.py
@@ -1,12 +1,11 @@
-from unittest.mock import MagicMock, patch
+from datetime import datetime
 
 import pytest
 
-from apps.github.api.organization import OrganizationSerializer
-from apps.github.models.organization import Organization
+from apps.github.api.v1.organization import OrganizationSchema
 
 
-class TestOrganizationSerializer:
+class TestOrganizationSchema:
     @pytest.mark.parametrize(
         "organization_data",
         [
@@ -28,27 +27,12 @@ class TestOrganizationSerializer:
             },
         ],
     )
-    # Ensures that test runs without actual database access by simulating behavior of a queryset.
-    @patch("apps.github.models.organization.Organization.objects.filter")
-    def test_organization_serializer(self, mock_filter, organization_data):
-        mock_qs = MagicMock()
-        # To mimic a queryset where no matching objects are found.
-        mock_qs.exists.return_value = False
-        mock_filter.return_value = mock_qs
+    def test_organization_schema(self, organization_data):
+        schema = OrganizationSchema(**organization_data)
+        assert schema.created_at == datetime.fromisoformat(organization_data["created_at"])
+        assert schema.updated_at == datetime.fromisoformat(organization_data["updated_at"])
 
-        serializer = OrganizationSerializer(data=organization_data)
-        assert serializer.is_valid()
-        validated_data = serializer.validated_data
-        validated_data["created_at"] = (
-            validated_data["created_at"].isoformat().replace("+00:00", "Z")
-        )
-        validated_data["updated_at"] = (
-            validated_data["updated_at"].isoformat().replace("+00:00", "Z")
-        )
-        assert validated_data == organization_data
-
-    @patch("apps.github.models.organization.Organization.objects.values_list")
-    def test_get_logins(self, mock_values_list):
-        mock_values_list.return_value = ["github", "microsoft"]
-        assert Organization.get_logins() == {"github", "microsoft"}
-        mock_values_list.assert_called_once_with("login", flat=True)
+        assert schema.name == organization_data["name"]
+        assert schema.login == organization_data["login"]
+        assert schema.company == organization_data["company"]
+        assert schema.location == organization_data["location"]
diff --git a/backend/tests/apps/github/api/release_test.py b/backend/tests/apps/github/api/release_test.py
index 30913dc26b..96ef265cf2 100644
--- a/backend/tests/apps/github/api/release_test.py
+++ b/backend/tests/apps/github/api/release_test.py
@@ -1,9 +1,11 @@
+from datetime import datetime
+
 import pytest
 
-from apps.github.api.release import ReleaseSerializer
+from apps.github.api.v1.release import ReleaseSchema
 
 
-class TestReleaseSerializer:
+class TestReleaseSchema:
     @pytest.mark.parametrize(
         "release_data",
         [
@@ -23,15 +25,11 @@ class TestReleaseSerializer:
             },
         ],
     )
-    def test_release_serializer(self, release_data):
-        serializer = ReleaseSerializer(data=release_data)
-        assert serializer.is_valid()
-        validated_data = serializer.validated_data
+    def test_release_schema(self, release_data):
+        schema = ReleaseSchema(**release_data)
+        assert schema.created_at == datetime.fromisoformat(release_data["created_at"])
+        assert schema.published_at == datetime.fromisoformat(release_data["published_at"])
 
-        validated_data["created_at"] = (
-            validated_data["created_at"].isoformat().replace("+00:00", "Z")
-        )
-        validated_data["published_at"] = (
-            validated_data["published_at"].isoformat().replace("+00:00", "Z")
-        )
-        assert validated_data == release_data
+        assert schema.name == release_data["name"]
+        assert schema.tag_name == release_data["tag_name"]
+        assert schema.description == release_data["description"]
diff --git a/backend/tests/apps/github/api/repository_test.py b/backend/tests/apps/github/api/repository_test.py
index 0f4a34896f..3b384b8373 100644
--- a/backend/tests/apps/github/api/repository_test.py
+++ b/backend/tests/apps/github/api/repository_test.py
@@ -1,9 +1,11 @@
+from datetime import datetime
+
 import pytest
 
-from apps.github.api.repository import RepositorySerializer
+from apps.github.api.v1.repository import RepositorySchema
 
 
-class TestRepositorySerializer:
+class TestRepositorySchema:
     @pytest.mark.parametrize(
         "repository_data",
         [
@@ -21,15 +23,10 @@ class TestRepositorySerializer:
             },
         ],
     )
-    def test_repository_serializer(self, repository_data):
-        serializer = RepositorySerializer(data=repository_data)
-        assert serializer.is_valid()
-        validated_data = serializer.validated_data
+    def test_repository_schema(self, repository_data):
+        repository = RepositorySchema(**repository_data)
 
-        validated_data["created_at"] = (
-            validated_data["created_at"].isoformat().replace("+00:00", "Z")
-        )
-        validated_data["updated_at"] = (
-            validated_data["updated_at"].isoformat().replace("+00:00", "Z")
-        )
-        assert validated_data == repository_data
+        assert repository.name == repository_data["name"]
+        assert repository.description == repository_data["description"]
+        assert repository.created_at == datetime.fromisoformat(repository_data["created_at"])
+        assert repository.updated_at == datetime.fromisoformat(repository_data["updated_at"])
diff --git a/backend/tests/apps/github/api/urls_test.py b/backend/tests/apps/github/api/urls_test.py
index ddf44b8b49..614227a1d0 100644
--- a/backend/tests/apps/github/api/urls_test.py
+++ b/backend/tests/apps/github/api/urls_test.py
@@ -1,36 +1,38 @@
 import pytest
 
-from apps.github.api.issue import IssueViewSet
-from apps.github.api.label import LabelViewSet
-from apps.github.api.organization import OrganizationViewSet
-from apps.github.api.release import ReleaseViewSet
-from apps.github.api.repository import RepositoryViewSet
-from apps.github.api.urls import router
-from apps.github.api.user import UserViewSet
+from apps.github.api.v1.issue import router as issue_router
+from apps.github.api.v1.label import router as label_router
+from apps.github.api.v1.organization import router as organization_router
+from apps.github.api.v1.release import router as release_router
+from apps.github.api.v1.repository import router as repository_router
+from apps.github.api.v1.urls import router as main_router
+from apps.github.api.v1.user import router as user_router
 
 
 class TestRouterRegistration:
+    """Test the urls registration."""
+
+    EXPECTED_ROUTERS = {
+        "/issues": issue_router,
+        "/labels": label_router,
+        "/organizations": organization_router,
+        "/releases": release_router,
+        "/repositories": repository_router,
+        "/users": user_router,
+    }
+
+    def test_all_routers_are_registered(self):
+        """Verifies that the main router has the correct number of registered sub-routers."""
+        registered_sub_routers = main_router._routers
+        assert len(registered_sub_routers) == len(self.EXPECTED_ROUTERS)
+
     @pytest.mark.parametrize(
-        ("url_name", "viewset_class", "expected_prefix"),
-        [
-            ("issue-list", IssueViewSet, "github/issues"),
-            ("label-list", LabelViewSet, "github/labels"),
-            ("organization-list", OrganizationViewSet, "github/organizations"),
-            ("release-list", ReleaseViewSet, "github/releases"),
-            ("repository-list", RepositoryViewSet, "github/repositories"),
-            ("user-list", UserViewSet, "github/users"),
-        ],
+        ("prefix", "expected_router_instance"), list(EXPECTED_ROUTERS.items())
     )
-    def test_router_registration(self, url_name, expected_prefix, viewset_class):
-        matching_routes = [route for route in router.urls if route.name == url_name]
-        assert matching_routes, f"Route '{url_name}' not found in router."
-
-        for route in matching_routes:
-            assert expected_prefix in route.pattern.describe(), (
-                f"Prefix '{expected_prefix}' not found in route '{route.name}'."
-            )
+    def test_sub_router_registration(self, prefix, expected_router_instance):
+        """Tests that each specific router is registered with the correct prefix."""
+        registered_router_map = dict(main_router._routers)
 
-            viewset = route.callback.cls
-            assert issubclass(viewset, viewset_class), (
-                f"Viewset for '{route.name}' does not match {viewset_class}."
-            )
+        assert prefix in registered_router_map
+        actual_router = registered_router_map[prefix]
+        assert actual_router is expected_router_instance
diff --git a/backend/tests/apps/github/api/user_test.py b/backend/tests/apps/github/api/user_test.py
index dd0fb3efb9..bcdfc6a4c9 100644
--- a/backend/tests/apps/github/api/user_test.py
+++ b/backend/tests/apps/github/api/user_test.py
@@ -1,12 +1,11 @@
-from unittest.mock import MagicMock, patch
+from datetime import datetime
 
 import pytest
 
-from apps.github.api.user import UserSerializer
-from apps.github.models.user import User
+from apps.github.api.v1.user import UserSchema
 
 
-class TestUserSerializer:
+class TestUserSchema:
     @pytest.mark.parametrize(
         "user_data",
         [
@@ -15,49 +14,35 @@ class TestUserSerializer:
                 "login": "johndoe",
                 "company": "GitHub",
                 "location": "San Francisco",
+                "avatar_url": "https://github.com/images/johndoe.png",
+                "bio": "Developer advocate",
+                "email": "john@example.com",
+                "followers_count": 10,
+                "following_count": 5,
+                "public_repositories_count": 3,
+                "title": "Senior Engineer",
+                "twitter_username": "johndoe",
+                "url": "https://github.com/johndoe",
                 "created_at": "2024-12-30T00:00:00Z",
                 "updated_at": "2024-12-30T00:00:00Z",
             },
-            {
-                "name": "Jane Smith",
-                "login": "jane-smith",
-                "company": "Microsoft",
-                "location": "Redmond",
-                "created_at": "2024-12-29T00:00:00Z",
-                "updated_at": "2024-12-30T00:00:00Z",
-            },
         ],
     )
-    # Ensures that test runs without actual database access by simulating behavior of a queryset.
-    @patch("apps.github.models.user.User.objects.filter")
-    def test_user_serializer(self, mock_filter, user_data):
-        mock_qs = MagicMock()
-        # To mimic a queryset where no matching objects are found.
-        mock_qs.exists.return_value = False
-        mock_filter.return_value = mock_qs
-
-        serializer = UserSerializer(data=user_data)
-        assert serializer.is_valid()
-        validated_data = serializer.validated_data
-
-        validated_data["created_at"] = (
-            validated_data["created_at"].isoformat().replace("+00:00", "Z")
-        )
-        validated_data["updated_at"] = (
-            validated_data["updated_at"].isoformat().replace("+00:00", "Z")
-        )
-        assert validated_data == user_data
+    def test_user_schema(self, user_data):
+        user = UserSchema(**user_data)
 
-    @pytest.mark.parametrize(
-        ("login", "organization_logins", "expected_result"),
-        [
-            ("johndoe", ["github", "microsoft"], True),  # Normal user
-            ("github", ["github", "microsoft"], False),  # Organization login
-            ("ghost", ["github", "microsoft"], False),  # Special 'ghost' user
-        ],
-    )
-    @patch("apps.github.models.organization.Organization.get_logins")
-    def test_is_indexable(self, mock_get_logins, login, organization_logins, expected_result):
-        mock_get_logins.return_value = organization_logins
-        user = User(login=login)
-        assert user.is_indexable == expected_result
+        assert user.name == user_data["name"]
+        assert user.login == user_data["login"]
+        assert user.company == user_data["company"]
+        assert user.location == user_data["location"]
+        assert user.avatar_url == user_data["avatar_url"]
+        assert user.bio == user_data["bio"]
+        assert user.email == user_data["email"]
+        assert user.followers_count == user_data["followers_count"]
+        assert user.following_count == user_data["following_count"]
+        assert user.public_repositories_count == user_data["public_repositories_count"]
+        assert user.title == user_data["title"]
+        assert user.twitter_username == user_data["twitter_username"]
+        assert user.url == user_data["url"]
+        assert user.created_at == datetime.fromisoformat(user_data["created_at"])
+        assert user.updated_at == datetime.fromisoformat(user_data["updated_at"])
diff --git a/backend/tests/apps/owasp/api/chapter_test.py b/backend/tests/apps/owasp/api/chapter_test.py
deleted file mode 100644
index 8a0d218ae3..0000000000
--- a/backend/tests/apps/owasp/api/chapter_test.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import pytest
-
-from apps.owasp.api.chapter import ChapterSerializer
-
-
-@pytest.mark.parametrize(
-    ("data", "expected"),
-    [
-        (
-            {
-                "name": "OWASP Nagoya",
-                "description": "A test chapter",
-                "level": "other",
-                "created_at": "2024-11-01T00:00:00Z",
-                "updated_at": "2024-07-02T00:00:00Z",
-            },
-            True,
-        ),
-        (
-            {
-                "name": "OWASP something",
-                "description": "it is description",
-                "level": "github",
-                "created_at": "2023-12-01T00:00:00Z",
-                "updated_at": "2023-09-02T00:00:00Z",
-            },
-            True,
-        ),
-    ],
-)
-def test_chapter_serializer_validation(data, expected):
-    serializer = ChapterSerializer(data=data)
-    is_valid = serializer.is_valid()
-
-    assert is_valid == expected
diff --git a/backend/tests/apps/owasp/api/committee_test.py b/backend/tests/apps/owasp/api/committee_test.py
deleted file mode 100644
index c6101da9c2..0000000000
--- a/backend/tests/apps/owasp/api/committee_test.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import pytest
-
-from apps.owasp.api.committee import CommitteeSerializer
-
-
-@pytest.mark.parametrize(
-    ("data", "expected"),
-    [
-        (
-            {
-                "name": "Test Project",
-                "description": "A test project",
-                "created_at": "2024-11-01T00:00:00Z",
-                "updated_at": "2024-07-02T00:00:00Z",
-            },
-            True,
-        ),
-        (
-            {
-                "name": "this is a project",
-                "description": "A project without a name",
-                "created_at": "2023-12-01T00:00:00Z",
-                "updated_at": "2023-09-02T00:00:00Z",
-            },
-            True,
-        ),
-    ],
-)
-def test_committee_serializer_validation(data, expected):
-    serializer = CommitteeSerializer(data=data)
-    is_valid = serializer.is_valid()
-
-    assert is_valid == expected
diff --git a/backend/tests/apps/owasp/api/event_test.py b/backend/tests/apps/owasp/api/event_test.py
deleted file mode 100644
index f2a6adb36c..0000000000
--- a/backend/tests/apps/owasp/api/event_test.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import pytest
-
-from apps.owasp.api.event import EventSerializer
-
-
-@pytest.mark.parametrize(
-    ("data", "expected"),
-    [
-        (
-            {
-                "name": "Test Event",
-                "description": "A test event",
-                "url": "https://github.com/owasp/Nest",
-            },
-            True,
-        ),
-        (
-            {
-                "name": "biggest event",
-                "description": "this is a biggest event",
-                "url": "https://github.com/owasp",
-            },
-            True,
-        ),
-    ],
-)
-def test_event_serializer_validation(data, expected):
-    serializer = EventSerializer(data=data)
-    is_valid = serializer.is_valid()
-
-    assert is_valid == expected
diff --git a/backend/tests/apps/owasp/api/project_test.py b/backend/tests/apps/owasp/api/project_test.py
deleted file mode 100644
index 6da788bc5b..0000000000
--- a/backend/tests/apps/owasp/api/project_test.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import pytest
-
-from apps.owasp.api.project import ProjectSerializer
-
-
-@pytest.mark.parametrize(
-    ("data", "expected"),
-    [
-        (
-            {
-                "name": "another project",
-                "description": "A test project by owasp",
-                "level": "other",
-                "created_at": "2023-01-01T00:00:00Z",
-                "updated_at": "2023-01-02T00:00:00Z",
-            },
-            True,
-        ),
-        (
-            {
-                "name": "this is a project",
-                "description": "this is not a project, this is just a file",
-                "level": "Hello",
-                "created_at": "2023-01-01T00:00:00Z",
-                "updated_at": "2023-01-02T00:00:00Z",
-            },
-            False,
-        ),
-    ],
-)
-def test_project_serializer_validation(data, expected):
-    serializer = ProjectSerializer(data=data)
-    is_valid = serializer.is_valid()
-
-    assert is_valid == expected
diff --git a/backend/tests/apps/owasp/api/urls_test.py b/backend/tests/apps/owasp/api/urls_test.py
deleted file mode 100644
index be8d9e7e83..0000000000
--- a/backend/tests/apps/owasp/api/urls_test.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import pytest
-
-from apps.owasp.api.chapter import ChapterViewSet
-from apps.owasp.api.committee import CommitteeViewSet
-from apps.owasp.api.event import EventViewSet
-from apps.owasp.api.project import ProjectViewSet
-from apps.owasp.api.urls import router
-
-
-@pytest.mark.parametrize(
-    ("url_name", "expected_prefix", "viewset_class"),
-    [
-        ("chapter-list", "owasp/chapters", ChapterViewSet),
-        ("committee-list", "owasp/committees", CommitteeViewSet),
-        ("event-list", "owasp/events", EventViewSet),
-        ("project-list", "owasp/projects", ProjectViewSet),
-    ],
-)
-def test_router_registration(url_name, expected_prefix, viewset_class):
-    matching_routes = [route for route in router.urls if route.name == url_name]
-    assert matching_routes, f"Route '{url_name}' not found in router."
-
-    for route in matching_routes:
-        assert expected_prefix in route.pattern.describe(), (
-            f"Prefix '{expected_prefix}' not found in route '{route.name}'."
-        )
-
-        viewset = route.callback.cls
-        assert issubclass(viewset, viewset_class), (
-            f"Viewset for '{route.name}' does not match {viewset_class}."
-        )
diff --git a/backend/tests/apps/owasp/api/v1/__init__.py b/backend/tests/apps/owasp/api/v1/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/backend/tests/apps/owasp/api/v1/chapter_test.py b/backend/tests/apps/owasp/api/v1/chapter_test.py
new file mode 100644
index 0000000000..eb6a394d20
--- /dev/null
+++ b/backend/tests/apps/owasp/api/v1/chapter_test.py
@@ -0,0 +1,33 @@
+from datetime import datetime
+
+import pytest
+
+from apps.owasp.api.v1.chapter import ChapterSchema
+
+
+@pytest.mark.parametrize(
+    "chapter_data",
+    [
+        {
+            "name": "OWASP Nagoya",
+            "country": "America",
+            "region": "Europe",
+            "created_at": "2024-11-01T00:00:00Z",
+            "updated_at": "2024-07-02T00:00:00Z",
+        },
+        {
+            "name": "OWASP something",
+            "country": "India",
+            "region": "Asia",
+            "created_at": "2023-12-01T00:00:00Z",
+            "updated_at": "2023-09-02T00:00:00Z",
+        },
+    ],
+)
+def test_chapter_serializer_validation(chapter_data):
+    chapter = ChapterSchema(**chapter_data)
+    assert chapter.name == chapter_data["name"]
+    assert chapter.country == chapter_data["country"]
+    assert chapter.region == chapter_data["region"]
+    assert chapter.created_at == datetime.fromisoformat(chapter_data["created_at"])
+    assert chapter.updated_at == datetime.fromisoformat(chapter_data["updated_at"])
diff --git a/backend/tests/apps/owasp/api/v1/committee_test.py b/backend/tests/apps/owasp/api/v1/committee_test.py
new file mode 100644
index 0000000000..a6216931d8
--- /dev/null
+++ b/backend/tests/apps/owasp/api/v1/committee_test.py
@@ -0,0 +1,30 @@
+from datetime import datetime
+
+import pytest
+
+from apps.owasp.api.v1.committee import CommitteeSchema
+
+
+@pytest.mark.parametrize(
+    "committee_data",
+    [
+        {
+            "name": "Test Committee",
+            "description": "A test committee",
+            "created_at": "2024-11-01T00:00:00Z",
+            "updated_at": "2024-07-02T00:00:00Z",
+        },
+        {
+            "name": "this is a committee",
+            "description": "A committee without a name",
+            "created_at": "2023-12-01T00:00:00Z",
+            "updated_at": "2023-09-02T00:00:00Z",
+        },
+    ],
+)
+def test_committee_serializer_validation(committee_data):
+    committee = CommitteeSchema(**committee_data)
+    assert committee.name == committee_data["name"]
+    assert committee.description == committee_data["description"]
+    assert committee.created_at == datetime.fromisoformat(committee_data["created_at"])
+    assert committee.updated_at == datetime.fromisoformat(committee_data["updated_at"])
diff --git a/backend/tests/apps/owasp/api/v1/event_test.py b/backend/tests/apps/owasp/api/v1/event_test.py
new file mode 100644
index 0000000000..d12d29411a
--- /dev/null
+++ b/backend/tests/apps/owasp/api/v1/event_test.py
@@ -0,0 +1,25 @@
+import pytest
+
+from apps.owasp.api.v1.event import EventSchema
+
+
+@pytest.mark.parametrize(
+    "event_data",
+    [
+        {
+            "name": "Test Event",
+            "description": "A test event",
+            "url": "https://github.com/owasp/Nest",
+        },
+        {
+            "name": "biggest event",
+            "description": "this is a biggest event",
+            "url": "https://github.com/owasp",
+        },
+    ],
+)
+def test_event_serializer_validation(event_data):
+    event = EventSchema(**event_data)
+    assert event.name == event_data["name"]
+    assert event.description == event_data["description"]
+    assert event.url == event_data["url"]
diff --git a/backend/tests/apps/owasp/api/v1/project_test.py b/backend/tests/apps/owasp/api/v1/project_test.py
new file mode 100644
index 0000000000..0bb8ed969e
--- /dev/null
+++ b/backend/tests/apps/owasp/api/v1/project_test.py
@@ -0,0 +1,33 @@
+from datetime import datetime
+
+import pytest
+
+from apps.owasp.api.v1.project import ProjectSchema
+
+
+@pytest.mark.parametrize(
+    "project_data",
+    [
+        {
+            "name": "another project",
+            "description": "A test project by owasp",
+            "level": "other",
+            "created_at": "2023-01-01T00:00:00Z",
+            "updated_at": "2023-01-02T00:00:00Z",
+        },
+        {
+            "name": "this is a project",
+            "description": "this is not a project, this is just a file",
+            "level": "Hello",
+            "created_at": "2023-01-01T00:00:00Z",
+            "updated_at": "2023-01-02T00:00:00Z",
+        },
+    ],
+)
+def test_project_serializer_validation(project_data):
+    project = ProjectSchema(**project_data)
+    assert project.name == project_data["name"]
+    assert project.description == project_data["description"]
+    assert project.level == project_data["level"]
+    assert project.created_at == datetime.fromisoformat(project_data["created_at"])
+    assert project.updated_at == datetime.fromisoformat(project_data["updated_at"])
diff --git a/backend/tests/apps/owasp/api/v1/urls_test.py b/backend/tests/apps/owasp/api/v1/urls_test.py
new file mode 100644
index 0000000000..da2a258d5b
--- /dev/null
+++ b/backend/tests/apps/owasp/api/v1/urls_test.py
@@ -0,0 +1,35 @@
+import pytest
+
+from apps.owasp.api.v1.chapter import router as chapter_router
+from apps.owasp.api.v1.committee import router as committee_router
+from apps.owasp.api.v1.event import router as event_router
+from apps.owasp.api.v1.project import router as project_router
+from apps.owasp.api.v1.urls import router as main_router
+
+
+class TestRouterRegistration:
+    """Test the urls registration."""
+
+    EXPECTED_ROUTERS = {
+        "/chapters": chapter_router,
+        "/committees": committee_router,
+        "/events": event_router,
+        "/projects": project_router,
+    }
+
+    def test_all_routers_are_registered(self):
+        """Verifies that the main router has the correct number of registered sub-routers."""
+        registered_sub_routers = main_router._routers
+        assert len(registered_sub_routers) == len(self.EXPECTED_ROUTERS)
+
+    @pytest.mark.parametrize(
+        ("prefix", "expected_router_instance"), list(EXPECTED_ROUTERS.items())
+    )
+    def test_sub_router_registration(self, prefix, expected_router_instance):
+        """Tests that each specific router is registered with the correct prefix."""
+        registered_router_map = dict(main_router._routers)
+
+        assert prefix in registered_router_map
+        actual_router = registered_router_map[prefix]
+
+        assert actual_router is expected_router_instance
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index de9a905b04..8047940c4f 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -3537,8 +3537,8 @@ packages:
   dompurify@3.2.6:
     resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
 
-  dotenv@16.6.0:
-    resolution: {integrity: sha512-Omf1L8paOy2VJhILjyhrhqwLIdstqm1BvcDPKg4NGAlkwEu9ODyrFbvk8UymUOMCT+HXo31jg1lArIrVAAhuGA==}
+  dotenv@16.6.1:
+    resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
     engines: {node: '>=12'}
 
   dunder-proto@1.0.1:
@@ -9079,7 +9079,7 @@ snapshots:
       '@babel/core': 7.27.7
       '@sentry/babel-plugin-component-annotate': 3.5.0
       '@sentry/cli': 2.42.2
-      dotenv: 16.6.0
+      dotenv: 16.6.1
       find-up: 5.0.0
       glob: 9.3.5
       magic-string: 0.30.8
@@ -10372,7 +10372,7 @@ snapshots:
     optionalDependencies:
       '@types/trusted-types': 2.0.7
 
-  dotenv@16.6.0: {}
+  dotenv@16.6.1: {}
 
   dunder-proto@1.0.1:
     dependencies: