diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index 31d1be3df8d545c7c8e7ac658e545e6dc970dfd8..ac68c91111ef9ffac50d45dee605fd68209aca68 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -139,9 +139,11 @@ flask_api = Api()
 def init_api():
 
     from .auth import blp as auth_blp
+    from .competitions import blp as competitions_blp
     from .misc import blp as misc_blp
     from .users import blp as user_blp
 
     flask_api.register_blueprint(user_blp)
     flask_api.register_blueprint(auth_blp)
+    flask_api.register_blueprint(competitions_blp)
     flask_api.register_blueprint(misc_blp)
diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
index 2d381425c9e5d3f41bba347128aab7b659bf8885..e28c000b345120accb38a8889651347003a227aa 100644
--- a/server/app/apis/competitions.py
+++ b/server/app/apis/competitions.py
@@ -4,101 +4,103 @@ Default route: /api/competitions
 """
 
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import CompetitionDTO
-from app.core.parsers import search_parser, sentinel
+from app.apis import http_codes, protect_route
+from app.core import ma
+from app.core.rich_schemas import CompetitionSchemaRich
+from app.core.schemas import BaseSchema, CompetitionSchema
+from app.database import models
 from app.database.models import Competition
-from flask_restx import Resource, reqparse
+from flask.views import MethodView
+from flask_smorest import Blueprint, arguments
 
-api = CompetitionDTO.api
-schema = CompetitionDTO.schema
-rich_schema = CompetitionDTO.rich_schema
-list_schema = CompetitionDTO.list_schema
+blp = Blueprint("competitions", "competitions", url_prefix="/api/competitions", description="Operations competitions")
 
-competition_parser_add = reqparse.RequestParser()
-competition_parser_add.add_argument("name", type=str, required=True, location="json")
-competition_parser_add.add_argument("year", type=int, required=True, location="json")
-competition_parser_add.add_argument("city_id", type=int, required=True, location="json")
 
-competition_parser_edit = reqparse.RequestParser()
-competition_parser_edit.add_argument("name", type=str, default=sentinel, location="json")
-competition_parser_edit.add_argument("year", type=int, default=sentinel, location="json")
-competition_parser_edit.add_argument("city_id", type=int, default=sentinel, location="json")
-competition_parser_edit.add_argument("background_image_id", default=sentinel, type=int, location="json")
+class CompetitionAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Competition
 
-competition_parser_search = search_parser.copy()
-competition_parser_search.add_argument("name", type=str, default=sentinel, location="args")
-competition_parser_search.add_argument("year", type=int, default=sentinel, location="args")
-competition_parser_search.add_argument("city_id", type=int, default=sentinel, location="args")
+    name = ma.auto_field()
+    year = ma.auto_field()
+    city_id = ma.auto_field()
 
 
-@api.route("")
-class CompetitionsList(Resource):
-    @protect_route(allowed_roles=["*"])
-    def post(self):
-        """ Posts a new competition. """
+class CompetitionEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Competition
+
+    name = ma.auto_field(required=False)
+    year = ma.auto_field(required=False)
+    city_id = ma.auto_field(required=False)
+    background_image_id = ma.auto_field(required=False)
+
+
+class CompetitionSearchArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Competition
 
-        args = competition_parser_add.parse_args(strict=True)
+    name = ma.auto_field(required=False)
+    year = ma.auto_field(required=False)
+    city_id = ma.auto_field(required=False)
+    background_image_id = ma.auto_field(required=False)
 
-        # Add competition
-        item = dbc.add.competition(**args)
 
-        # Add default slide
-        # dbc.add.slide(item.id)
-        return item_response(schema.dump(item))
+@blp.route("")
+class CompetitionsList(MethodView):
+    @protect_route(allowed_roles=["*"])
+    @blp.arguments(CompetitionAddArgsSchema)
+    @blp.response(http_codes.OK, CompetitionSchema)
+    @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be added")
+    def post(self, args):
+        """ Adds a new competition. """
+        return dbc.add.competition(**args)
 
 
-@api.route("/<competition_id>")
-@api.param("competition_id")
-class Competitions(Resource):
+@blp.route("/<competition_id>")
+class Competitions(MethodView):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
+    @blp.response(http_codes.OK, CompetitionSchemaRich)
+    @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found")
     def get(self, competition_id):
         """ Gets the specified competition. """
-
-        item = dbc.get.competition(competition_id)
-
-        return item_response(rich_schema.dump(item))
+        return dbc.get.competition(competition_id)
 
     @protect_route(allowed_roles=["*"])
-    def put(self, competition_id):
+    @blp.arguments(CompetitionEditArgsSchema)
+    @blp.response(http_codes.OK, CompetitionSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found")
+    @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be updated")
+    def put(self, args, competition_id):
         """ Edits the specified competition with the specified arguments. """
-
-        args = competition_parser_edit.parse_args(strict=True)
-        item = dbc.get.one(Competition, competition_id)
-        item = dbc.edit.default(item, **args)
-
-        return item_response(schema.dump(item))
+        return dbc.edit.default(dbc.get.one(Competition, competition_id), **args)
 
     @protect_route(allowed_roles=["*"])
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found")
+    @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be deleted")
     def delete(self, competition_id):
         """ Deletes the specified competition. """
+        dbc.delete.competition(dbc.get.one(Competition, competition_id))
+        return None
 
-        item = dbc.get.one(Competition, competition_id)
-        dbc.delete.competition(item)
-
-        return "deleted"
 
-
-@api.route("/search")
-class CompetitionSearch(Resource):
+@blp.route("/search")
+class CompetitionSearch(MethodView):
     @protect_route(allowed_roles=["*"])
-    def get(self):
+    @blp.arguments(CompetitionSearchArgsSchema, location="query")
+    @blp.paginate()
+    @blp.response(http_codes.OK, CompetitionSchema(many=True))
+    def get(self, args, pagination_parameters):
         """ Finds a specific competition based on the provided arguments. """
-
-        args = competition_parser_search.parse_args(strict=True)
-        items, total = dbc.search.competition(**args)
-        return list_response(list_schema.dump(items), total)
+        return dbc.search.competition(pagination_parameters, **args)
 
 
-@api.route("/<competition_id>/copy")
-@api.param("competition_id")
-class SlidesOrder(Resource):
+@blp.route("/<competition_id>/copy")
+class SlidesOrder(MethodView):
     @protect_route(allowed_roles=["*"])
+    @blp.response(http_codes.OK, CompetitionSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found")
+    @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be copied")
     def post(self, competition_id):
         """ Creates a deep copy of the specified competition. """
-
-        item_competition = dbc.get.competition(competition_id)
-
-        item_competition_copy = dbc.copy.competition(item_competition)
-
-        return item_response(schema.dump(item_competition_copy))
+        return dbc.copy.competition(dbc.get.competition(competition_id))
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
index a7b90e44d99f75762614838b2a9192e79c903e5a..4856e46ca74160cd65a91abe31290750804dd527 100644
--- a/server/app/apis/users.py
+++ b/server/app/apis/users.py
@@ -37,8 +37,8 @@ class UserEditArgsSchema(BaseSchema):
     class Meta(BaseSchema.Meta):
         model = models.User
 
-    name = ma.auto_field()
-    email = ma.auto_field()
+    name = ma.auto_field(required=False)
+    email = ma.auto_field(required=False)
     role_id = ma.auto_field(required=False)
     city_id = ma.auto_field(required=False)
 
diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py
index 594894480efea039c5cdf0c544e964ad4d554b3f..b3b99a5ca1eaccffd4c4860504a3f719051c3aaa 100644
--- a/server/app/database/__init__.py
+++ b/server/app/database/__init__.py
@@ -46,7 +46,7 @@ class ExtendedQuery(BaseQuery):
         item = self.first()
 
         if required and not item:
-            abort(error_code, error_message or "Object not found")
+            abort(error_code, message=error_message or "Objektet hittades inte")
 
         return item
 
diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py
index d64d8a5bb43b91e3eff9935df74ed6d3a48901fe..f16ce3feb54b300aed46e8575cb1a78d8c82a967 100644
--- a/server/app/database/controller/copy.py
+++ b/server/app/database/controller/copy.py
@@ -3,7 +3,7 @@ This file contains functionality to copy and duplicate data to the database.
 """
 
 from app.database.controller import add, get, search, utils
-from app.database.models import Question
+from app.database.models import Competition, Question
 from app.database.types import IMAGE_COMPONENT_ID, QUESTION_COMPONENT_ID, TEXT_COMPONENT_ID
 
 
@@ -115,9 +115,9 @@ def competition(item_competition_old):
     """
 
     name = "Kopia av " + item_competition_old.name
-    item_competition, total = search.competition(name=name)
-    if item_competition:
-        name = "Kopia av " + item_competition[total - 1].name
+
+    while item_competition := Competition.query.filter(Competition.name == name).first():
+        name = "Kopia av " + item_competition.name
 
     item_competition_new = add._competition_no_slides(
         name,
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index e620b0593d7687e132c8e01f07ab9c42601dd19f..536a80a3ba761c23d74d5a105fb23bdd74add0b3 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -297,7 +297,7 @@ def component_list(competition_id, slide_id):
 
 
 ### Competitions ###
-def competition(competition_id):
+def competition(competition_id, required=True):
     """ Get Competition and all it's sub-entities. """
 
     join_component = joinedload(Competition.slides).subqueryload(Slide.components)
@@ -310,5 +310,5 @@ def competition(competition_id):
         .options(join_alternatives)
         .options(join_question_alternative_answer)
         .options(join_question_score)
-        .first()
+        .first_api(required, "Tävlingen kunde inte hittas")
     )
diff --git a/server/app/database/controller/search.py b/server/app/database/controller/search.py
index 7582579c3311be6a84b56dc01de1c444981adc5d..2ea1a811a00f44cf8c0e52a38eafb1c585568274 100644
--- a/server/app/database/controller/search.py
+++ b/server/app/database/controller/search.py
@@ -40,13 +40,10 @@ def user(
 
 
 def competition(
+    pagination_parameters,
     name=None,
     year=None,
     city_id=None,
-    page=0,
-    page_size=15,
-    order=1,
-    order_by=None,
 ):
     """ Finds and returns a competition from the provided parameters. """
 
@@ -58,11 +55,9 @@ def competition(
     if city_id:
         query = query.filter(Competition.city_id == city_id)
 
-    order_column = Competition.year  # Default order_by
-    if order_by:
-        order_column = getattr(Competition.columns, order_by)
-
-    return query.pagination(page, page_size, order_column, order)
+    pagination = query.paginate(page=pagination_parameters.page, per_page=pagination_parameters.page_size)
+    pagination_parameters.item_count = pagination.total
+    return pagination.items
 
 
 def slide(