diff --git a/client/src/actions/cities.ts b/client/src/actions/cities.ts
index 59f8be983260eec6f4e46e39d8218e2b5c8243eb..c06c94a032c0e4d04f35e679ecd8869d92e76d86 100644
--- a/client/src/actions/cities.ts
+++ b/client/src/actions/cities.ts
@@ -13,15 +13,15 @@ export const getCities = () => async (dispatch: AppDispatch) => {
     .then((res) => {
       dispatch({
         type: Types.SET_CITIES,
-        payload: res.data.items,
+        payload: res.data,
       })
       dispatch({
-        type: Types.SET_CITIES_COUNT,
-        payload: res.data.total_count,
+        type: Types.SET_CITIES_TOTAL,
+        payload: res.data.length,
       })
       dispatch({
-        type: Types.SET_CITIES_TOTAL,
-        payload: res.data.count,
+        type: Types.SET_CITIES_COUNT,
+        payload: res.data.length,
       })
     })
     .catch((err) => console.log(err))
diff --git a/client/src/actions/competitionLogin.ts b/client/src/actions/competitionLogin.ts
index 42a050532ef5ebe571eeb6ab67a3d8eaf1fa0129..b2198f74698f8fb7b40acd10f7a2b2c64ebb66d7 100644
--- a/client/src/actions/competitionLogin.ts
+++ b/client/src/actions/competitionLogin.ts
@@ -15,7 +15,7 @@ export const loginCompetition = (code: string, history: History, redirect: boole
 ) => {
   dispatch({ type: Types.LOADING_COMPETITION_LOGIN })
   await axios
-    .post('/api/auth/login/code', { code })
+    .post('/api/auth/code', { code })
     .then((res) => {
       const token = `Bearer ${res.data.access_token}`
       localStorage.setItem(`${res.data.view}Token`, token) //setting token to local storage
diff --git a/client/src/actions/competitions.test.ts b/client/src/actions/competitions.test.ts
index 52be36acb658576e9b5c1ffc1b40394c338d0f5f..2ebb22b74f7ef7b187c11df9e347988a82e4baf5 100644
--- a/client/src/actions/competitions.test.ts
+++ b/client/src/actions/competitions.test.ts
@@ -11,25 +11,24 @@ const mockStore = configureMockStore(middlewares)
 
 it('dispatches correct actions when getting competitions', async () => {
   const compRes: any = {
-    data: {
-      items: [
-        {
-          id: 21,
-          name: 'ggff',
-          year: 2021,
-          style_id: 1,
-          city: { name: 'city_name', id: 5 },
-        },
-        {
-          id: 22,
-          name: 'sssss',
-          year: 2021,
-          style_id: 1,
-          city: { name: 'city_name', id: 5 },
-        },
-      ],
-      count: 2,
-      total_count: 3,
+    data: [
+      {
+        id: 21,
+        name: 'ggff',
+        year: 2021,
+        style_id: 1,
+        city: { name: 'city_name', id: 5 },
+      },
+      {
+        id: 22,
+        name: 'sssss',
+        year: 2021,
+        style_id: 1,
+        city: { name: 'city_name', id: 5 },
+      },
+    ],
+    headers: {
+      pagination: '{"count": 2,"total": 3, "page_size": 5}',
     },
   }
 
@@ -37,9 +36,9 @@ it('dispatches correct actions when getting competitions', async () => {
     return Promise.resolve(compRes)
   })
   const expectedActions = [
-    { type: Types.SET_COMPETITIONS, payload: compRes.data.items },
-    { type: Types.SET_COMPETITIONS_TOTAL, payload: compRes.data.total_count },
-    { type: Types.SET_COMPETITIONS_COUNT, payload: compRes.data.count },
+    { type: Types.SET_COMPETITIONS, payload: compRes.data },
+    { type: Types.SET_COMPETITIONS_TOTAL, payload: 3 },
+    { type: Types.SET_COMPETITIONS_COUNT, payload: 2 },
   ]
   const store = mockStore({ competitions: { filterParams: [] } })
   await getCompetitions()(store.dispatch, store.getState as any)
diff --git a/client/src/actions/competitions.ts b/client/src/actions/competitions.ts
index 64fd11e1a61614e30296f685bf85eac8700e7a96..e934726d8265a2ea88751137c8cc3bbaef9375b1 100644
--- a/client/src/actions/competitions.ts
+++ b/client/src/actions/competitions.ts
@@ -24,15 +24,16 @@ export const getCompetitions = () => async (dispatch: AppDispatch, getState: ()
     .then((res) => {
       dispatch({
         type: Types.SET_COMPETITIONS,
-        payload: res.data.items,
+        payload: res.data,
       })
+      const pagination = JSON.parse(res.headers.pagination)
       dispatch({
         type: Types.SET_COMPETITIONS_TOTAL,
-        payload: res.data.total_count,
+        payload: pagination.total,
       })
       dispatch({
         type: Types.SET_COMPETITIONS_COUNT,
-        payload: res.data.count,
+        payload: res.data.length,
       })
     })
     .catch((err) => {
diff --git a/client/src/actions/roles.ts b/client/src/actions/roles.ts
index 68b86b30e415daa1c5b00470fb5e86b1cb10bd74..810e13b1437ef0465039a89ea0b65dbbac3c3cb6 100644
--- a/client/src/actions/roles.ts
+++ b/client/src/actions/roles.ts
@@ -13,7 +13,7 @@ export const getRoles = () => async (dispatch: AppDispatch) => {
     .then((res) => {
       dispatch({
         type: Types.SET_ROLES,
-        payload: res.data.items,
+        payload: res.data,
       })
     })
     .catch((err) => console.log(err))
diff --git a/client/src/actions/searchUser.test.ts b/client/src/actions/searchUser.test.ts
index 66c155079a82d0efd21f0df949c669ab70f85c22..71d539d5dccab99c065c9c3f899f6ea8a1b61f8d 100644
--- a/client/src/actions/searchUser.test.ts
+++ b/client/src/actions/searchUser.test.ts
@@ -10,27 +10,26 @@ const middlewares = [thunk]
 const mockStore = configureMockStore(middlewares)
 it('dispatches correct actions when getting users', async () => {
   const userRes: any = {
-    data: {
-      items: [
-        {
-          id: 21,
-          name: 'ggff',
-          email: 'email@test.com',
-          year: 2021,
-          role_id: 1,
-          city_id: 0,
-        },
-        {
-          id: 22,
-          name: 'sssss',
-          email: 'email@test.com',
-          year: 2021,
-          role_id: 1,
-          city_id: 0,
-        },
-      ],
-      count: 2,
-      total_count: 3,
+    data: [
+      {
+        id: 21,
+        name: 'ggff',
+        email: 'email@test.com',
+        year: 2021,
+        role_id: 1,
+        city_id: 0,
+      },
+      {
+        id: 22,
+        name: 'sssss',
+        email: 'email@test.com',
+        year: 2021,
+        role_id: 1,
+        city_id: 0,
+      },
+    ],
+    headers: {
+      pagination: '{"count": 2,"total": 3, "page_size":5 }',
     },
   }
 
@@ -38,9 +37,9 @@ it('dispatches correct actions when getting users', async () => {
     return Promise.resolve(userRes)
   })
   const expectedActions = [
-    { type: Types.SET_SEARCH_USERS, payload: userRes.data.items },
-    { type: Types.SET_SEARCH_USERS_TOTAL_COUNT, payload: userRes.data.total_count },
-    { type: Types.SET_SEARCH_USERS_COUNT, payload: userRes.data.count },
+    { type: Types.SET_SEARCH_USERS, payload: userRes.data },
+    { type: Types.SET_SEARCH_USERS_TOTAL_COUNT, payload: 3 },
+    { type: Types.SET_SEARCH_USERS_COUNT, payload: userRes.data.length },
   ]
   const store = mockStore({ searchUsers: { filterParams: [] } })
   await getSearchUsers()(store.dispatch, store.getState as any)
diff --git a/client/src/actions/searchUser.ts b/client/src/actions/searchUser.ts
index d47d4dd0903f57e1e298418bb72f43122b90407f..4197ee188e51e4f2c17a207d9686a25d2cf00b63 100644
--- a/client/src/actions/searchUser.ts
+++ b/client/src/actions/searchUser.ts
@@ -24,15 +24,17 @@ export const getSearchUsers = () => async (dispatch: AppDispatch, getState: () =
     .then((res) => {
       dispatch({
         type: Types.SET_SEARCH_USERS,
-        payload: res.data.items,
+        payload: res.data,
       })
+
+      const pagination = JSON.parse(res.headers.pagination)
       dispatch({
         type: Types.SET_SEARCH_USERS_TOTAL_COUNT,
-        payload: res.data.total_count,
+        payload: pagination.total,
       })
       dispatch({
         type: Types.SET_SEARCH_USERS_COUNT,
-        payload: res.data.count,
+        payload: res.data.length,
       })
     })
     .catch((err) => {
diff --git a/client/src/pages/admin/AdminPage.test.tsx b/client/src/pages/admin/AdminPage.test.tsx
index 445373a032207faa3f546a0c480fb03a093b1529..61d409733c09d3b7ff1fb2ef902eb8a10ab67e34 100644
--- a/client/src/pages/admin/AdminPage.test.tsx
+++ b/client/src/pages/admin/AdminPage.test.tsx
@@ -8,35 +8,33 @@ import AdminPage from './AdminPage'
 
 it('renders admin view', () => {
   const cityRes: any = {
-    data: {
-      items: [
-        {
-          id: 1,
-          name: 'Link\u00f6ping',
-        },
-        {
-          id: 2,
-          name: 'Stockholm',
-        },
-      ],
-      count: 2,
-      total_count: 3,
+    data: [
+      {
+        id: 1,
+        name: 'Link\u00f6ping',
+      },
+      {
+        id: 2,
+        name: 'Stockholm',
+      },
+    ],
+    headers: {
+      pagination: '{"count": 2,"total_count": 3}',
     },
   }
   const rolesRes: any = {
-    data: {
-      items: [
-        {
-          id: 1,
-          name: 'role1',
-        },
-        {
-          id: 2,
-          name: 'role2',
-        },
-      ],
-      count: 2,
-      total_count: 3,
+    data: [
+      {
+        id: 1,
+        name: 'role1',
+      },
+      {
+        id: 2,
+        name: 'role2',
+      },
+    ],
+    headers: {
+      pagination: '{"count": 2,"total_count": 3}',
     },
   }
   ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
diff --git a/client/src/pages/admin/competitions/CompetitionManager.test.tsx b/client/src/pages/admin/competitions/CompetitionManager.test.tsx
index 7af04abb66bba7ded7655398f288cba1c62bf78b..0b270a7df39653567333c06516090c9c0364c900 100644
--- a/client/src/pages/admin/competitions/CompetitionManager.test.tsx
+++ b/client/src/pages/admin/competitions/CompetitionManager.test.tsx
@@ -8,42 +8,36 @@ import CompetitionManager from './CompetitionManager'
 
 it('renders competition manager', () => {
   const cityRes: any = {
-    data: {
-      items: [
-        {
-          id: 1,
-          name: 'Link\u00f6ping',
-        },
-        {
-          id: 2,
-          name: 'Stockholm',
-        },
-      ],
-      count: 2,
-      total_count: 3,
-    },
+    data: [
+      {
+        id: 1,
+        name: 'Link\u00f6ping',
+      },
+      {
+        id: 2,
+        name: 'Stockholm',
+      },
+    ],
+    pagination: '{"count": 2,"total": 3, "page_size": 5}',
   }
   const compRes: any = {
-    data: {
-      items: [
-        {
-          id: 21,
-          name: 'ggff',
-          year: 2021,
-          style_id: 1,
-          city: cityRes.data.items[0],
-        },
-        {
-          id: 22,
-          name: 'sssss',
-          year: 2021,
-          style_id: 1,
-          city: cityRes.data.items[1],
-        },
-      ],
-      count: 2,
-      total_count: 3,
-    },
+    data: [
+      {
+        id: 21,
+        name: 'ggff',
+        year: 2021,
+        style_id: 1,
+        city: cityRes.data[0],
+      },
+      {
+        id: 22,
+        name: 'sssss',
+        year: 2021,
+        style_id: 1,
+        city: cityRes.data[1],
+      },
+    ],
+    headers: { pagination: '{"count": 2,"total": 3, "page_size": 5}' },
   }
 
   ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
diff --git a/client/src/pages/admin/competitions/CompetitionManager.tsx b/client/src/pages/admin/competitions/CompetitionManager.tsx
index 0545102893ce342878602972d63c1dbe879ac59a..7c2c969efba20debfc116818a6f04945cb6ecbfe 100644
--- a/client/src/pages/admin/competitions/CompetitionManager.tsx
+++ b/client/src/pages/admin/competitions/CompetitionManager.tsx
@@ -161,7 +161,7 @@ const CompetitionManager: React.FC = (props: any) => {
     await axios
       .get(`/api/competitions/${id}/codes`)
       .then((response) => {
-        setCodes(response.data.items)
+        setCodes(response.data)
       })
       .catch(console.log)
   }
@@ -171,8 +171,7 @@ const CompetitionManager: React.FC = (props: any) => {
     await axios
       .get(`/api/competitions/${id}/teams`)
       .then((response) => {
-        // console.log(response.data.items)
-        setTeams(response.data.items)
+        setTeams(response.data)
       })
       .catch((err) => {
         console.log(err)
@@ -344,8 +343,8 @@ const CompetitionManager: React.FC = (props: any) => {
         rowsPerPageOptions={[]}
         rowsPerPage={filterParams.pageSize}
         count={competitionTotal}
-        page={filterParams.page}
-        onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
+        page={filterParams.page - 1}
+        onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage + 1 })}
       />
       <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
         <MenuItem onClick={handleStartCompetition}>Starta</MenuItem>
diff --git a/client/src/pages/admin/regions/Regions.test.tsx b/client/src/pages/admin/regions/Regions.test.tsx
index 7046bff04fc8fc9fc8cb3f08d0d140d0d475b5a3..864001b31dcc9cf17edbaf70af65aac240232663 100644
--- a/client/src/pages/admin/regions/Regions.test.tsx
+++ b/client/src/pages/admin/regions/Regions.test.tsx
@@ -8,19 +8,18 @@ import RegionManager from './Regions'
 
 it('renders region manager', () => {
   const cityRes: any = {
-    data: {
-      items: [
-        {
-          id: 1,
-          name: 'Link\u00f6ping',
-        },
-        {
-          id: 2,
-          name: 'Stockholm',
-        },
-      ],
-      count: 2,
-      total_count: 3,
+    data: [
+      {
+        id: 1,
+        name: 'Link\u00f6ping',
+      },
+      {
+        id: 2,
+        name: 'Stockholm',
+      },
+    ],
+    headers: {
+      pagination: '{"count": 2,"total_count": 3}',
     },
   }
 
diff --git a/client/src/pages/admin/users/AddUser.tsx b/client/src/pages/admin/users/AddUser.tsx
index 05f5745c94dc58d6f851556b9571f4aeff7c45ac..cde3fa322dd236edbac13b664dfdafe3985bc56f 100644
--- a/client/src/pages/admin/users/AddUser.tsx
+++ b/client/src/pages/admin/users/AddUser.tsx
@@ -59,7 +59,7 @@ const AddUser: React.FC = (props: any) => {
       role_id: selectedRole?.id as number,
     }
     await axios
-      .post('/api/auth/signup', params)
+      .post('/api/users', params)
       .then(() => {
         actions.resetForm()
         setAnchorEl(null)
diff --git a/client/src/pages/admin/users/EditUser.tsx b/client/src/pages/admin/users/EditUser.tsx
index 44375e4a8b5382996ecf20720865bdf1ac161fde..9e9d5d2a1d61d235abf715f6c4075bd5f4439ffa 100644
--- a/client/src/pages/admin/users/EditUser.tsx
+++ b/client/src/pages/admin/users/EditUser.tsx
@@ -14,7 +14,7 @@ import {
   TextField,
   Theme,
   useMediaQuery,
-  useTheme,
+  useTheme
 } from '@material-ui/core'
 import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
 import { Alert, AlertTitle } from '@material-ui/lab'
@@ -110,7 +110,7 @@ const EditUser = ({ user }: UserIdProps) => {
   const handleDeleteUsers = async () => {
     setOpen(false)
     await axios
-      .delete(`/api/auth/delete/${user.id}`)
+      .delete(`/api/users/${user.id}`)
       .then(() => {
         setAnchorEl(null)
         dispatch(getSearchUsers())
diff --git a/client/src/pages/admin/users/UserManager.test.tsx b/client/src/pages/admin/users/UserManager.test.tsx
index f50cbed8ded2b12d3d487e96f1d185bca2a2cbf8..69117bc5829fc6fca85bc2316c1faf668112d3f7 100644
--- a/client/src/pages/admin/users/UserManager.test.tsx
+++ b/client/src/pages/admin/users/UserManager.test.tsx
@@ -8,25 +8,24 @@ import UserManager from './UserManager'
 
 it('renders user manager', () => {
   const userRes: any = {
-    data: {
-      items: [
-        {
-          id: 1,
-          name: 'user1',
-          email: 'user1@email.com',
-          role_id: 0,
-          city_id: 0,
-        },
-        {
-          id: 2,
-          name: 'Stockholm',
-          email: 'user2@email.com',
-          role_id: 0,
-          city_id: 0,
-        },
-      ],
-      count: 2,
-      total_count: 3,
+    data: [
+      {
+        id: 1,
+        name: 'user1',
+        email: 'user1@email.com',
+        role_id: 0,
+        city_id: 0,
+      },
+      {
+        id: 2,
+        name: 'Stockholm',
+        email: 'user2@email.com',
+        role_id: 0,
+        city_id: 0,
+      },
+    ],
+    headers: {
+      pagination: '{"count": 2,"total_count": 3, "page_size": 5}',
     },
   }
 
diff --git a/client/src/pages/admin/users/UserManager.tsx b/client/src/pages/admin/users/UserManager.tsx
index 9713e90f3159ee1d8cc91b2cc9916438a2537ab0..0b32e86893a979c3e37061102e49ab4fb5ed4e7f 100644
--- a/client/src/pages/admin/users/UserManager.tsx
+++ b/client/src/pages/admin/users/UserManager.tsx
@@ -170,9 +170,9 @@ const UserManager: React.FC = (props: any) => {
         component="div"
         rowsPerPageOptions={[]}
         rowsPerPage={filterParams.pageSize}
-        count={usersTotal}
-        page={filterParams.page}
-        onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
+        count={usersTotal || 0}
+        page={filterParams.page - 1}
+        onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage + 1 })}
       />
     </div>
   )
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx
index 7254a69d3bdae4e39c6ce1097dc06e753ff333f0..539d54e809926bc7a583ea06b00acddd10084f1e 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx
@@ -18,14 +18,12 @@ it('renders presentation editor', () => {
     },
   }
   const citiesRes: any = {
-    data: {
-      items: [
-        {
-          name: '',
-          city_id: 0,
-        },
-      ],
-    },
+    data: [
+      {
+        name: '',
+        city_id: 0,
+      },
+    ],
   }
   const typesRes: any = {
     data: {
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx
index bedd621fa5629f87a93176369542f1420222bf98..3d854b87112a990c28dfc92408bf254e15c2678f 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerMatch.tsx
@@ -70,7 +70,7 @@ const AnswerMatch = ({ variant, activeSlide, competitionId }: AnswerMultipleProp
   }, [teamId])
 
   const getButtonStyle = () => {
-    if (activeSlide?.timer !== undefined && !timer.enabled) {
+    if (activeSlide?.timer !== null && !timer.enabled) {
       return { fill: '#AAAAAA' } // Buttons are light grey if  timer is not on
     }
     return {}
@@ -79,7 +79,7 @@ const AnswerMatch = ({ variant, activeSlide, competitionId }: AnswerMultipleProp
   const onMove = async (previousIndex: number, resultIndex: number) => {
     // moved outside the list
     if (resultIndex < 0 || resultIndex >= sortedAnswers.length || variant !== 'presentation') return
-    if (activeSlide?.timer !== undefined && !timer.enabled) return
+    if (activeSlide?.timer !== null && !timer.enabled) return
     const answersCopy = [...sortedAnswers]
     const [removed] = answersCopy.splice(previousIndex, 1)
     answersCopy.splice(resultIndex, 0, removed)
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
index c687faf00c1b811c793cd88035becec93f638d53..039d9dfb558fa037d578566ef1b2280527f6c42b 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
@@ -48,7 +48,7 @@ const AnswerMultiple = ({ variant, activeSlide, competitionId }: AnswerMultipleP
 
   const updateAnswer = async (alternative: QuestionAlternative, checked: boolean) => {
     // TODO: fix. Make list of alternatives and delete & post instead of put to allow multiple boxes checked.
-    if (!activeSlide || (activeSlide?.timer !== undefined && !timer.enabled)) {
+    if (!activeSlide || (activeSlide?.timer !== null && !timer.enabled)) {
       return
     }
     const url = `/api/competitions/${competitionId}/teams/${teamId}/answers/question_alternatives/${alternative.id}`
@@ -91,7 +91,7 @@ const AnswerMultiple = ({ variant, activeSlide, competitionId }: AnswerMultipleP
           <div key={alt.id}>
             <ListItem divider>
               <GreenCheckbox
-                disabled={activeSlide?.timer !== undefined && !timer.enabled}
+                disabled={activeSlide?.timer !== null && !timer.enabled}
                 checked={decideChecked(alt)}
                 onChange={(event: any) => updateAnswer(alt, event.target.checked)}
               />
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
index 70d59c01c4985541f8f503448172998a93dd4a48..1ff4b7cf16542dbe4540234d0ee1f66a45c474c0 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
@@ -50,7 +50,7 @@ const AnswerSingle = ({ variant, activeSlide, competitionId }: AnswerSingleProps
   }
 
   const updateAnswer = async (alternative: QuestionAlternative) => {
-    if (!activeSlide || (activeSlide?.timer !== undefined && !timer.enabled)) {
+    if (!activeSlide || (activeSlide?.timer !== null && !timer.enabled)) {
       return
     }
 
@@ -79,7 +79,7 @@ const AnswerSingle = ({ variant, activeSlide, competitionId }: AnswerSingleProps
    */
   const renderRadioButton = (alt: QuestionAlternative) => {
     let disabledStyle
-    if (activeSlide?.timer !== undefined && !timer.enabled) {
+    if (activeSlide?.timer !== null && !timer.enabled) {
       disabledStyle = { fill: '#AAAAAA' } // Buttons are light grey if  timer is not on
     }
     if (variant === 'presentation') {
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
index 595462c97ecbc4583754d40cc4f94eeee204e224..b8048c750c433cbbce68fa521e98a4b7937f38d8 100644
--- a/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
@@ -39,7 +39,7 @@ const AnswerText = ({ activeSlide, competitionId }: AnswerTextProps) => {
       setTimerHandle(undefined)
     }
     //Only updates answer if the timer is on
-    if (activeSlide?.timer !== undefined && !timer.enabled) {
+    if (!(activeSlide?.timer !== null && !timer.enabled)) {
       //Only updates answer 100ms after last input was made
       setTimerHandle(window.setTimeout(() => updateAnswer(answer), 100))
     }
@@ -79,7 +79,7 @@ const AnswerText = ({ activeSlide, competitionId }: AnswerTextProps) => {
       </ListItem>
       <ListItem style={{ height: '100%' }}>
         <TextField
-          disabled={team === undefined || (activeSlide?.timer !== undefined && !timer.enabled)}
+          disabled={team === undefined || (activeSlide?.timer !== null && !timer.enabled)}
           defaultValue={getDefaultString()}
           style={{ height: '100%' }}
           variant="outlined"
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx
index 086a64a480c1f7d6de820d91c03d47cb327c1467..1cc012b8febf61346cd0e7c6e62679d4a3207f99 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx
@@ -32,7 +32,7 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => {
     if (activeSlide) {
       setTimer(timerValue)
       await axios
-        .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}`, { timer: timerValue })
+        .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}`, { timer: timerValue || null })
         .then(() => {
           dispatch(getEditorCompetition(competitionId))
         })
diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx
index df80d4fc4ecff26ccbab5ab863a33bf520c52e51..ad328fa018ea9d1430c9d454e64eea9073479a1a 100644
--- a/client/src/pages/views/JudgeViewPage.tsx
+++ b/client/src/pages/views/JudgeViewPage.tsx
@@ -124,19 +124,18 @@ const JudgeViewPage: React.FC = () => {
         <div className={classes.toolbar} />
         <List>
           {slides.map((slide, index) => (
-            <>
+            <div key={slide.id}>
               <SlideListItem
                 selected={slide.order === currentSlide?.order}
                 onClick={() => handleSelectSlide(index)}
                 button
-                key={slide.id}
                 style={{ border: 2, borderStyle: slide.id === operatorActiveSlideId ? 'dashed' : 'none' }}
               >
                 {renderSlideIcon(slide)}
                 <ListItemText primary={`Sida ${slide.order + 1}`} />
               </SlideListItem>
               <Divider />
-            </>
+            </div>
           ))}
         </List>
       </LeftDrawer>
diff --git a/client/src/pages/views/OperatorViewPage.test.tsx b/client/src/pages/views/OperatorViewPage.test.tsx
index 3259fcfcf07f7f54a725e01b2ea6fa8b69a89b21..f20cb695a695f94ccc519fe1f149ad5d957a86d2 100644
--- a/client/src/pages/views/OperatorViewPage.test.tsx
+++ b/client/src/pages/views/OperatorViewPage.test.tsx
@@ -15,19 +15,18 @@ it('renders operator view page', async () => {
       },
     }
     const teamsRes: any = {
-      data: {
-        items: [
-          {
-            id: 1,
-            name: 'team1',
-          },
-          {
-            id: 2,
-            name: 'team2',
-          },
-        ],
-        count: 2,
-        total_count: 3,
+      data: [
+        {
+          id: 1,
+          name: 'team1',
+        },
+        {
+          id: 2,
+          name: 'team2',
+        },
+      ],
+      headers: {
+        pagination: '{"count": 2,"total_count": 3}',
       },
     }
 
diff --git a/client/src/pages/views/OperatorViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx
index 130240c20fcff7ee742347c06b38b0424232fbb9..55946eec366106c42886ea5f0cb9fb69de225eb3 100644
--- a/client/src/pages/views/OperatorViewPage.tsx
+++ b/client/src/pages/views/OperatorViewPage.tsx
@@ -152,7 +152,7 @@ const OperatorViewPage: React.FC = () => {
     await axios
       .get(`/api/competitions/${activeId}/codes`)
       .then((response) => {
-        setCodes(response.data.items)
+        setCodes(response.data)
       })
       .catch(console.log)
   }
diff --git a/client/src/reducers/competitionsReducer.ts b/client/src/reducers/competitionsReducer.ts
index 62b7006ebe0e379ab58774b6e317a1fdbe592d22..e7cab2058b2b903eb7c8f4e02904050701deff95 100644
--- a/client/src/reducers/competitionsReducer.ts
+++ b/client/src/reducers/competitionsReducer.ts
@@ -16,7 +16,7 @@ const initialState: CompetitionState = {
   competitions: [],
   total: 0,
   count: 0,
-  filterParams: { pageSize: 10, page: 0 },
+  filterParams: { pageSize: 10, page: 1 },
 }
 
 /** Intercept actions for competitions state and update the state */
diff --git a/client/src/reducers/searchUserReducer.ts b/client/src/reducers/searchUserReducer.ts
index 1e78f8961e014714dd6f1a8b762902298a2287a2..4fea6ef9c12cdcce2d05c5a197ff046a25fc904d 100644
--- a/client/src/reducers/searchUserReducer.ts
+++ b/client/src/reducers/searchUserReducer.ts
@@ -16,7 +16,7 @@ const initialState: SearchUserState = {
   users: [],
   total: 0,
   count: 0,
-  filterParams: { pageSize: 10, page: 0 },
+  filterParams: { pageSize: 10, page: 1 },
 }
 
 /** Intercept actions for searchUser state and update the state */
diff --git a/client/src/utils/checkAuthenticationCompetition.test.ts b/client/src/utils/checkAuthenticationCompetition.test.ts
index 478c4f5e0d7ae43211b45e37bcf92476d6dac004..ecd8e9beb4d2541746c1d0d879e7e7b26aefa421 100644
--- a/client/src/utils/checkAuthenticationCompetition.test.ts
+++ b/client/src/utils/checkAuthenticationCompetition.test.ts
@@ -15,22 +15,25 @@ it('dispatches correct actions when auth token is ok', async () => {
   const decodedToken = {
     iat: 1620216181,
     exp: 32514436993,
-    user_claims: { competition_id: 123123, team_id: 321321, view: 'Participant', code: 'ABCDEF' },
+    competition_id: 123123,
+    team_id: 321321,
+    view: 'Participant',
+    code: 'ABCDEF',
   }
 
   const testToken =
-    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjMyNTE0NDM2OTkzLCJ1c2VyX2NsYWltcyI6eyJjb21wZXRpdGlvbl9pZCI6MTIzMTIzLCJ0ZWFtX2lkIjozMjEzMjEsInZpZXciOiJQYXJ0aWNpcGFudCIsImNvZGUiOiJBQkNERUYifX0.1gPRJcjn3xuPOcgUUffMngIQDoDtxS9RZczcbdyyaaA'
+    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MjAyMTYxODEsImV4cCI6MzI1MTQ0MzY5OTMsImNvbXBldGl0aW9uX2lkIjoxMjMxMjMsInRlYW1faWQiOjMyMTMyMSwidmlldyI6IlBhcnRpY2lwYW50IiwiY29kZSI6IkFCQ0RFRiJ9.fNrU8s-ZHPFCLYqtD2nogmSy31sBtX-8KWu911xNC8I'
   localStorage.setItem('JudgeToken', testToken)
   await CheckAuthenticationCompetition('Judge')
   expect(spy).toBeCalledWith({
     type: Types.SET_COMPETITION_LOGIN_DATA,
     payload: {
-      competition_id: decodedToken.user_claims.competition_id,
-      team_id: decodedToken.user_claims.team_id,
-      view: decodedToken.user_claims.view,
+      competition_id: decodedToken.competition_id,
+      team_id: decodedToken.team_id,
+      view: decodedToken.view,
     },
   })
-  expect(spy).toBeCalledWith({ type: Types.SET_PRESENTATION_CODE, payload: decodedToken.user_claims.code })
+  expect(spy).toBeCalledWith({ type: Types.SET_PRESENTATION_CODE, payload: decodedToken.code })
   expect(spy).toBeCalledWith({
     type: Types.SET_PRESENTATION_COMPETITION,
     payload: compRes.data,
@@ -48,7 +51,7 @@ it('dispatches correct actions when getting user data fails', async () => {
   })
   const spy = jest.spyOn(store, 'dispatch')
   const testToken =
-    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjMyNTE0NDM2OTkzLCJ1c2VyX2NsYWltcyI6eyJjb21wZXRpdGlvbl9pZCI6MTIzMTIzLCJ0ZWFtX2lkIjozMjEzMjEsInZpZXciOiJQYXJ0aWNpcGFudCIsImNvZGUiOiJBQkNERUYifX0.1gPRJcjn3xuPOcgUUffMngIQDoDtxS9RZczcbdyyaaA'
+    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MjAyMTYxODEsImV4cCI6MzI1MTQ0MzY5OTMsImNvbXBldGl0aW9uX2lkIjoxMjMxMjMsInRlYW1faWQiOjMyMTMyMSwidmlldyI6IlBhcnRpY2lwYW50IiwiY29kZSI6IkFCQ0RFRiJ9.fNrU8s-ZHPFCLYqtD2nogmSy31sBtX-8KWu911xNC8I'
   localStorage.setItem('AudienceToken', testToken)
   await CheckAuthenticationCompetition('Audience')
   expect(spy).toBeCalledWith({ type: Types.SET_COMPETITION_LOGIN_UNAUTHENTICATED })
diff --git a/client/src/utils/checkAuthenticationCompetition.ts b/client/src/utils/checkAuthenticationCompetition.ts
index f6dfd8fe227e5ef5bf71dc1d7cef0a4daf9b9ee9..5da2eb3dac6f6d33bc0fce3b79d8272cfe4ded71 100644
--- a/client/src/utils/checkAuthenticationCompetition.ts
+++ b/client/src/utils/checkAuthenticationCompetition.ts
@@ -21,13 +21,13 @@ export const CheckAuthenticationCompetition = async (role: 'Judge' | 'Operator'
           store.dispatch({
             type: Types.SET_COMPETITION_LOGIN_DATA,
             payload: {
-              competition_id: decodedToken.user_claims.competition_id,
-              team_id: decodedToken.user_claims.team_id,
-              view: decodedToken.user_claims.view,
+              competition_id: decodedToken.competition_id,
+              team_id: decodedToken.team_id,
+              view: decodedToken.view,
             },
           })
-          getPresentationCompetition(decodedToken.user_claims.competition_id)(store.dispatch, store.getState)
-          setPresentationCode(decodedToken.user_claims.code)(store.dispatch)
+          getPresentationCompetition(decodedToken.competition_id)(store.dispatch, store.getState)
+          setPresentationCode(decodedToken.code)(store.dispatch)
         })
         .catch((error) => {
           console.log(error)
diff --git a/server/app/__init__.py b/server/app/__init__.py
index 5edf5d5d7567caa88537716e7702b77a4e48ccfc..e1512dd590b55127e9aa54f2d9925aaa816a09f3 100644
--- a/server/app/__init__.py
+++ b/server/app/__init__.py
@@ -1,9 +1,11 @@
 from flask import Flask, redirect, request
 from flask_uploads import configure_uploads
+from flask_uploads.extensions import IMAGES
+from flask_uploads.flask_uploads import UploadSet
 
 import app.database.models as models
+from app.apis import init_api
 from app.core import bcrypt, db, jwt, ma
-from app.core.dto import MediaDTO
 
 
 def create_app(config_name="configmodule.DevelopmentConfig"):
@@ -25,7 +27,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"):
         db.init_app(app)
         db.create_all()
         ma.init_app(app)
-        configure_uploads(app, (MediaDTO.image_set,))
+        configure_uploads(app, (UploadSet("photos", IMAGES),))
 
         # Init socket
         from app.core.sockets import sio
@@ -36,6 +38,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"):
         from app.apis import flask_api
 
         flask_api.init_app(app)
+        init_api()
 
         # Flask helpers methods
 
diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index ad71728a7d6d73d3453e628c9c4305f2c1da30ed..5bb204b3de372e93a4182943013f20a3b7d5b3f9 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -1,18 +1,31 @@
-import app.core.http_codes as http_codes
+from functools import wraps
+
 from flask_jwt_extended import verify_jwt_in_request
-from flask_jwt_extended.utils import get_jwt_claims
-from flask_restx.errors import abort
+from flask_jwt_extended.utils import get_jwt
+from flask_smorest import Blueprint, abort
+from flask_smorest.error_handler import ErrorSchema
+
+Blueprint.PAGINATION_HEADER_FIELD_NAME = "pagination"
+
 
+ALL = ["*"]
 
-def validate_editor(db_item, *views):
-    claims = get_jwt_claims()
-    city_id = int(claims.get("city_id"))
-    if db_item.city_id != city_id:
-        abort(http_codes.UNAUTHORIZED)
+
+class http_codes:
+    OK = 200
+    NO_CONTENT = 204
+    BAD_REQUEST = 400
+    UNAUTHORIZED = 401
+    FORBIDDEN = 403
+    NOT_FOUND = 404
+    CONFLICT = 409
+    GONE = 410
+    INTERNAL_SERVER_ERROR = 500
+    SERVICE_UNAVAILABLE = 503
 
 
 def _is_allowed(allowed, actual):
-    return actual and "*" in allowed or actual in allowed
+    return actual and allowed == ALL or actual in allowed
 
 
 def _has_access(in_claim, in_route):
@@ -20,104 +33,108 @@ def _has_access(in_claim, in_route):
     return not in_route or in_claim and in_claim == in_route
 
 
-def protect_route(allowed_roles=None, allowed_views=None):
-    def wrapper(func):
-        def inner(*args, **kwargs):
-            verify_jwt_in_request()
-            claims = get_jwt_claims()
+# class AuthorizationHeadersSchema(Schema):
 
-            # Authorize request if roles has access to the route #
+#     Authorization = fields.String(required=True)
 
-            nonlocal allowed_roles
-            allowed_roles = allowed_roles or []
-            role = claims.get("role")
-            if _is_allowed(allowed_roles, role):
-                return func(*args, **kwargs)
 
-            # Authorize request if view has access and is trying to access the
-            # competition its in. Also check team if client is a team.
-            # Allow request if route doesn't belong to any competition.
-
-            nonlocal allowed_views
-            allowed_views = allowed_views or []
-            view = claims.get("view")
-            if not _is_allowed(allowed_views, view):
-                abort(
-                    http_codes.UNAUTHORIZED,
-                    f"Client with view '{view}' is not allowed to access route with allowed views {allowed_views}.",
-                )
-
-            claim_competition_id = claims.get("competition_id")
-            route_competition_id = kwargs.get("competition_id")
-            if not _has_access(claim_competition_id, route_competition_id):
-                abort(
-                    http_codes.UNAUTHORIZED,
-                    f"Client in competition '{claim_competition_id}' is not allowed to access competition '{route_competition_id}'.",
-                )
-
-            if view == "Team":
-                claim_team_id = claims.get("team_id")
-                route_team_id = kwargs.get("team_id")
-                if not _has_access(claim_team_id, route_team_id):
-                    abort(
-                        http_codes.UNAUTHORIZED,
-                        f"Client in team '{claim_team_id}' is not allowed to access team '{route_team_id}'.",
-                    )
+class ExtendedBlueprint(Blueprint):
+    def authorization(self, allowed_roles=None, allowed_views=None):
+        def decorator(func):
+
+            # func = self.arguments(AuthorizationHeadersSchema, location="headers")(func)
+            func = self.alt_response(http_codes.UNAUTHORIZED, ErrorSchema, description="Unauthorized")(func)
+
+            @wraps(func)
+            def wrapper(*args, **kwargs):
 
-            return func(*args, **kwargs)
+                # Check that allowed_roles and allowed_views have correct type
+                nonlocal allowed_roles
+                nonlocal allowed_views
+                allowed_roles = allowed_roles or []
+                allowed_views = allowed_views or []
+                assert (
+                    isinstance(allowed_roles, list) or allowed_roles == "*"
+                ), f"Allowed roles must be a list or '*', not '{allowed_roles}'"
+                assert (
+                    isinstance(allowed_views, list) or allowed_views == "*"
+                ), f"Allowed views must be a list or '*', not '{allowed_views}'"
 
-        return inner
+                verify_jwt_in_request()
+                jwt = get_jwt()
 
-    return wrapper
+                # Authorize request if roles has access to the route #
 
+                role = jwt.get("role")
+                if _is_allowed(allowed_roles, role):
+                    return func(*args, **kwargs)
 
-def text_response(message, code=http_codes.OK):
-    return {"message": message}, code
+                # Authorize request if view has access and is trying to access the
+                # competition its in. Also check team if client is a team.
+                # Allow request if route doesn't belong to any competition.
 
+                view = jwt.get("view")
+                if not _is_allowed(allowed_views, view):
+                    abort(
+                        http_codes.UNAUTHORIZED,
+                        f"Client with view '{view}' is not allowed to access route with allowed views {allowed_views}.",
+                    )
+
+                claim_competition_id = jwt.get("competition_id")
+                route_competition_id = kwargs.get("competition_id")
+                if not _has_access(claim_competition_id, route_competition_id):
+                    abort(
+                        http_codes.UNAUTHORIZED,
+                        f"Client in competition '{claim_competition_id}' is not allowed to access competition '{route_competition_id}'.",
+                    )
 
-def list_response(items, total=None, code=http_codes.OK):
-    if type(items) is not list:
-        abort(http_codes.INTERNAL_SERVER_ERROR)
-    if not total:
-        total = len(items)
-    return {"items": items, "count": len(items), "total_count": total}, code
+                if view == "Team":
+                    claim_team_id = jwt.get("team_id")
+                    route_team_id = kwargs.get("team_id")
+                    if not _has_access(claim_team_id, route_team_id):
+                        abort(
+                            http_codes.UNAUTHORIZED,
+                            f"Client in team '{claim_team_id}' is not allowed to access team '{route_team_id}'.",
+                        )
 
+                return func(*args, **kwargs)
 
-def item_response(item, code=http_codes.OK):
-    if isinstance(item, list):
-        abort(http_codes.INTERNAL_SERVER_ERROR)
-    return item, code
+            return wrapper
 
+        return decorator
 
-from flask_restx import Api
 
-from .alternatives import api as alternative_ns
-from .answers import api as answer_ns
-from .auth import api as auth_ns
-from .codes import api as code_ns
-from .competitions import api as comp_ns
-from .components import api as component_ns
-from .media import api as media_ns
-from .misc import api as misc_ns
-from .questions import api as question_ns
-from .scores import api as score_ns
-from .slides import api as slide_ns
-from .teams import api as team_ns
-from .users import api as user_ns
+from flask_smorest import Api
 
 flask_api = Api()
-flask_api.add_namespace(media_ns, path="/api/media")
-flask_api.add_namespace(misc_ns, path="/api/misc")
-flask_api.add_namespace(user_ns, path="/api/users")
-flask_api.add_namespace(auth_ns, path="/api/auth")
-flask_api.add_namespace(comp_ns, path="/api/competitions")
-flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides")
-flask_api.add_namespace(
-    alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives"
-)
-flask_api.add_namespace(team_ns, path="/api/competitions/<competition_id>/teams")
-flask_api.add_namespace(code_ns, path="/api/competitions/<competition_id>/codes")
-flask_api.add_namespace(question_ns, path="/api/competitions/<competition_id>")
-flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<slide_id>/components")
-flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<team_id>/answers")
-flask_api.add_namespace(score_ns, path="/api/competitions/<competition_id>/teams/<team_id>/answers/question_scores")
+
+
+def init_api():
+
+    from .alternatives import blp as alternative_blp
+    from .answers import blp as answer_blp
+    from .auth import blp as auth_blp
+    from .codes import blp as code_blp
+    from .competitions import blp as competition_blp
+    from .components import blp as component_blp
+    from .media import blp as media_blp
+    from .misc import blp as misc_blp
+    from .questions import blp as question_blp
+    from .scores import blp as score_blp
+    from .slides import blp as slide_blp
+    from .teams import blp as team_blp
+    from .users import blp as user_blp
+
+    flask_api.register_blueprint(user_blp)
+    flask_api.register_blueprint(auth_blp)
+    flask_api.register_blueprint(competition_blp)
+    flask_api.register_blueprint(misc_blp)
+    flask_api.register_blueprint(media_blp)
+    flask_api.register_blueprint(slide_blp)
+    flask_api.register_blueprint(question_blp)
+    flask_api.register_blueprint(team_blp)
+    flask_api.register_blueprint(code_blp)
+    flask_api.register_blueprint(alternative_blp)
+    flask_api.register_blueprint(component_blp)
+    flask_api.register_blueprint(answer_blp)
+    flask_api.register_blueprint(score_blp)
diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py
index d3ae5d9d4be9f3a5835c4bb4c6f00ee5c38aa34d..d4db80fc3405374f10b53665544714d9f97ad41e 100644
--- a/server/app/apis/alternatives.py
+++ b/server/app/apis/alternatives.py
@@ -3,79 +3,86 @@ All API calls concerning question alternatives.
 Default route: /api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives
 """
 
-from os import abort
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import QuestionAlternativeDTO
-from app.core.parsers import sentinel
+from app.core import ma
+from app.core.schemas import BaseSchema, QuestionAlternativeSchema
+from app.database import models
 from app.database.models import Question, QuestionAlternative
-from flask_restx import Resource, reqparse
+from flask.views import MethodView
+from flask_smorest import abort
+from flask_smorest.error_handler import ErrorSchema
 
-api = QuestionAlternativeDTO.api
-schema = QuestionAlternativeDTO.schema
-list_schema = QuestionAlternativeDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-alternative_parser_add = reqparse.RequestParser()
-alternative_parser_add.add_argument("alternative", type=str, default="", location="json")
-alternative_parser_add.add_argument("correct", type=str, default="", location="json")
+blp = ExtendedBlueprint(
+    "alternative",
+    "alternative",
+    url_prefix="/api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives",
+    description="Adding, updating, deleting and copy alternatives",
+)
 
-alternative_parser_edit = reqparse.RequestParser()
-alternative_parser_edit.add_argument("alternative", type=str, default=sentinel, location="json")
-alternative_parser_edit.add_argument("alternative_order", type=int, default=sentinel, location="json")
-alternative_parser_edit.add_argument("correct", type=str, default=sentinel, location="json")
-alternative_parser_edit.add_argument("correct_order", type=int, default=sentinel, location="json")
 
+class AlternativeAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.QuestionAlternative
 
-@api.route("")
-@api.param("competition_id, slide_id, question_id")
-class QuestionAlternativeList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+    alternative = ma.auto_field(required=False, missing="")
+    correct = ma.auto_field(required=False, missing="")
+
+
+class AlternativeEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.QuestionAlternative
+
+    alternative = ma.auto_field(required=False)
+    alternative_order = ma.auto_field(required=False, missing=None)
+    correct = ma.auto_field(required=False)
+    correct_order = ma.auto_field(required=False, missing=None)
+
+
+@blp.route("")
+class Alternatives(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, QuestionAlternativeSchema(many=True))
     def get(self, competition_id, slide_id, question_id):
         """ Gets the all question alternatives to the specified question. """
+        return dbc.get.question_alternative_list(competition_id, slide_id, question_id)
 
-        items = dbc.get.question_alternative_list(
-            competition_id,
-            slide_id,
-            question_id,
-        )
-        return list_response(list_schema.dump(items))
-
-    @protect_route(allowed_roles=["*"])
-    def post(self, competition_id, slide_id, question_id):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(AlternativeAddArgsSchema)
+    @blp.response(http_codes.OK, QuestionAlternativeSchema)
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not add alternative")
+    def post(self, args, competition_id, slide_id, question_id):
         """
         Posts a new question alternative to the specified
         question using the provided arguments.
         """
+        return dbc.add.question_alternative(**args, question_id=question_id)
 
-        args = alternative_parser_add.parse_args(strict=True)
-        item = dbc.add.question_alternative(**args, question_id=question_id)
-        return item_response(schema.dump(item))
 
-
-@api.route("/<alternative_id>")
-@api.param("competition_id, slide_id, question_id, alternative_id")
-class QuestionAlternatives(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+@blp.route("/<alternative_id>")
+class QuestionAlternatives(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, QuestionAlternativeSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find alternative")
     def get(self, competition_id, slide_id, question_id, alternative_id):
         """ Gets the specified question alternative. """
-
-        items = dbc.get.question_alternative(
-            competition_id,
-            slide_id,
-            question_id,
-            alternative_id,
-        )
-        return item_response(schema.dump(items))
-
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id, slide_id, question_id, alternative_id):
+        return dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(AlternativeEditArgsSchema)
+    @blp.response(http_codes.OK, QuestionAlternativeSchema)
+    @blp.alt_response(
+        http_codes.BAD_REQUEST, ErrorSchema, description="Paramters to edit alternative with is incorrect"
+    )
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find alternative")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not edit alternative with the given values")
+    def put(self, args, competition_id, slide_id, question_id, alternative_id):
         """
         Edits the specified question alternative using the provided arguments.
         """
 
-        args = alternative_parser_edit.parse_args(strict=True)
         item = dbc.get.question_alternative(
             competition_id,
             slide_id,
@@ -84,9 +91,12 @@ class QuestionAlternatives(Resource):
         )
 
         new_alternative_order = args.pop("alternative_order")
-        if new_alternative_order is not sentinel and item.alternative_order != new_alternative_order:
+        if new_alternative_order is not None and item.alternative_order != new_alternative_order:
             if not (0 <= new_alternative_order < dbc.utils.count(QuestionAlternative, {"question_id": question_id})):
-                abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_alternative_order}'")
+                abort(
+                    http_codes.BAD_REQUEST,
+                    message=f"Kan inte ändra till ogiltigt sidordning '{new_alternative_order}'",
+                )
 
             item_question = dbc.get.one(Question, question_id)
             dbc.utils.move_order(
@@ -94,25 +104,20 @@ class QuestionAlternatives(Resource):
             )
 
         new_correct_order = args.pop("correct_order")
-        if new_correct_order is not sentinel and item.correct_order != new_correct_order:
+        if new_correct_order is not None and item.correct_order != new_correct_order:
             if not (0 <= new_correct_order < dbc.utils.count(QuestionAlternative, {"question_id": question_id})):
-                abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_correct_order}'")
+                abort(http_codes.BAD_REQUEST, message=f"Kan inte ändra till ogiltigt sidordning '{new_correct_order}'")
 
             item_question = dbc.get.one(Question, question_id)
             dbc.utils.move_order(item_question.alternatives, "correct_order", item.correct_order, new_correct_order)
 
-        item = dbc.edit.default(item, **args)
-        return item_response(schema.dump(item))
+        return dbc.edit.default(item, **args)
 
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find alternative")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not delete alternative")
     def delete(self, competition_id, slide_id, question_id, alternative_id):
         """ Deletes the specified question alternative. """
-
-        item = dbc.get.question_alternative(
-            competition_id,
-            slide_id,
-            question_id,
-            alternative_id,
-        )
-        dbc.delete.default(item)
-        return {}, codes.NO_CONTENT
+        dbc.delete.default(dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id))
+        return None
diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py
index 190f74914c18be00c241f47e2f0a5053b8af13ee..15f99a1c2b7ce569938ee7794ec4679e52bfbe6c 100644
--- a/server/app/apis/answers.py
+++ b/server/app/apis/answers.py
@@ -4,57 +4,55 @@ Default route: /api/competitions/<competition_id>/teams/<team_id>/answers
 """
 
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import QuestionAlternativeAnswerDTO, QuestionScoreDTO
-from app.core.parsers import sentinel
-from flask_restx import Resource, reqparse
+from app.core import ma
+from app.core.schemas import BaseSchema, QuestionAlternativeAnswerSchema
+from app.database import models
+from flask.views import MethodView
 
-api = QuestionAlternativeAnswerDTO.api
-schema = QuestionAlternativeAnswerDTO.schema
-list_schema = QuestionAlternativeAnswerDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-score_schema = QuestionScoreDTO.schema
-score_list_schema = QuestionScoreDTO.list_schema
+blp = ExtendedBlueprint(
+    "answer",
+    "answer",
+    url_prefix="/api/competitions/<competition_id>/teams/<team_id>/answers",
+    description="Adding, updating, deleting and copy answer",
+)
 
 
-answer_parser_add = reqparse.RequestParser()
-answer_parser_add.add_argument("answer", type=str, required=True, location="json")
+class AnswerAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.QuestionAlternativeAnswer
 
-answer_parser_edit = reqparse.RequestParser()
-answer_parser_edit.add_argument("answer", type=str, default=sentinel, location="json")
+    answer = ma.auto_field(required=False)
 
 
-@api.route("/question_alternatives")
-@api.param("competition_id, team_id")
-class QuestionAlternativeList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+@blp.route("")
+class QuestionAlternativeList(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, QuestionAlternativeAnswerSchema)
     def get(self, competition_id, team_id):
         """ Gets all question answers that the specified team has given. """
+        return dbc.get.question_alternative_answer_list(competition_id, team_id)
 
-        items = dbc.get.question_alternative_answer_list(competition_id, team_id)
-        return list_response(list_schema.dump(items))
 
-
-@api.route("/question_alternatives/<question_alternative_id>")
-@api.param("competition_id, team_id, question_alternative_id")
-class QuestionAlternativeAnswers(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
-    def get(self, competition_id, team_id, question_alternative_id):
+@blp.route("/<answer_id>")
+class QuestionAlternativeAnswers(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, QuestionAlternativeAnswerSchema)
+    def get(self, competition_id, team_id, answer_id):
         """ Gets the specified question answer. """
+        return dbc.get.question_alternative_answer(competition_id, team_id, answer_id)
 
-        item = dbc.get.question_alternative_answer(competition_id, team_id, question_alternative_id)
-        return item_response(schema.dump(item))
-
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
-    def put(self, competition_id, team_id, question_alternative_id):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.arguments(AnswerAddArgsSchema)
+    @blp.response(http_codes.OK, QuestionAlternativeAnswerSchema)
+    def put(self, args, competition_id, team_id, answer_id):
         """ Add or edit specified quesiton_answer. """
 
-        item = dbc.get.question_alternative_answer(competition_id, team_id, question_alternative_id, required=False)
+        item = dbc.get.question_alternative_answer(competition_id, team_id, answer_id, required=False)
         if item is None:
-            args = answer_parser_add.parse_args(strict=True)
-            item = dbc.add.question_alternative_answer(args.get("answer"), question_alternative_id, team_id)
+            item = dbc.add.question_alternative_answer(args.get("answer"), answer_id, team_id)
         else:
-            args = answer_parser_edit.parse_args(strict=True)
             item = dbc.edit.default(item, **args)
 
-        return item_response(schema.dump(item))
+        return item
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
index e2bd165dd82b77c96266c810774c84103b8e58a6..18fb5f5d716af23a08e0a126c1cd2ae3e5394da5 100644
--- a/server/app/apis/auth.py
+++ b/server/app/apis/auth.py
@@ -3,35 +3,37 @@ All API calls concerning question answers.
 Default route: /api/auth
 """
 
-from datetime import datetime, timedelta, timezone
+from datetime import datetime, timedelta
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
-from app.apis import item_response, protect_route, text_response
+import marshmallow as ma
 from app.core.codes import verify_code
-from app.core.dto import AuthDTO
 from app.core.sockets import is_active_competition
-from app.database.models import User, Whitelist
+from app.database.controller.delete import whitelist_to_blacklist
+from app.database.models import Whitelist
 from flask import current_app, has_app_context
-from flask_jwt_extended import create_access_token, get_jti, get_raw_jwt
-from flask_jwt_extended.utils import get_jti
-from flask_restx import Resource, inputs, reqparse
+from flask.views import MethodView
+from flask_jwt_extended import create_access_token, get_jti
+from flask_jwt_extended.utils import get_jti, get_jwt
+from flask_smorest import abort
+from flask_smorest.error_handler import ErrorSchema
 
-api = AuthDTO.api
-schema = AuthDTO.schema
-list_schema = AuthDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-login_parser = reqparse.RequestParser()
-login_parser.add_argument("email", type=inputs.email(), required=True, location="json")
-login_parser.add_argument("password", type=str, required=True, location="json")
+blp = ExtendedBlueprint(
+    "auth", "auth", url_prefix="/api/auth", description="Logging in as a user or with a code, and logging out"
+)
 
-create_user_parser = login_parser.copy()
-create_user_parser.add_argument("city_id", type=int, required=True, location="json")
-create_user_parser.add_argument("role_id", type=int, required=True, location="json")
-create_user_parser.add_argument("name", type=str, required=False, location="json")
 
-login_code_parser = reqparse.RequestParser()
-login_code_parser.add_argument("code", type=str, required=True, location="json")
+class UserLoginArgsSchema(ma.Schema):
+    email = ma.fields.Email(required=True)
+    password = ma.fields.String(required=True)
+
+
+class UserLoginResponseSchema(ma.Schema):
+    id = ma.fields.Int()
+    access_token = ma.fields.String()
+
 
 if has_app_context():
     USER_LOGIN_LOCKED_ATTEMPTS = current_app.config["USER_LOGIN_LOCKED_ATTEMPTS"]
@@ -39,13 +41,13 @@ if has_app_context():
 
 
 def get_user_claims(item_user):
-    """ Gets user details for jwt-token. """
+    """ Gets user details for jwt. """
 
     return {"role": item_user.role.name, "city_id": item_user.city_id}
 
 
 def get_code_claims(item_code):
-    """ Gets code details for jwt-token. """
+    """ Gets code details for jwt. """
 
     return {
         "view": item_code.view_type.name,
@@ -55,65 +57,29 @@ def get_code_claims(item_code):
     }
 
 
-@api.route("/test")
-class AuthSignup(Resource):
-    @protect_route(allowed_roles=["Admin"], allowed_views=["*"])
+@blp.route("/test")
+class AuthSignup(MethodView):
+    @blp.authorization(allowed_roles=["Admin"], allowed_views=["*"])
+    @blp.response(http_codes.NO_CONTENT, None)
     def get(self):
-        """ Tests that the user is an admin. """
+        """ Tests that the user is admin or is in a competition. """
+        return None
 
-        return "ok"
 
+@blp.route("/login")
+class AuthLogin(MethodView):
+    @blp.arguments(UserLoginArgsSchema)
+    @blp.response(http_codes.OK, UserLoginResponseSchema)
+    def post(self, args):
+        """ Logs in the specified user and creates a jwt. """
 
-@api.route("/signup")
-class AuthSignup(Resource):
-    @protect_route(allowed_roles=["Admin"])
-    def post(self):
-        """ Creates a new user if the user does not already exist. """
-
-        args = create_user_parser.parse_args(strict=True)
-        email = args.get("email")
-
-        # Check if email is already used
-        if dbc.get.user_exists(email):
-            api.abort(codes.BAD_REQUEST, "User already exists")
-
-        # Add user
-        item_user = dbc.add.user(**args)
-        return item_response(schema.dump(item_user))
-
-
-@api.route("/delete/<user_id>")
-@api.param("user_id")
-class AuthDelete(Resource):
-    @protect_route(allowed_roles=["Admin"])
-    def delete(self, user_id):
-        """ Deletes the specified user and adds their token to the blacklist. """
-
-        item_user = dbc.get.one(User, user_id)
-
-        # Blacklist all the whitelisted tokens
-        # in use for the user that will be deleted
-        dbc.delete.whitelist_to_blacklist(Whitelist.user_id == user_id)
-
-        # Delete user
-        dbc.delete.default(item_user)
-        return text_response(f"User {user_id} deleted")
-
-
-@api.route("/login")
-class AuthLogin(Resource):
-    def post(self):
-        """ Logs in the specified user and creates a jwt-token. """
-
-        args = login_parser.parse_args(strict=True)
         email = args.get("email")
         password = args.get("password")
-
         item_user = dbc.get.user_by_email(email)
 
-        # Login with unkown email
+        # Login with unknown email
         if not item_user:
-            api.abort(codes.UNAUTHORIZED, "Invalid email or password")
+            abort(http_codes.UNAUTHORIZED, "Ogiltigt användarnamn eller lösenord")
 
         now = datetime.now()
 
@@ -127,15 +93,13 @@ class AuthLogin(Resource):
                 item_user.locked = now + USER_LOGIN_LOCKED_EXPIRES
 
             dbc.utils.commit()
-            api.abort(codes.UNAUTHORIZED, "Invalid email or password")
+            abort(http_codes.UNAUTHORIZED, "Ogiltigt användarnamn eller lösenord")
 
         # Otherwise if login was successful but the user is locked
         if item_user.locked:
-            print(item_user.locked)
-            print(now)
             # Check if locked is greater than now
             if item_user.locked.timestamp() > now.timestamp():
-                api.abort(codes.UNAUTHORIZED, f"Try again in {item_user.locked} hours.")
+                abort(http_codes.UNAUTHORIZED, f"Kontot låst, försök igen om {item_user.locked} timmar")
             else:
                 item_user.locked = None
 
@@ -144,79 +108,65 @@ class AuthLogin(Resource):
         dbc.utils.commit()
 
         # Create the jwt with user.id as the identifier
-        access_token = create_access_token(item_user.id, user_claims=get_user_claims(item_user))
-
-        # Login response includes the id and jwt for the user
-        response = {"id": item_user.id, "access_token": access_token}
+        access_token = create_access_token(item_user.id, additional_claims=get_user_claims(item_user))
 
         # Whitelist the created jwt
         dbc.add.whitelist(get_jti(access_token), item_user.id)
-        return response
 
+        # Login response includes the id and jwt for the user
+        return {"id": item_user.id, "access_token": access_token}
 
-@api.route("/login/code")
-class AuthLoginCode(Resource):
+
+@blp.route("/logout")
+class AuthLogout(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
     def post(self):
+        """ Logs out. """
+        whitelist_to_blacklist(Whitelist.jti == get_jwt()["jti"])
+        return None
+
+
+class CodeArgsSchema(ma.Schema):
+    code = ma.fields.String(required=True)
+
+
+class CodeResponseSchema(ma.Schema):
+    competition_id = ma.fields.Int()
+    view = ma.fields.String()
+    team_id = ma.fields.Int()
+    access_token = ma.fields.String()
+
+
+@blp.route("/code")
+class AuthLoginCode(MethodView):
+    @blp.arguments(CodeArgsSchema)
+    @blp.response(http_codes.OK, CodeResponseSchema)
+    @blp.alt_response(http_codes.UNAUTHORIZED, ErrorSchema, description="Incorrect code or competition is not active")
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="The code doesn't exist")
+    def post(self, args):
         """ Logs in using the provided competition code. """
 
-        args = login_code_parser.parse_args()
         code = args["code"]
 
-        # Check so the code string is valid
-        if not verify_code(code):
-            api.abort(codes.UNAUTHORIZED, "Invalid code")
+        if not verify_code(code):  # Check that code string is valid
+            abort(http_codes.UNAUTHORIZED, message="Felaktigt kod")
 
         item_code = dbc.get.code_by_code(code)
 
-        if item_code.view_type_id != 4:
-            if not is_active_competition(item_code.competition_id):
-                api.abort(codes.UNAUTHORIZED, "Competition not active")
+        # If joining client is not operator and competition is not active
+        if item_code.view_type_id != 4 and not is_active_competition(item_code.competition_id):
+            abort(http_codes.UNAUTHORIZED, message="Tävlingen är ej aktiv")
 
         # Create jwt that is only valid for 8 hours
         access_token = create_access_token(
-            item_code.id, user_claims=get_code_claims(item_code), expires_delta=timedelta(hours=8)
+            item_code.id, additional_claims=get_code_claims(item_code), expires_delta=timedelta(hours=8)
         )
+        dbc.add.whitelist(get_jti(access_token), competition_id=item_code.competition_id)  # Whitelist the created jwt
 
-        # Whitelist the created jwt
-        dbc.add.whitelist(get_jti(access_token), competition_id=item_code.competition_id)
-        response = {
+        return {
             "competition_id": item_code.competition_id,
             "view": item_code.view_type.name,
             "team_id": item_code.team_id,
             "access_token": access_token,
         }
-        return response
-
-
-@api.route("/logout")
-class AuthLogout(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
-    def post(self):
-        """ Logs out. """
-
-        jti = get_raw_jwt()["jti"]
-
-        # Blacklist the token so the user cannot access the api anymore
-        dbc.add.blacklist(jti)
-
-        # Remove the the token from the whitelist since it's blacklisted now
-        Whitelist.query.filter(Whitelist.jti == jti).delete()
-
-        dbc.utils.commit()
-        return text_response("Logout")
-
-
-"""
-@api.route("/refresh")
-class AuthRefresh(Resource):
-    @protect_route(allowed_roles=["*"])
-    @jwt_refresh_token_required
-    def post(self):
-        old_jti = get_raw_jwt()["jti"]
-
-        item_user = dbc.get.user(get_jwt_identity())
-        access_token = create_access_token(item_user.id, user_claims=get_user_claims(item_user))
-        dbc.add.blacklist(old_jti)
-        response = {"access_token": access_token}
-        return response
-"""
diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py
index 45f9578280e82c306ff716a646505dbffd62be3b..8ea7901763bf85e01417ecabadeb843c3b7d1dcc 100644
--- a/server/app/apis/codes.py
+++ b/server/app/apis/codes.py
@@ -4,35 +4,32 @@ Default route: /api/competitions/<competition_id>/codes
 """
 
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import CodeDTO
+from app.core.schemas import CodeSchema
 from app.database.models import Code
-from flask_restx import Resource
+from flask.views import MethodView
+from flask_smorest.error_handler import ErrorSchema
 
-api = CodeDTO.api
-schema = CodeDTO.schema
-list_schema = CodeDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
+blp = ExtendedBlueprint(
+    "code", "code", url_prefix="/api/competitions/<competition_id>/codes", description="Operations on codes"
+)
 
-@api.route("")
-@api.param("competition_id")
-class CodesList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["Operator"])
+
+@blp.route("")
+class CodesList(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=["Operator"])
+    @blp.response(http_codes.OK, CodeSchema(many=True))
     def get(self, competition_id):
         """ Gets the all competition codes. """
-
-        items = dbc.get.code_list(competition_id)
-        return list_response(list_schema.dump(items), len(items))
+        return dbc.get.code_list(competition_id)
 
 
-@api.route("/<code_id>")
-@api.param("competition_id, code_id")
-class CodesById(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<code_id>")
+class CodesById(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, CodeSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Code not found")
     def put(self, competition_id, code_id):
         """ Generates a new competition code. """
-
-        item = dbc.get.one(Code, code_id)
-        item.code = dbc.utils.generate_unique_code()
-        dbc.utils.commit_and_refresh(item)
-        return item_response(schema.dump(item))
+        return dbc.edit.default(dbc.get.one(Code, code_id), code=dbc.utils.generate_unique_code())
diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
index 2d381425c9e5d3f41bba347128aab7b659bf8885..4f31c8b6338d5335fc85e6b091694d08ba59fc4d 100644
--- a/server/app/apis/competitions.py
+++ b/server/app/apis/competitions.py
@@ -4,101 +4,106 @@ 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.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.error_handler import ErrorSchema
 
-api = CompetitionDTO.api
-schema = CompetitionDTO.schema
-rich_schema = CompetitionDTO.rich_schema
-list_schema = CompetitionDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-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")
+blp = ExtendedBlueprint(
+    "competitions", "competitions", url_prefix="/api/competitions", description="Operations competitions"
+)
 
-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")
 
-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")
+class CompetitionAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Competition
 
+    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. """
 
-        args = competition_parser_add.parse_args(strict=True)
+class CompetitionEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Competition
 
-        # Add competition
-        item = dbc.add.competition(**args)
+    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 default slide
-        # dbc.add.slide(item.id)
-        return item_response(schema.dump(item))
 
+class CompetitionSearchArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Competition
 
-@api.route("/<competition_id>")
-@api.param("competition_id")
-class Competitions(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
-    def get(self, competition_id):
-        """ Gets the specified 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)
 
-        item = dbc.get.competition(competition_id)
 
-        return item_response(rich_schema.dump(item))
-
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id):
-        """ Edits the specified competition with the specified arguments. """
+@blp.route("")
+class Competitions(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(CompetitionAddArgsSchema)
+    @blp.response(http_codes.OK, CompetitionSchema)
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Competition could not be added")
+    def post(self, args):
+        """ Adds a new competition. """
+        return dbc.add.competition(**args)
 
-        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))
+@blp.route("/<competition_id>")
+class CompetitionById(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, CompetitionSchemaRich)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Competition not found")
+    def get(self, competition_id):
+        """ Gets the specified competition. """
+        return dbc.get.competition(competition_id)
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(CompetitionEditArgsSchema)
+    @blp.response(http_codes.OK, CompetitionSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Competition not found")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Competition could not be updated")
+    def put(self, args, competition_id):
+        """ Edits the specified competition with the specified arguments. """
+        return dbc.edit.default(dbc.get.one(Competition, competition_id), **args)
 
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Competition not found")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, 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):
-    @protect_route(allowed_roles=["*"])
-    def get(self):
+@blp.route("/search")
+class CompetitionSearch(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @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):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<competition_id>/copy")
+class SlidesOrder(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, CompetitionSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Competition not found")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, 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/components.py b/server/app/apis/components.py
index 39e1932824f6e6ab103f773402bb1c3feb331e43..b72053fa8b861dec25c658295f43de4fc862ad49 100644
--- a/server/app/apis/components.py
+++ b/server/app/apis/components.py
@@ -3,97 +3,100 @@ All API calls concerning competitions.
 Default route: /api/competitions/<competition_id>/slides/<slide_id>/components
 """
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import ComponentDTO
-from app.core.parsers import sentinel
-from flask_restx import Resource, reqparse
-
-api = ComponentDTO.api
-schema = ComponentDTO.schema
-list_schema = ComponentDTO.list_schema
-
-component_parser_add = reqparse.RequestParser()
-component_parser_add.add_argument("x", type=int, default=0, location="json")
-component_parser_add.add_argument("y", type=int, default=0, location="json")
-component_parser_add.add_argument("w", type=int, default=1, location="json")
-component_parser_add.add_argument("h", type=int, default=1, location="json")
-component_parser_add.add_argument("type_id", type=int, required=True, location="json")
-component_parser_add.add_argument("view_type_id", type=int, required=True, location="json")
-component_parser_add.add_argument("text", type=str, default=None, location="json")
-component_parser_add.add_argument("media_id", type=int, default=None, location="json")
-component_parser_add.add_argument("question_id", type=int, default=None, location="json")
-
-component_parser_edit = reqparse.RequestParser()
-component_parser_edit.add_argument("x", type=int, default=sentinel, location="json")
-component_parser_edit.add_argument("y", type=int, default=sentinel, location="json")
-component_parser_edit.add_argument("w", type=int, default=sentinel, location="json")
-component_parser_edit.add_argument("h", type=int, default=sentinel, location="json")
-component_parser_edit.add_argument("text", type=str, default=sentinel, location="json")
-component_parser_edit.add_argument("media_id", type=int, default=sentinel, location="json")
-component_parser_edit.add_argument("question_id", type=int, default=sentinel, location="json")
-
-
-@api.route("")
-@api.param("competition_id, slide_id")
-class ComponentList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+from app.core.schemas import BaseSchema, ComponentSchema
+from flask.views import MethodView
+from flask_smorest.error_handler import ErrorSchema
+from marshmallow import fields
+
+from . import ALL, ExtendedBlueprint, http_codes
+
+blp = ExtendedBlueprint(
+    "component",
+    "component",
+    url_prefix="/api/competitions/<competition_id>/slides/<slide_id>/components",
+    description="Adding, updating, deleting and copy components",
+)
+
+
+class ComponentAddArgsSchema(BaseSchema):
+
+    x = fields.Integer(required=False, missing=0)
+    y = fields.Integer(required=False, missing=0)
+    w = fields.Integer(required=False, missing=1)
+    h = fields.Integer(required=False, missing=1)
+
+    type_id = fields.Integer(required=True)
+    view_type_id = fields.Integer(required=True)
+
+    text = fields.String(required=False)
+    media_id = fields.Integer(required=False)
+    question_id = fields.Integer(required=False)
+
+
+class ComponentEditArgsSchema(BaseSchema):
+
+    x = fields.Integer(required=False)
+    y = fields.Integer(required=False)
+    w = fields.Integer(required=False)
+    h = fields.Integer(required=False)
+
+    type_id = fields.Integer(required=False)
+    view_type_id = fields.Integer(required=False)
+
+    text = fields.String(required=False)
+    media_id = fields.Integer(required=False)
+    question_id = fields.Integer(required=False)
+
+
+@blp.route("")
+class Components(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, ComponentSchema(many=True))
     def get(self, competition_id, slide_id):
         """ Gets all components in the specified slide and competition. """
+        return dbc.get.component_list(competition_id, slide_id)
 
-        items = dbc.get.component_list(competition_id, slide_id)
-        return list_response(list_schema.dump(items))
-
-    @protect_route(allowed_roles=["*"])
-    def post(self, competition_id, slide_id):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(ComponentAddArgsSchema)
+    @blp.response(http_codes.OK, ComponentSchema)
+    def post(self, args, competition_id, slide_id):
         """ Posts a new component to the specified slide. """
-
-        args = component_parser_add.parse_args()
-        item = dbc.add.component(slide_id=slide_id, **args)
-        return item_response(schema.dump(item))
+        return dbc.add.component(slide_id=slide_id, **args)
 
 
-@api.route("/<component_id>")
-@api.param("competition_id, slide_id, component_id")
-class ComponentByID(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+@blp.route("/<component_id>")
+class ComponentById(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, ComponentSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find component")
     def get(self, competition_id, slide_id, component_id):
         """ Gets the specified component. """
-
-        item = dbc.get.component(competition_id, slide_id, component_id)
-        return item_response(schema.dump(item))
-
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id, slide_id, component_id):
+        return dbc.get.component(competition_id, slide_id, component_id)
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(ComponentEditArgsSchema)
+    @blp.response(http_codes.OK, ComponentSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find component")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not update component with given values")
+    def put(self, args, competition_id, slide_id, component_id):
         """ Edits the specified component using the provided arguments. """
+        return dbc.edit.default(dbc.get.component(competition_id, slide_id, component_id), **args)
 
-        args = component_parser_edit.parse_args(strict=True)
-        item = dbc.get.component(competition_id, slide_id, component_id)
-        args_without_sentinel = {key: value for key, value in args.items() if value is not sentinel}
-        item = dbc.edit.default(item, **args_without_sentinel)
-        return item_response(schema.dump(item))
-
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find component")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not delete component")
     def delete(self, competition_id, slide_id, component_id):
         """ Deletes the specified component. """
+        dbc.delete.component(dbc.get.component(competition_id, slide_id, component_id))
+        return None
 
-        item = dbc.get.component(competition_id, slide_id, component_id)
-        dbc.delete.component(item)
-        return {}, codes.NO_CONTENT
 
-
-@api.route("/<component_id>/copy/<view_type_id>")
-@api.param("competition_id, slide_id, component_id, view_type_id")
-class ComponentList(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<component_id>/copy/<view_type_id>")
+class ComponentCopy(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, ComponentSchema)
     def post(self, competition_id, slide_id, component_id, view_type_id):
         """ Creates a deep copy of the specified component. """
-
-        item_component = dbc.get.component(
-            competition_id,
-            slide_id,
-            component_id,
-        )
-        item = dbc.copy.component(item_component, slide_id, view_type_id)
-        return item_response(schema.dump(item))
+        return dbc.copy.component(dbc.get.component(competition_id, slide_id, component_id), slide_id, view_type_id)
diff --git a/server/app/apis/media.py b/server/app/apis/media.py
index 3d95bd370c49c9953898377fc1aabb16d74ab877..a20cd1b0bdba88518be7a430f5ea71f910d71adc 100644
--- a/server/app/apis/media.py
+++ b/server/app/apis/media.py
@@ -4,85 +4,94 @@ Default route: /api/media
 """
 
 import app.core.files as files
-import app.core.http_codes as codes
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import MediaDTO
-from app.core.parsers import search_parser, sentinel
-from app.database.models import Media
+from app.core import ma
+from app.core.schemas import BaseSchema, MediaSchema
+from app.database import models
 from flask import request
+from flask.views import MethodView
 from flask_jwt_extended import get_jwt_identity
-from flask_restx import Resource
+from flask_smorest import abort
+from flask_smorest.error_handler import ErrorSchema
 from flask_uploads import UploadNotAllowed
 from sqlalchemy import exc
 
-api = MediaDTO.api
-image_set = MediaDTO.image_set
-schema = MediaDTO.schema
-list_schema = MediaDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-media_parser_search = search_parser.copy()
-media_parser_search.add_argument("filename", type=str, default=sentinel, location="args")
 
+class ImageSearchArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Media
+
+    filename = ma.auto_field(required=False)
 
-@api.route("/images")
-class ImageList(Resource):
-    @protect_route(allowed_roles=["*"])
-    def get(self):
-        """ Gets a list of all images with the specified filename. """
 
-        args = media_parser_search.parse_args(strict=True)
-        items, total = dbc.search.image(**args)
-        return list_response(list_schema.dump(items), total)
+blp = ExtendedBlueprint("media", "media", url_prefix="/api/media", description="Operations on media")
 
-    @protect_route(allowed_roles=["*"])
+
+@blp.route("/images")
+class Images(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(ImageSearchArgsSchema, location="query")
+    @blp.paginate()
+    @blp.response(http_codes.OK, MediaSchema(many=True))
+    def get(self, args, pagination_parameters):
+        """ Gets a list of all images with the specified filename. """
+        return dbc.search.image(pagination_parameters, **args)
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, MediaSchema)
+    @blp.alt_response(http_codes.BAD_REQUEST, ErrorSchema, description="Could not save image")
+    @blp.alt_response(http_codes.INTERNAL_SERVER_ERROR, ErrorSchema, description="Could not save image")
     def post(self):
         """ Posts the specified image. """
 
         if "image" not in request.files:
-            api.abort(codes.BAD_REQUEST, "Missing image in request.files")
+            abort(http_codes.BAD_REQUEST, message="Missing image in request.files")
         try:
             filename = files.save_image_with_thumbnail(request.files["image"])
-            item = Media.query.filter(Media.filename == filename).first()
+            item = models.Media.query.filter(models.Media.filename == filename).first()
             if not item:
                 item = dbc.add.image(filename, get_jwt_identity())
 
-            return item_response(schema.dump(item))
+            return item
         except UploadNotAllowed:
-            api.abort(codes.BAD_REQUEST, "Could not save the image")
+            abort(http_codes.BAD_REQUEST, message="Could not save the image")
         except:
-            api.abort(codes.INTERNAL_SERVER_ERROR, "Something went wrong when trying to save image")
+            abort(http_codes.INTERNAL_SERVER_ERROR, message="Something went wrong when trying to save image")
 
 
-@api.route("/images/<media_id>")
-@api.param("media_id")
-class ImageList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+@blp.route("/images/<media_id>")
+class ImageById(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, MediaSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, MediaSchema, description="Could not find image")
     def get(self, media_id):
         """ Gets the specified image. """
+        return dbc.get.one(models.Media, media_id)
 
-        item = dbc.get.one(Media, media_id)
-        return item_response(schema.dump(item))
-
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.response(http_codes.CONFLICT, None, description="Could not delete image it is used by something")
+    @blp.response(http_codes.BAD_REQUEST, None, description="Failed to delete image")
+    @blp.response(http_codes.INTERNAL_SERVER_ERROR, ErrorSchema, description="Somehting very serious went wrong")
     def delete(self, media_id):
         """ Deletes the specified image. """
-
-        item = dbc.get.one(Media, media_id)
+        item = dbc.get.one(models.Media, media_id)
         if len(item.image_components) > 0:
-            api.abort(codes.CONFLICT, "Component depends on this Image")
+            abort(http_codes.CONFLICT, "Component depends on this Image")
 
         if len(item.competition_background_images) > 0:
-            api.abort(codes.CONFLICT, "Competition background image depends on this Image")
+            abort(http_codes.CONFLICT, "Competition background image depends on this Image")
 
         if len(item.slide_background_images) > 0:
-            api.abort(codes.CONFLICT, "Slide background image depends on this Image")
+            abort(http_codes.CONFLICT, "Slide background image depends on this Image")
 
         try:
             files.delete_image_and_thumbnail(item.filename)
             dbc.delete.default(item)
-            return {}, codes.NO_CONTENT
+            return None
         except OSError:
-            api.abort(codes.BAD_REQUEST, "Could not delete the file image")
+            abort(http_codes.BAD_REQUEST, "Could not delete the file image")
         except exc.SQLAlchemyError:
-            api.abort(codes.INTERNAL_SERVER_ERROR, "Something went wrong when trying to delete image")
+            abort(http_codes.INTERNAL_SERVER_ERROR, "Something went wrong when trying to delete image")
diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index 0f4aba74c8c5edb467f08b1c66e0e4801aa5506b..713a707db73f56cf5d8e332f615c4a3c558ba52d 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -4,100 +4,116 @@ Default route: /api/misc
 """
 
 import app.database.controller as dbc
-from app.apis import list_response, protect_route
-from app.core import http_codes
-from app.core.dto import MiscDTO
+import marshmallow as ma
+from app.core.schemas import (
+    BaseSchema,
+    CitySchema,
+    ComponentTypeSchema,
+    MediaTypeSchema,
+    QuestionTypeSchema,
+    RoleSchema,
+    ViewTypeSchema,
+)
+from app.database import models
 from app.database.models import City, Competition, ComponentType, MediaType, QuestionType, Role, User, ViewType
-from flask_restx import Resource, reqparse
+from flask.views import MethodView
+from flask_smorest.error_handler import ErrorSchema
+from marshmallow_sqlalchemy import auto_field
 
-api = MiscDTO.api
+from . import ALL, ExtendedBlueprint, http_codes
 
-question_type_schema = MiscDTO.question_type_schema
-media_type_schema = MiscDTO.media_type_schema
-component_type_schema = MiscDTO.component_type_schema
-view_type_schema = MiscDTO.view_type_schema
+blp = ExtendedBlueprint("misc", "misc", url_prefix="/api/misc", description="Roles, regions, types and statistics")
 
-role_schema = MiscDTO.role_schema
-city_schema = MiscDTO.city_schema
 
+class TypesResponseSchema(BaseSchema):
+    media_types = ma.fields.Nested(MediaTypeSchema, many=True)
+    component_types = ma.fields.Nested(ComponentTypeSchema, many=True)
+    question_types = ma.fields.Nested(QuestionTypeSchema, many=True)
+    view_types = ma.fields.Nested(ViewTypeSchema, many=True)
 
-name_parser = reqparse.RequestParser()
-name_parser.add_argument("name", type=str, required=True, location="json")
 
-
-@api.route("/types")
-class TypesList(Resource):
+@blp.route("/types")
+class Types(MethodView):
+    @blp.response(http_codes.OK, TypesResponseSchema)
     def get(self):
-        """ Gets a list of all types. """
-
-        result = {}
-        result["media_types"] = media_type_schema.dump(dbc.get.all(MediaType))
-        result["component_types"] = component_type_schema.dump(dbc.get.all(ComponentType))
-        result["question_types"] = question_type_schema.dump(dbc.get.all(QuestionType))
-        result["view_types"] = view_type_schema.dump(dbc.get.all(ViewType))
-        return result
-
-
-@api.route("/roles")
-class RoleList(Resource):
-    @protect_route(allowed_roles=["*"])
+        """ Gets a list of all types """
+        return dict(
+            media_types=dbc.get.all(MediaType),
+            component_types=dbc.get.all(ComponentType),
+            question_types=dbc.get.all(QuestionType),
+            view_types=dbc.get.all(ViewType),
+        )
+
+
+@blp.route("/roles")
+class RoleList(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, RoleSchema(many=True))
     def get(self):
         """ Gets a list of all roles. """
+        return dbc.get.all(Role)
 
-        items = dbc.get.all(Role)
-        return list_response(role_schema.dump(items))
 
+class CityAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.City
 
-@api.route("/cities")
-class CitiesList(Resource):
-    @protect_route(allowed_roles=["*"])
+    name = auto_field()
+
+
+@blp.route("/cities")
+class CitiesList(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, CitySchema(many=True))
     def get(self):
         """ Gets a list of all cities. """
+        return dbc.get.all(City)
 
-        items = dbc.get.all(City)
-        return list_response(city_schema.dump(items))
-
-    @protect_route(allowed_roles=["Admin"])
-    def post(self):
+    @blp.authorization(allowed_roles=["Admin"])
+    @blp.arguments(CitySchema)
+    @blp.response(http_codes.OK, CitySchema(many=True))
+    def post(self, args):
         """ Posts the specified city. """
-
-        args = name_parser.parse_args(strict=True)
-        dbc.add.city(args["name"])
-        items = dbc.get.all(City)
-        return list_response(city_schema.dump(items))
-
-
-@api.route("/cities/<ID>")
-@api.param("ID")
-class Cities(Resource):
-    @protect_route(allowed_roles=["Admin"])
-    def put(self, ID):
+        dbc.add.city(**args)
+        return dbc.get.all(City)
+
+
+@blp.route("/cities/<city_id>")
+class Cities(MethodView):
+    @blp.authorization(allowed_roles=["Admin"])
+    @blp.arguments(CitySchema)
+    @blp.response(http_codes.OK, CitySchema(many=True))
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="City not found")
+    @blp.alt_response(
+        http_codes.CONFLICT, ErrorSchema, description="The city can't be updated with the provided values"
+    )
+    def put(self, args, city_id):
         """ Edits the specified city with the provided arguments. """
-
-        item = dbc.get.one(City, ID)
-        args = name_parser.parse_args(strict=True)
-        item.name = args["name"]
-        dbc.utils.commit_and_refresh(item)
-        items = dbc.get.all(City)
-        return list_response(city_schema.dump(items))
-
-    @protect_route(allowed_roles=["Admin"])
-    def delete(self, ID):
+        dbc.edit.default(dbc.get.one(City, city_id), **args)
+        return dbc.get.all(City)
+
+    @blp.authorization(allowed_roles=["Admin"])
+    @blp.response(http_codes.OK, CitySchema(many=True))
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="City not found")
+    @blp.alt_response(
+        http_codes.CONFLICT, ErrorSchema, description="The city can't be updated with the provided values"
+    )
+    def delete(self, city_id):
         """ Deletes the specified city. """
+        dbc.delete.default(dbc.get.one(City, city_id))
+        return dbc.get.all(City)
+
 
-        item = dbc.get.one(City, ID)
-        dbc.delete.default(item)
-        items = dbc.get.all(City)
-        return list_response(city_schema.dump(items))
+class StatisticsResponseSchema(BaseSchema):
+    users = ma.fields.Int()
+    competitions = ma.fields.Int()
+    regions = ma.fields.Int()
 
 
-@api.route("/statistics")
-class Statistics(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/statistics")
+class Statistics(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, StatisticsResponseSchema)
     def get(self):
         """ Gets statistics. """
-
-        user_count = dbc.utils.count(User)
-        competition_count = dbc.utils.count(Competition)
-        region_count = dbc.utils.count(City)
-        return {"users": user_count, "competitions": competition_count, "regions": region_count}, http_codes.OK
+        return {"users": User.query.count(), "competitions": Competition.query.count(), "regions": City.query.count()}
diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py
index 6ba32382fa524180c76eef35bd02a68382e552c6..092c91d5460fbc877d05d2f67bf00130151eb4e3 100644
--- a/server/app/apis/questions.py
+++ b/server/app/apis/questions.py
@@ -3,87 +3,85 @@ All API calls concerning question answers.
 Default route: /api/competitions/<competition_id>
 """
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import QuestionDTO
-from app.core.parsers import sentinel
-from flask_restx import Resource, reqparse
-
-api = QuestionDTO.api
-schema = QuestionDTO.schema
-list_schema = QuestionDTO.list_schema
-
-question_parser_add = reqparse.RequestParser()
-question_parser_add.add_argument("name", type=str, default=None, location="json")
-question_parser_add.add_argument("total_score", type=int, default=None, location="json")
-question_parser_add.add_argument("type_id", type=int, required=True, location="json")
-question_parser_add.add_argument("correcting_instructions", type=str, default=None, location="json")
-
-question_parser_edit = reqparse.RequestParser()
-question_parser_edit.add_argument("name", type=str, default=sentinel, location="json")
-question_parser_edit.add_argument("total_score", type=int, default=sentinel, location="json")
-question_parser_edit.add_argument("type_id", type=int, default=sentinel, location="json")
-question_parser_edit.add_argument("correcting_instructions", type=str, default=sentinel, location="json")
-
-
-@api.route("/questions")
-@api.param("competition_id")
-class QuestionList(Resource):
-    @protect_route(allowed_roles=["*"])
-    def get(self, competition_id):
-        """ Gets all questions in the specified competition. """
-
-        items = dbc.get.question_list_for_competition(competition_id)
-        return list_response(list_schema.dump(items))
-
-
-@api.route("/slides/<slide_id>/questions")
-@api.param("competition_id, slide_id")
-class QuestionListForSlide(Resource):
-    @protect_route(allowed_roles=["*"])
+from app.core import ma
+from app.core.schemas import BaseSchema, QuestionSchema
+from app.database import models
+from flask.views import MethodView
+from flask_smorest.error_handler import ErrorSchema
+
+from . import ALL, ExtendedBlueprint, http_codes
+
+blp = ExtendedBlueprint(
+    "question",
+    "question",
+    url_prefix="/api/competitions/<competition_id>/slides/<slide_id>/questions",
+    description="Adding, updating and deleting questions",
+)
+
+
+class QuestionAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Question
+
+    name = ma.auto_field(required=False, missing="")
+    total_score = ma.auto_field(required=False, missing=None)
+    type_id = ma.auto_field(required=True)
+    correcting_instructions = ma.auto_field(required=False, missing=None)
+
+
+class QuestionEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Question
+
+    name = ma.auto_field(required=False)
+    total_score = ma.auto_field(required=False)
+    type_id = ma.auto_field(required=False)
+    correcting_instructions = ma.auto_field(required=False)
+
+
+@blp.route("")
+class Questions(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, QuestionSchema(many=True))
     def get(self, competition_id, slide_id):
         """ Gets all questions in the specified competition and slide. """
+        return dbc.get.question_list(competition_id, slide_id)
 
-        items = dbc.get.question_list(competition_id, slide_id)
-        return list_response(list_schema.dump(items))
-
-    @protect_route(allowed_roles=["*"])
-    def post(self, competition_id, slide_id):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(QuestionAddArgsSchema)
+    @blp.response(http_codes.OK, QuestionSchema)
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not add question")
+    def post(self, args, competition_id, slide_id):
         """ Posts a new question to the specified slide using the provided arguments. """
-
-        args = question_parser_add.parse_args(strict=True)
-        item = dbc.add.question(slide_id=slide_id, **args)
-        return item_response(schema.dump(item))
+        return dbc.add.question(slide_id=slide_id, **args)
 
 
-@api.route("/slides/<slide_id>/questions/<question_id>")
-@api.param("competition_id, slide_id, question_id")
-class QuestionById(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<question_id>")
+class QuestionById(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, QuestionSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find question")
     def get(self, competition_id, slide_id, question_id):
         """
         Gets the specified question using the specified competition and slide.
         """
-
-        item_question = dbc.get.question(competition_id, slide_id, question_id)
-        return item_response(schema.dump(item_question))
-
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id, slide_id, question_id):
+        return dbc.get.question(competition_id, slide_id, question_id)
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(QuestionEditArgsSchema)
+    @blp.response(http_codes.OK, QuestionSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find question")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not edit question")
+    def put(self, args, competition_id, slide_id, question_id):
         """ Edits the specified question with the provided arguments. """
+        return dbc.edit.default(dbc.get.question(competition_id, slide_id, question_id), **args)
 
-        args = question_parser_edit.parse_args(strict=True)
-
-        item_question = dbc.get.question(competition_id, slide_id, question_id)
-        item_question = dbc.edit.default(item_question, **args)
-
-        return item_response(schema.dump(item_question))
-
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find question")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not delete question")
     def delete(self, competition_id, slide_id, question_id):
         """ Deletes the specified question. """
-
-        item_question = dbc.get.question(competition_id, slide_id, question_id)
-        dbc.delete.question(item_question)
-        return {}, codes.NO_CONTENT
+        dbc.delete.question(dbc.get.question(competition_id, slide_id, question_id))
+        return None
diff --git a/server/app/apis/scores.py b/server/app/apis/scores.py
index 2d303e1c734b5f776334ea2775fdb1b165db3480..8e7208d685538b0e83970cb2accff3f5caa93938 100644
--- a/server/app/apis/scores.py
+++ b/server/app/apis/scores.py
@@ -4,53 +4,59 @@ Default route: /api/competitions/<competition_id>/teams/<team_id>/answers/quesit
 """
 
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import QuestionScoreDTO
-from app.core.parsers import sentinel
-from flask_restx import Resource, reqparse
+from app.core import ma
+from app.core.schemas import BaseSchema, QuestionScoreSchema
+from app.database import models
+from flask.views import MethodView
+from flask_smorest.error_handler import ErrorSchema
 
-api = QuestionScoreDTO.api
-schema = QuestionScoreDTO.schema
-list_schema = QuestionScoreDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-score_parser_add = reqparse.RequestParser()
-score_parser_add.add_argument("score", type=int, required=False, location="json")
+blp = ExtendedBlueprint(
+    "score",
+    "score",
+    url_prefix="/api/competitions/<competition_id>/teams/<team_id>/scores",
+    description="Operations on scores",
+)
 
-score_parser_edit = reqparse.RequestParser()
-score_parser_edit.add_argument("score", type=int, default=sentinel, location="json")
 
+class ScoreAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.QuestionScore
 
-@api.route("/")
-@api.param("competition_id, team_id")
-class QuestionScoreList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+    score = ma.auto_field(required=False)
+
+
+@blp.route("")
+class QuestionScoreList(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, QuestionScoreSchema(many=True))
     def get(self, competition_id, team_id):
         """ Gets all question answers that the specified team has given. """
-
-        items = dbc.get.question_score_list(competition_id, team_id)
-        return list_response(list_schema.dump(items))
+        return dbc.get.question_score_list(competition_id, team_id)
 
 
-@api.route("/<question_id>")
-@api.param("competition_id, team_id, question_id")
-class QuestionScores(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+@blp.route("/<question_id>")
+class QuestionScores(MethodView):
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.response(http_codes.OK, QuestionScoreSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Cant find answer")
     def get(self, competition_id, team_id, question_id):
-        """ Gets the specified question answer. """
-
-        item = dbc.get.question_score(competition_id, team_id, question_id)
-        return item_response(schema.dump(item))
-
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
-    def put(self, competition_id, team_id, question_id):
+        """ Gets the score for the provided team on the provided question. """
+        return dbc.get.question_score(competition_id, team_id, question_id)
+
+    @blp.authorization(allowed_roles=ALL, allowed_views=ALL)
+    @blp.arguments(ScoreAddArgsSchema)
+    @blp.response(http_codes.OK, QuestionScoreSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Cant find score")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Can't add or edit score with provided values")
+    def put(self, args, competition_id, team_id, question_id):
         """ Add or edit specified quesiton_answer. """
 
         item = dbc.get.question_score(competition_id, team_id, question_id, required=False)
         if item is None:
-            args = score_parser_add.parse_args(strict=True)
             item = dbc.add.question_score(args.get("score"), question_id, team_id)
         else:
-            args = score_parser_edit.parse_args(strict=True)
             item = dbc.edit.default(item, **args)
 
-        return item_response(schema.dump(item))
+        return item
diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
index 654ee49f6ce02d8e56d6d5529fcbbcf0c0b12af2..86854efc9cf764250bc34d7791503c2362de8a5f 100644
--- a/server/app/apis/slides.py
+++ b/server/app/apis/slides.py
@@ -3,93 +3,95 @@ All API calls concerning question alternatives.
 Default route: /api/competitions/<competition_id>/slides
 """
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import SlideDTO
-from app.core.parsers import sentinel
+from app.core import ma
+from app.core.schemas import BaseSchema, SlideSchema
+from app.database import models
 from app.database.models import Competition, Slide
-from flask_restx import Resource, reqparse
-from flask_restx.errors import abort
+from flask.views import MethodView
+from flask_smorest import abort
+from flask_smorest.error_handler import ErrorSchema
 
-api = SlideDTO.api
-schema = SlideDTO.schema
-list_schema = SlideDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-slide_parser_edit = reqparse.RequestParser()
-slide_parser_edit.add_argument("order", type=int, default=sentinel, location="json")
-slide_parser_edit.add_argument("title", type=str, default=sentinel, location="json")
-slide_parser_edit.add_argument("timer", type=int, default=sentinel, location="json")
-slide_parser_edit.add_argument("order", type=int, default=sentinel, location="json")
-slide_parser_edit.add_argument("background_image_id", default=sentinel, type=int, location="json")
+blp = ExtendedBlueprint(
+    "slide",
+    "slide",
+    url_prefix="/api/competitions/<competition_id>/slides",
+    description="Adding, updating, deleting and copy slide",
+)
 
 
-@api.route("")
-@api.param("competition_id")
-class SlidesList(Resource):
-    @protect_route(allowed_roles=["*"])
+class SlideEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Slide
+
+    title = ma.auto_field(required=False)
+    timer = ma.auto_field(required=False)
+    order = ma.auto_field(required=False, missing=None)
+    background_image_id = ma.auto_field(required=False)
+
+
+@blp.route("")
+class Slides(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, SlideSchema(many=True))
     def get(self, competition_id):
         """ Gets all slides from the specified competition. """
+        return dbc.get.slide_list(competition_id)
 
-        items = dbc.get.slide_list(competition_id)
-        return list_response(list_schema.dump(items))
-
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, SlideSchema)
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Can't add slide")
     def post(self, competition_id):
         """ Posts a new slide to the specified competition. """
+        return dbc.add.slide(competition_id)
 
-        item_slide = dbc.add.slide(competition_id)
-        return item_response(schema.dump(item_slide))
 
-
-@api.route("/<slide_id>")
-@api.param("competition_id, slide_id")
-class Slides(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<slide_id>")
+class Slides(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, SlideSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema)
     def get(self, competition_id, slide_id):
         """ Gets the specified slide. """
-
-        item_slide = dbc.get.slide(competition_id, slide_id)
-        return item_response(schema.dump(item_slide))
-
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id, slide_id):
+        return dbc.get.slide(competition_id, slide_id)
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(SlideEditArgsSchema)
+    @blp.response(http_codes.OK, SlideSchema)
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Can't edit slide")
+    @blp.alt_response(http_codes.BAD_REQUEST, ErrorSchema, description="Can't edit slide with the provided arguments")
+    def put(self, args, competition_id, slide_id):
         """ Edits the specified slide using the provided arguments. """
 
-        args = slide_parser_edit.parse_args(strict=True)
-
         item_slide = dbc.get.slide(competition_id, slide_id)
 
         new_order = args.pop("order")
-        if new_order is not sentinel and item_slide.order != new_order:
+        if new_order is not None and item_slide.order != new_order:
             if not (0 <= new_order < dbc.utils.count(Slide, {"competition_id": competition_id})):
-                abort(codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_order}'")
+                abort(http_codes.BAD_REQUEST, f"Cant change to invalid slide order '{new_order}'")
 
             item_competition = dbc.get.one(Competition, competition_id)
             dbc.utils.move_order(item_competition.slides, "order", item_slide.order, new_order)
 
-        item_slide = dbc.edit.default(item_slide, **args)
-
-        return item_response(schema.dump(item_slide))
+        return dbc.edit.default(item_slide, **args)
 
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Slide not found")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Can't delete slide")
     def delete(self, competition_id, slide_id):
         """ Deletes the specified slide. """
+        dbc.delete.slide(dbc.get.slide(competition_id, slide_id))
+        return None
 
-        item_slide = dbc.get.slide(competition_id, slide_id)
-
-        dbc.delete.slide(item_slide)
-        return {}, codes.NO_CONTENT
 
-
-@api.route("/<slide_id>/copy")
-@api.param("competition_id,slide_id")
-class SlideCopy(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<slide_id>/copy")
+class SlideCopy(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, SlideSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Can't find slide")
     def post(self, competition_id, slide_id):
         """ Creates a deep copy of the specified slide. """
-
-        item_slide = dbc.get.slide(competition_id, slide_id)
-        item_slide_copy = dbc.copy.slide(item_slide)
-
-        return item_response(schema.dump(item_slide_copy))
+        return dbc.copy.slide(dbc.get.slide(competition_id, slide_id))
diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py
index 913deeb789340939c3dcb37f4a3edefbd7d06e95..7a1b4841f1a327f006abf5675a326b13f12f4028 100644
--- a/server/app/apis/teams.py
+++ b/server/app/apis/teams.py
@@ -3,70 +3,75 @@ All API calls concerning question alternatives.
 Default route: /api/competitions/<competition_id>/teams
 """
 
-import app.core.http_codes as codes
+from flask_smorest.error_handler import ErrorSchema
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import TeamDTO
-from app.core.parsers import sentinel
-from flask_restx import Resource, reqparse
+from app.core import ma
+from app.core.schemas import BaseSchema, TeamSchema
+from app.database import models
+from flask.views import MethodView
 
-api = TeamDTO.api
-schema = TeamDTO.schema
-list_schema = TeamDTO.list_schema
+from . import ALL, ExtendedBlueprint, http_codes
 
-team_parser_add = reqparse.RequestParser()
-team_parser_add.add_argument("name", type=str, required=True, location="json")
+blp = ExtendedBlueprint(
+    "team",
+    "team",
+    url_prefix="/api/competitions/<competition_id>/teams",
+    description="Operations on teams",
+)
 
-team_parser_edit = reqparse.RequestParser()
-team_parser_edit.add_argument("name", type=str, default=sentinel, location="json")
 
+class TeamAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Team
 
-@api.route("")
-@api.param("competition_id")
-class TeamsList(Resource):
-    @protect_route(allowed_roles=["*"])
+    name = ma.auto_field(required=True)
+
+
+class TeamEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Team
+
+    name = ma.auto_field(required=False)
+
+
+@blp.route("")
+class Teams(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, TeamSchema(many=True))
     def get(self, competition_id):
         """ Gets all teams to the specified competition. """
+        return dbc.get.team_list(competition_id)
 
-        items = dbc.get.team_list(competition_id)
-        return list_response(list_schema.dump(items))
-
-    @protect_route(allowed_roles=["*"])
-    def post(self, competition_id):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(TeamAddArgsSchema)
+    @blp.response(http_codes.OK, TeamSchema)
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="Could not add team")
+    def post(self, args, competition_id):
         """ Posts a new team to the specified competition. """
-
-        args = team_parser_add.parse_args(strict=True)
-        item_team = dbc.add.team(args["name"], competition_id)
-        return item_response(schema.dump(item_team))
+        return dbc.add.team(args["name"], competition_id)
 
 
-@api.route("/<team_id>")
-@api.param("competition_id,team_id")
-class Teams(Resource):
-    @protect_route(allowed_roles=["*"])
+@blp.route("/<team_id>")
+class TeamsById(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, TeamSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find team")
     def get(self, competition_id, team_id):
         """ Gets the specified team. """
+        return dbc.get.team(competition_id, team_id)
 
-        item = dbc.get.team(competition_id, team_id)
-        return item_response(schema.dump(item))
-
-    @protect_route(allowed_roles=["*"])
-    def put(self, competition_id, team_id):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(TeamEditArgsSchema)
+    @blp.response(http_codes.OK, TeamSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find team")
+    def put(self, args, competition_id, team_id):
         """ Edits the specified team using the provided arguments. """
+        return dbc.edit.default(dbc.get.team(competition_id, team_id), **args)
 
-        args = team_parser_edit.parse_args(strict=True)
-        name = args.get("name")
-
-        item_team = dbc.get.team(competition_id, team_id)
-
-        item_team = dbc.edit.default(item_team, name=name, competition_id=competition_id)
-        return item_response(schema.dump(item_team))
-
-    @protect_route(allowed_roles=["*"])
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="Could not find team")
     def delete(self, competition_id, team_id):
         """ Deletes the specified team. """
-
-        item_team = dbc.get.team(competition_id, team_id)
-
-        dbc.delete.team(item_team)
-        return {}, codes.NO_CONTENT
+        dbc.delete.team(dbc.get.team(competition_id, team_id))
+        return None
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
index bf393a89abcc0f85d4885e8333afab1e0df8b56f..9617cd4543bbe0ff97eead8d0a34d9f2bfed86fe 100644
--- a/server/app/apis/users.py
+++ b/server/app/apis/users.py
@@ -3,92 +3,131 @@ All API calls concerning question alternatives.
 Default route: /api/users
 """
 
-import app.core.http_codes as codes
+
 import app.database.controller as dbc
-from app.apis import item_response, list_response, protect_route
-from app.core.dto import UserDTO
-from app.core.parsers import search_parser, sentinel
-from app.database.models import User
-from flask_jwt_extended import get_jwt_identity
-from flask_restx import Resource, inputs, reqparse
-
-api = UserDTO.api
-schema = UserDTO.schema
-list_schema = UserDTO.list_schema
-
-user_parser_edit = reqparse.RequestParser()
-user_parser_edit.add_argument("email", type=inputs.email(), default=sentinel, location="json")
-user_parser_edit.add_argument("name", type=str, default=sentinel, location="json")
-user_parser_edit.add_argument("city_id", type=int, default=sentinel, location="json")
-user_parser_edit.add_argument("role_id", type=int, default=sentinel, location="json")
-
-user_search_parser = search_parser.copy()
-user_search_parser.add_argument("name", type=str, default=sentinel, location="args")
-user_search_parser.add_argument("email", type=str, default=sentinel, location="args")
-user_search_parser.add_argument("city_id", type=int, default=sentinel, location="args")
-user_search_parser.add_argument("role_id", type=int, default=sentinel, location="args")
+from app.core import ma
+from app.core.schemas import BaseSchema, UserSchema
+from app.database import models
+from app.database.models import User, Whitelist
+from flask.views import MethodView
+from flask_jwt_extended.utils import get_jwt_identity
+from flask_smorest import abort
+from flask_smorest.error_handler import ErrorSchema
+from marshmallow import fields
 
+from . import ALL, ExtendedBlueprint, http_codes
 
-def _edit_user(item_user, args):
-    """ Edits a user using the provided arguments. """
+blp = ExtendedBlueprint(
+    "users", "users", url_prefix="/api/users", description="Adding, updating, deleting and searching for users"
+)
 
-    email = args.get("email")
-    name = args.get("name")
 
-    if email:
-        if dbc.get.user_exists(email):
-            api.abort(codes.BAD_REQUEST, "Email is already in use")
-    if name:
-        args["name"] = args["name"].title()
+class UserAddArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.User
 
-    return dbc.edit.default(item_user, **args)
+    name = ma.auto_field()
+    password = fields.String(required=True)
+    email = ma.auto_field(required=True)
+    role_id = ma.auto_field(required=True)
+    city_id = ma.auto_field(required=True)
 
 
-@api.route("")
-class UsersList(Resource):
-    @protect_route(allowed_roles=["*"])
-    def get(self):
-        """ Gets all users. """
+class UserEditArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.User
 
-        item = dbc.get.one(User, get_jwt_identity())
-        return item_response(schema.dump(item))
+    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)
 
-    @protect_route(allowed_roles=["*"])
-    def put(self):
-        """ Posts a new user using the specified arguments. """
 
-        args = user_parser_edit.parse_args(strict=True)
-        item = dbc.get.one(User, get_jwt_identity())
-        item = _edit_user(item, args)
-        return item_response(schema.dump(item))
+class UserSearchArgsSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.User
 
+    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)
 
-@api.route("/<ID>")
-@api.param("ID")
-class Users(Resource):
-    @protect_route(allowed_roles=["*"])
-    def get(self, ID):
-        """ Gets the specified user. """
 
-        item = dbc.get.one(User, ID)
-        return item_response(schema.dump(item))
+@blp.route("")
+class Users(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, UserSchema)
+    def get(self):
+        """ Get currently logged in user. """
+        return dbc.get.one(User, get_jwt_identity())
+
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(UserEditArgsSchema)
+    @blp.response(http_codes.OK, UserSchema)
+    def put(self, args):
+        """ Edit current user. """
+        return _edit_user(dbc.get.one(User, get_jwt_identity()), args)
+
+    @blp.authorization(allowed_roles=["Admin"])
+    @blp.arguments(UserAddArgsSchema)
+    @blp.response(http_codes.OK, UserSchema)
+    def post(self, args):
+        """ Creates a new user if the user does not already exist. """
+        return dbc.add.user(**args)
+
+
+@blp.route("/<user_id>")
+class UsersById(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.response(http_codes.OK, UserSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="User not found")
+    def get(self, user_id):
+        """ Get user with <user_id> """
+        return dbc.get.one(User, user_id)
+
+    @blp.authorization(allowed_roles=["Admin"])
+    @blp.arguments(UserEditArgsSchema)
+    @blp.response(http_codes.OK, UserSchema)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="User not found")
+    @blp.alt_response(
+        http_codes.CONFLICT, ErrorSchema, description="The user can't be updated with the provided values"
+    )
+    def put(self, args, user_id):
+        """ Edits user with <user_id> """
+        return _edit_user(dbc.get.one(User, user_id), args)
+
+    @blp.authorization(allowed_roles=["Admin"])
+    @blp.response(http_codes.NO_CONTENT, None)
+    @blp.alt_response(http_codes.NOT_FOUND, ErrorSchema, description="User not found")
+    @blp.alt_response(http_codes.CONFLICT, ErrorSchema, description="The user can't be deleted")
+    def delete(self, user_id):
+        """ Deletes the specified user and adds their token to the blacklist. """
+        item_user = dbc.get.one(User, user_id)
+        dbc.delete.whitelist_to_blacklist(Whitelist.user_id == user_id)  # Blacklist all the whitelisted tokens
+        dbc.delete.default(item_user)
+        return None
+
+
+@blp.route("/search")
+class UserSearch(MethodView):
+    @blp.authorization(allowed_roles=ALL)
+    @blp.arguments(UserSearchArgsSchema, location="query")
+    @blp.paginate()
+    @blp.response(http_codes.OK, UserSchema(many=True))
+    def get(self, args, pagination_parameters):
+        """ Finds a specific user based on the provided arguments. """
+        return dbc.search.user(pagination_parameters, **args)
 
-    @protect_route(allowed_roles=["Admin"])
-    def put(self, ID):
-        """ Edits the specified team using the provided arguments. """
 
-        args = user_parser_edit.parse_args(strict=True)
-        item = dbc.get.one(User, ID)
-        item = _edit_user(item, args)
-        return item_response(schema.dump(item))
+def _edit_user(item_user, args):
+    """ Edits a user using the provided arguments. """
 
+    email = args.get("email")
+    name = args.get("name")
 
-@api.route("/search")
-class UserSearch(Resource):
-    @protect_route(allowed_roles=["*"])
-    def get(self):
-        """ Finds a specific user based on the provided arguments. """
+    if email and dbc.get.user_exists(email):
+        abort(http_codes.CONFLICT, message="En användare med den mejladressen finns redan")
+    if name:
+        args["name"] = args["name"].title()
 
-        args = user_search_parser.parse_args(strict=True)
-        items, total = dbc.search.user(**args)
-        return list_response(list_schema.dump(items), total)
+    return dbc.edit.default(item_user, **args)
diff --git a/server/app/core/__init__.py b/server/app/core/__init__.py
index 091a76c349930355887a34a72915b14ad2950ed4..b65b3dc0df1360ea285e5820c7c18ee669c99551 100644
--- a/server/app/core/__init__.py
+++ b/server/app/core/__init__.py
@@ -16,15 +16,12 @@ jwt = JWTManager()
 ma = Marshmallow()
 
 
-@jwt.token_in_blacklist_loader
-def check_if_token_in_blacklist(decrypted_token):
+@jwt.token_in_blocklist_loader
+def check_if_token_in_blacklist(jwt_headers, jwt_data):
     """
     An extension method with flask_jwt_extended that will execute when jwt verifies
     Check if the token is blacklisted in the database
-    :param decrypted_token: jti or string of the jwt
-    :type decrypted_token: str
-    :return: True if token is blacklisted
-    :rtype: bool
     """
-    jti = decrypted_token["jti"]
+
+    jti = jwt_data["jti"]
     return models.Blacklist.query.filter_by(jti=jti).first() is not None
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
deleted file mode 100644
index e4e0bf0d34feb93e93597460c19a422501b4dba4..0000000000000000000000000000000000000000
--- a/server/app/core/dto.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""
-The DTO module (short for Data Transfer Object) connects the namespace of an
-API and its related schemas.
-"""
-
-import app.core.rich_schemas as rich_schemas
-import app.core.schemas as schemas
-from flask_restx import Namespace
-from flask_uploads import IMAGES, UploadSet
-
-
-class ComponentDTO:
-    api = Namespace("component")
-    schema = schemas.ComponentSchema(many=False)
-    list_schema = schemas.ComponentSchema(many=True)
-
-
-class MediaDTO:
-    api = Namespace("media")
-    image_set = UploadSet("photos", IMAGES)
-    schema = schemas.MediaSchema(many=False)
-    list_schema = schemas.MediaSchema(many=True)
-
-
-class AuthDTO:
-    api = Namespace("auth")
-    schema = schemas.UserSchema(many=False)
-    list_schema = schemas.UserSchema(many=True)
-
-
-class UserDTO:
-    api = Namespace("users")
-    schema = schemas.UserSchema(many=False)
-    list_schema = schemas.UserSchema(many=True)
-
-
-class CompetitionDTO:
-    api = Namespace("competitions")
-    schema = schemas.CompetitionSchema(many=False)
-    list_schema = schemas.CompetitionSchema(many=True)
-    rich_schema = rich_schemas.CompetitionSchemaRich(many=False)
-
-
-class CodeDTO:
-    api = Namespace("codes")
-    schema = schemas.CodeSchema(many=False)
-    list_schema = schemas.CodeSchema(many=True)
-
-
-class SlideDTO:
-    api = Namespace("slides")
-    schema = schemas.SlideSchema(many=False)
-    list_schema = schemas.SlideSchema(many=True)
-
-
-class TeamDTO:
-    api = Namespace("teams")
-    schema = schemas.TeamSchema(many=False)
-    list_schema = schemas.TeamSchema(many=True)
-
-
-class MiscDTO:
-    api = Namespace("misc")
-    role_schema = schemas.RoleSchema(many=True)
-    question_type_schema = schemas.QuestionTypeSchema(many=True)
-    media_type_schema = schemas.MediaTypeSchema(many=True)
-    component_type_schema = schemas.ComponentTypeSchema(many=True)
-    view_type_schema = schemas.ViewTypeSchema(many=True)
-    city_schema = schemas.CitySchema(many=True)
-
-
-class QuestionDTO:
-    api = Namespace("questions")
-    schema = schemas.QuestionSchema(many=False)
-    list_schema = schemas.QuestionSchema(many=True)
-
-
-class QuestionAlternativeDTO:
-    api = Namespace("alternatives")
-    schema = schemas.QuestionAlternativeSchema(many=False)
-    list_schema = schemas.QuestionAlternativeSchema(many=True)
-
-
-class QuestionAlternativeAnswerDTO:
-    api = Namespace("answers")
-    schema = schemas.QuestionAlternativeAnswerSchema(many=False)
-    list_schema = schemas.QuestionAlternativeAnswerSchema(many=True)
-
-
-class QuestionScoreDTO:
-    api = Namespace("answers")
-    schema = schemas.QuestionScoreSchema(many=False)
-    list_schema = schemas.QuestionScoreSchema(many=True)
diff --git a/server/app/core/http_codes.py b/server/app/core/http_codes.py
deleted file mode 100644
index 1f6f5524e54d1fed15d989ce6407a3ed1b4ddc42..0000000000000000000000000000000000000000
--- a/server/app/core/http_codes.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-This module defines all the http status codes thats used in the api.
-"""
-
-OK = 200
-NO_CONTENT = 204
-BAD_REQUEST = 400
-UNAUTHORIZED = 401
-FORBIDDEN = 403
-NOT_FOUND = 404
-CONFLICT = 409
-GONE = 410
-INTERNAL_SERVER_ERROR = 500
-SERVICE_UNAVAILABLE = 503
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
deleted file mode 100644
index 3b541cf2778e4a3c153c950c96cd911ba3e04323..0000000000000000000000000000000000000000
--- a/server/app/core/parsers.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-This module contains the parsers used to parse the data gotten in api requests.
-"""
-
-from flask_restx import reqparse
-
-
-class Sentinel:
-    """
-    Sentinel is used as default argument to parsers if it isn't necessary to
-    supply a value. This is used instead of None so that None can be supplied
-    as value.
-    """
-
-    def __repr__(self):
-        return "Sentinel"
-
-    def __bool__(self):
-        return False
-
-
-sentinel = Sentinel()
-
-###SEARCH####
-search_parser = reqparse.RequestParser()
-search_parser.add_argument("page", type=int, default=0, location="args")
-search_parser.add_argument("page_size", type=int, default=15, location="args")
-search_parser.add_argument("order", type=int, default=1, location="args")
-search_parser.add_argument("order_by", type=str, default=None, location="args")
diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py
index e2e6037e0ec018e81c3a49c6b15ed80dc8789015..b7a7e53c24e2a1cff90392cb5f70cb69f028eb69 100644
--- a/server/app/core/sockets.py
+++ b/server/app/core/sockets.py
@@ -3,11 +3,11 @@ Contains all functionality related sockets. That is starting, joining, ending,
 disconnecting from and syncing active competitions.
 """
 import logging
+from functools import wraps
 
-from decorator import decorator
 from flask.globals import request
 from flask_jwt_extended import verify_jwt_in_request
-from flask_jwt_extended.utils import get_jwt_claims
+from flask_jwt_extended.utils import get_jwt
 from flask_socketio import SocketIO, emit, join_room
 
 logger = logging.getLogger(__name__)
@@ -30,8 +30,8 @@ def _unpack_claims():
     :rtype: tuple
     """
 
-    claims = get_jwt_claims()
-    return claims["competition_id"], claims["view"]
+    jwt = get_jwt()
+    return jwt["competition_id"], jwt["view"]
 
 
 def is_active_competition(competition_id):
@@ -55,8 +55,7 @@ def _get_sync_variables(active_competition, sync_values):
     return {key: value for key, value in active_competition.items() if key in sync_values}
 
 
-@decorator
-def authorize_client(f, allowed_views=None, require_active_competition=True, *args, **kwargs):
+def authorization(allowed_views=None, require_active_competition=True):
     """
     Decorator used to authorize a client that sends socket events. Check that
     the client has authorization headers, that client view gotten from claims
@@ -64,31 +63,41 @@ def authorize_client(f, allowed_views=None, require_active_competition=True, *ar
     if require_active_competition is True.
     """
 
-    try:
-        verify_jwt_in_request()
-    except:
-        logger.error(f"Won't call function '{f.__name__}': Missing Authorization Header")
-        return
+    def decorator(func):
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            try:
+                verify_jwt_in_request()
+            except:
+                logger.error(f"Won't call function '{func.__name__}': Missing Authorization Header")
+                return
 
-    def _is_allowed(allowed, actual):
-        return actual and "*" in allowed or actual in allowed
+            def _is_allowed(allowed, actual):
+                return actual and "*" in allowed or actual in allowed
 
-    competition_id, view = _unpack_claims()
+            competition_id, view = _unpack_claims()
+
+            if require_active_competition and not is_active_competition(competition_id):
+                logger.error(f"Won't call function '{func.__name__}': Competition '{competition_id}' is not active")
+                return
+
+            nonlocal allowed_views
+            allowed_views = allowed_views or []
+            if not _is_allowed(allowed_views, view):
+                logger.error(
+                    f"Won't call function '{func.__name__}': View '{view}' is not '{' or '.join(allowed_views)}'"
+                )
+                return
 
-    if require_active_competition and not is_active_competition(competition_id):
-        logger.error(f"Won't call function '{f.__name__}': Competition '{competition_id}' is not active")
-        return
+            return func(*args, **kwargs)
 
-    allowed_views = allowed_views or []
-    if not _is_allowed(allowed_views, view):
-        logger.error(f"Won't call function '{f.__name__}': View '{view}' is not '{' or '.join(allowed_views)}'")
-        return
+        return wrapper
 
-    return f(*args, **kwargs)
+    return decorator
 
 
 @sio.event
-@authorize_client(require_active_competition=False, allowed_views=["*"])
+@authorization(require_active_competition=False, allowed_views=["*"])
 def connect() -> None:
     """
     Connect to a active competition. If competition with competition_id is not active,
@@ -122,7 +131,7 @@ def connect() -> None:
 
 
 @sio.event
-@authorize_client(allowed_views=["*"])
+@authorization(allowed_views=["*"])
 def disconnect() -> None:
     """
     Remove client from the active_competition it was in. Delete active_competition if no
@@ -139,7 +148,7 @@ def disconnect() -> None:
 
 
 @sio.event
-@authorize_client(allowed_views=["Operator"])
+@authorization(allowed_views=["Operator"])
 def end_presentation() -> None:
     """
     End a presentation by sending end_presentation to all connected clients.
@@ -150,7 +159,7 @@ def end_presentation() -> None:
 
 
 @sio.event
-@authorize_client(allowed_views=["Operator"])
+@authorization(allowed_views=["Operator"])
 def sync(data) -> None:
     """
     Update all values from data thats in an active_competitions. Also sync all
diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py
index 47fd3079c10314adc7391f4282fbec3b2ebe36e8..92e6dc8eff7a09ed2dbef788238693e5dde51190 100644
--- a/server/app/database/__init__.py
+++ b/server/app/database/__init__.py
@@ -3,7 +3,11 @@ The database submodule contaisn all functionality that has to do with the
 database. It can add, get, delete, edit, search and copy items.
 """
 
-from flask_restx import abort
+from app.apis import http_codes
+from flask_smorest import abort
+from flask_smorest.pagination import PaginationParameters
+
+# from flask_restx import abort
 from flask_sqlalchemy import BaseQuery
 from flask_sqlalchemy.model import Model
 from sqlalchemy import Column, DateTime
@@ -25,7 +29,7 @@ class ExtendedQuery(BaseQuery):
     Extensions to a regular query which makes using the database more convenient.
     """
 
-    def first_api(self, required=True, error_message=None, error_code=404):
+    def first_api(self, required=True, error_message=None, error_code=http_codes.NOT_FOUND):
         """
         Extensions of the first() functions otherwise used on queries. Abort
         if no item was found and it was required.
@@ -43,12 +47,11 @@ class ExtendedQuery(BaseQuery):
         item = self.first()
 
         if required and not item:
-            error_message = error_message or "Object not found"
-            abort(error_code, error_message)
+            abort(error_code, message=error_message or "Objektet hittades inte")
 
         return item
 
-    def pagination(self, page=0, page_size=15, order_column=None, order=1):
+    def paginate_api(self, pagination_parameters, order_column=None, order=1):
         """
         When looking for lists of items this is used to only return a few of
         them to allow for pagination.
@@ -64,30 +67,11 @@ class ExtendedQuery(BaseQuery):
         :rtype: list, int
         """
 
-        query = self
-        if order_column:
-            if order == 1:
-                query = query.order_by(order_column)
-            else:
-                query = query.order_by(order_column.desc())
-
-        total = query.count()
-        query = query.limit(page_size).offset(page * page_size)
-        items = query.all()
-        return items, total
-
+        pagination_parameters = pagination_parameters or PaginationParameters(page=1, page_size=10)
 
-# class Dictionary(TypeDecorator):
-
-#     impl = Text
-
-#     def process_bind_param(self, value, dialect):
-#         if value is not None:
-#             value = json.dumps(value)
-
-#         return value
+        if order_column:
+            self = self.order_by(order_column if order == 1 else order_column.desc())
 
-#     def process_result_value(self, value, dialect):
-#         if value is not None:
-#             value = json.loads(value)
-#         return value
+        pagination = self.paginate(page=pagination_parameters.page, per_page=pagination_parameters.page_size)
+        pagination_parameters.item_count = pagination.total
+        return pagination.items
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 1f5ebca00eeaa96d4bdd722819cba2aa79a8f79f..9031eded1b37000485a3f8e8b7c76d7342369f70 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -4,21 +4,36 @@ This file contains functionality to add data to the database.
 
 import os
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
+from app.apis import http_codes
 from app.core import db
-from app.database.models import (Blacklist, City, Code, Competition,
-                                 ComponentType, ImageComponent, Media,
-                                 MediaType, Question, QuestionAlternative,
-                                 QuestionAlternativeAnswer, QuestionComponent,
-                                 QuestionScore, QuestionType, Role, Slide,
-                                 Team, TextComponent, User, ViewType,
-                                 Whitelist)
-from app.database.types import (IMAGE_COMPONENT_ID, QUESTION_COMPONENT_ID,
-                                TEXT_COMPONENT_ID)
+from app.database.models import (
+    Blacklist,
+    City,
+    Code,
+    Competition,
+    ComponentType,
+    ImageComponent,
+    Media,
+    MediaType,
+    Question,
+    QuestionAlternative,
+    QuestionAlternativeAnswer,
+    QuestionComponent,
+    QuestionScore,
+    QuestionType,
+    Role,
+    Slide,
+    Team,
+    TextComponent,
+    User,
+    ViewType,
+    Whitelist,
+)
+from app.database.types import IMAGE_COMPONENT_ID, QUESTION_COMPONENT_ID, TEXT_COMPONENT_ID
 from flask import current_app
 from flask.globals import current_app
-from flask_restx import abort
+from flask_smorest import abort
 from PIL import Image
 from sqlalchemy import exc
 
@@ -33,21 +48,16 @@ def db_add(item):
         db.session.commit()
         db.session.refresh(item)
     except (exc.IntegrityError):
-        abort(codes.CONFLICT, f"Item of type {type(item)} cannot be added due to an Integrity Constraint")
+        db.session.rollback()
+        abort(http_codes.CONFLICT, message=f"Kunde inte lägga objektet")
     except (exc.SQLAlchemyError, exc.DBAPIError):
         db.session.rollback()
         # SQL errors such as item already exists
-        abort(
-            codes.INTERNAL_SERVER_ERROR,
-            f"Item of type {type(item)} could not be created",
-        )
+        abort(http_codes.INTERNAL_SERVER_ERROR, message=f"Kunde inte lägga objektet")
     except:
         db.session.rollback()
         # Catching other errors
-        abort(
-            codes.INTERNAL_SERVER_ERROR,
-            f"Something went wrong when creating {type(item)}",
-        )
+        abort(http_codes.INTERNAL_SERVER_ERROR, message=f"Kunde lägga till objektet")
 
     return item
 
@@ -92,7 +102,7 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, copy=False, *
         )
         item.question_id = data.get("question_id")
     else:
-        abort(codes.BAD_REQUEST, f"Invalid type_id{type_id}")
+        abort(http_codes.BAD_REQUEST, f"Ogiltigt typ_id '{type_id}'")
 
     item = dbc.utils.commit_and_refresh(item)
     return item
@@ -256,7 +266,7 @@ def question_alternative(alternative, correct, question_id):
 
 def question_score(score, question_id, team_id):
     """
-    Adds a question answer to the specified team
+    Adds a question score to the specified team
     and question using the provided arguments.
     """
 
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/delete.py b/server/app/database/controller/delete.py
index d3f017bade09890b9820f80fd0ba8300a8fe317e..3a617d3c7df5d0b96fee21261f0694cd6bb9b27c 100644
--- a/server/app/database/controller/delete.py
+++ b/server/app/database/controller/delete.py
@@ -2,11 +2,13 @@
 This file contains functionality to delete data to the database.
 """
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
+from app.apis import http_codes
 from app.core import db
 from app.database.models import QuestionAlternativeAnswer, QuestionScore, Whitelist
-from flask_restx import abort
+from flask_smorest import abort
+
+# from flask_restx import abort
 from sqlalchemy.exc import IntegrityError
 
 
@@ -18,13 +20,10 @@ def default(item):
         db.session.commit()
     except IntegrityError:
         db.session.rollback()
-        abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint")
+        abort(http_codes.CONFLICT, message=f"Kunde inte ta bort objektet")
     except:
         db.session.rollback()
-        abort(
-            codes.INTERNAL_SERVER_ERROR,
-            f"Item of type {type(item)} could not be deleted",
-        )
+        abort(http_codes.INTERNAL_SERVER_ERROR, message=f"Kunde inte ta bort objektet")
 
 
 def whitelist_to_blacklist(filters):
diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py
index 9f54df3e088b9c13f942f898453ab54741df521c..a5e35352163b04729851b53f62ed839dacdb93f7 100644
--- a/server/app/database/controller/edit.py
+++ b/server/app/database/controller/edit.py
@@ -2,10 +2,11 @@
 This file contains functionality to get data from the database.
 """
 
-import app.core.http_codes as codes
+from app.apis import http_codes
 from app.core import db
-from app.core.parsers import sentinel
-from flask_restx.errors import abort
+from flask_smorest import abort
+
+# from flask_restx.errors import abort
 from sqlalchemy import exc
 
 
@@ -29,12 +30,12 @@ def default(item, **kwargs):
     for key, value in kwargs.items():
         if not hasattr(item, key):
             raise AttributeError(f"Item of type {type(item)} has no attribute '{key}'")
-        if value is not sentinel:
-            setattr(item, key, value)
+        setattr(item, key, value)
     try:
         db.session.commit()
     except exc.IntegrityError:
-        abort(codes.CONFLICT, f"Item of type {type(item)} cannot be edited due to an Integrity Constraint")
+        db.session.rollback()
+        abort(http_codes.CONFLICT, f"Kunde inte utföra ändringen")
 
     db.session.refresh(item)
     return item
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 3c982eec5afcd12b621bbe5b8e4abd5d4aa9522d..bcaece403f8436f93bec5c9335c56a9efb763e3d 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -3,8 +3,8 @@ This file contains functionality to get data from the database.
 """
 
 import app.database.controller as dbc
+from app.apis import http_codes
 from app.core import db
-from app.core import http_codes as codes
 from app.database.models import (
     Code,
     Competition,
@@ -63,8 +63,7 @@ def user_exists(email):
 
 def user_by_email(email):
     """ Gets the user object associated with the provided email. """
-
-    return User.query.filter(User.email == email).first_api(error_code=codes.UNAUTHORIZED)
+    return User.query.filter(User.email == email).first_api()
 
 
 ### Slides ###
@@ -297,7 +296,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 +309,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 4d112a5f4b9d339b48d0add3e1382e81bc700c80..1be280e4230f43d5cdbf95456bc1b849c8a23398 100644
--- a/server/app/database/controller/search.py
+++ b/server/app/database/controller/search.py
@@ -5,27 +5,24 @@ This file contains functionality to find data to the database.
 from app.database.models import Competition, Media, Question, Slide, User
 
 
-def image(filename, page=0, page_size=15, order=1, order_by=None):
+def image(pagination_parameters=None, filename=None, order=1, order_by=None):
     """ Finds and returns an image from the file name. """
 
     query = Media.query.filter(Media.type_id == 1)
     if filename:
         query = query.filter(Media.filename.like(f"%{filename}%"))
 
-    return query.pagination(page, page_size, None, None)
+    return query.paginate_api(pagination_parameters)
 
 
 def user(
+    pagination_parameters=None,
     email=None,
     name=None,
     city_id=None,
     role_id=None,
-    page=0,
-    page_size=15,
-    order=1,
-    order_by=None,
 ):
-    """ Finds and returns a user from the provided parameters. """
+    """ Finds and returns any number of users from the provided parameters. """
 
     query = User.query
     if name:
@@ -37,21 +34,14 @@ def user(
     if role_id:
         query = query.filter(User.role_id == role_id)
 
-    order_column = User.id  # Default order_by
-    if order_by:
-        order_column = getattr(User.__table__.c, order_by)
-
-    return query.pagination(page, page_size, order_column, order)
+    return query.paginate_api(pagination_parameters)
 
 
 def competition(
+    pagination_parameters=None,
     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. """
 
@@ -63,21 +53,15 @@ 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)
+    return query.paginate_api(pagination_parameters)
 
 
 def slide(
+    pagination_paramters=None,
     slide_order=None,
     title=None,
     body=None,
     competition_id=None,
-    page=0,
-    page_size=15,
-    order=1,
     order_by=None,
 ):
     """ Finds and returns a slide from the provided parameters. """
@@ -92,11 +76,7 @@ def slide(
     if competition_id:
         query = query.filter(Slide.competition_id == competition_id)
 
-    order_column = Slide.id  # Default order_by
-    if order_by:
-        order_column = getattr(Slide.__table__.c, order_by)
-
-    return query.pagination(page, page_size, order_column, order)
+    return query.paginate_api(pagination_paramters)
 
 
 def questions(
diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py
index f71b61e765037fa04f1bc10e0e0ae6c541d9d8d0..acfddc136a3e4c58135dede0839885ee1c93974c 100644
--- a/server/app/database/controller/utils.py
+++ b/server/app/database/controller/utils.py
@@ -2,11 +2,13 @@
 This file contains some miscellaneous functionality.
 """
 
-import app.core.http_codes as codes
+from app.apis import http_codes
 from app.core import db
 from app.core.codes import generate_code_string
 from app.database.models import Code
-from flask_restx import abort
+from flask_smorest import abort
+
+# from flask_restx import abort
 
 
 def move_order(orders, order_key, from_order, to_order):
@@ -106,7 +108,7 @@ def refresh(item):
     try:
         db.session.refresh(item)
     except Exception as e:
-        abort(codes.INTERNAL_SERVER_ERROR, f"Refresh failed!\n{str(e)}")
+        abort(http_codes.INTERNAL_SERVER_ERROR, f"Refresh failed!\n{str(e)}")
 
     return item
 
@@ -118,7 +120,7 @@ def commit():
         db.session.commit()
     except Exception as e:
         db.session.rollback()
-        abort(codes.INTERNAL_SERVER_ERROR, f"Commit failed!\n{str(e)}")
+        abort(http_codes.INTERNAL_SERVER_ERROR, f"Commit failed!\n{str(e)}")
 
 
 def commit_and_refresh(item):
diff --git a/server/configmodule.py b/server/configmodule.py
index e5df0ea77fbc7a042054f76488ea3b7be0fe3243..390b4ea74978671b6c04c3822d02c9440ca3357c 100644
--- a/server/configmodule.py
+++ b/server/configmodule.py
@@ -46,6 +46,14 @@ class Config:
     USER_LOGIN_LOCKED_ATTEMPTS = 12
     USER_LOGIN_LOCKED_EXPIRES = timedelta(hours=3)
 
+    # Configure flask_smorest
+    API_TITLE = "Teknikåttan"
+    API_VERSION = "v1.0"
+    OPENAPI_VERSION = "3.0.3"
+    OPENAPI_URL_PREFIX = "/"
+    OPENAPI_SWAGGER_UI_PATH = "/"
+    OPENAPI_SWAGGER_UI_URL = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+
 
 class DevelopmentConfig(Config, LiteDevDbConfig):
     DEBUG = True
diff --git a/server/requirements.txt b/server/requirements.txt
index 472b12cdf8229664d6d4bc679e6a85cb941341d7..bcb325f5f3a16a5ee0e655435923c38d8926b807 100644
Binary files a/server/requirements.txt and b/server/requirements.txt differ
diff --git a/server/tests/__init__.py b/server/tests/__init__.py
index c5b8f20d24cbfabe4d6c87f66a3e9b893a51d23d..2bf699418f230976e4ac28827e6b7d2f08030ec3 100644
--- a/server/tests/__init__.py
+++ b/server/tests/__init__.py
@@ -1,6 +1,8 @@
 import pytest
 from app import create_app, db
 
+DISABLE_TESTS = False
+
 
 @pytest.fixture
 def app():
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 48fbaef0ed13ebda3bdc721424beb23b07b225bc..1bf7663df8f0aac1ad005b58c892e6cf8dc28cd0 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -4,46 +4,47 @@ This file tests the api function calls.
 
 import time
 
-import app.core.http_codes as codes
 import pytest
+from app.apis import http_codes
 from app.core import sockets
 
-from tests import app, client, db
+from tests import DISABLE_TESTS, app, client, db
 from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put
 
 
-# @pytest.mark.skip(reason="Takes long time")
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_locked_api(client):
     add_default_values()
 
     # Login in with default user but wrong password until blocked
     for i in range(4):
         response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password1"})
-        assert response.status_code == codes.UNAUTHORIZED
+        assert response.status_code == http_codes.UNAUTHORIZED
 
     # Login with right password, user should be locked
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Sleep for 4 secounds
     time.sleep(4)
 
     # Check so the user is no longer locked
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_misc_api(client):
     add_default_values()
 
     # Login in with default user
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Get types
     response, body = get(client, "/api/misc/types", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert len(body["media_types"]) >= 2
     assert len(body["question_types"]) >= 3
     assert len(body["component_types"]) >= 2
@@ -51,143 +52,146 @@ def test_misc_api(client):
 
     ## Get misc
     response, body = get(client, "/api/misc/roles", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 2
+    assert response.status_code == http_codes.OK
+    assert len(body) == 2  # There are currently two roles
 
     response, body = get(client, "/api/misc/cities", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 2
-    assert body["items"][0]["name"] == "Linköping" and body["items"][1]["name"] == "Testköping"
+    assert response.status_code == http_codes.OK
+    assert len(body) >= 2
+    assert body[0]["name"] == "Linköping" and body[1]["name"] == "Testköping"
 
     ## Cities
     response, body = post(client, "/api/misc/cities", {"name": "Göteborg"}, headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 2 and body["items"][2]["name"] == "Göteborg"
+    assert response.status_code == http_codes.OK
+    assert len(body) >= 2 and body[2]["name"] == "Göteborg"
 
     # Rename city
     response, body = put(client, "/api/misc/cities/3", {"name": "Gbg"}, headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 2 and body["items"][2]["name"] == "Gbg"
+    assert response.status_code == http_codes.OK
+    assert len(body) >= 2 and body[2]["name"] == "Gbg"
 
     # Delete city
     # First checks current cities
     response, body = get(client, "/api/misc/cities", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 3
-    assert body["items"][0]["name"] == "Linköping"
-    assert body["items"][1]["name"] == "Testköping"
-    assert body["items"][2]["name"] == "Gbg"
+    assert response.status_code == http_codes.OK
+    assert len(body) >= 3
+    assert body[0]["name"] == "Linköping"
+    assert body[1]["name"] == "Testköping"
+    assert body[2]["name"] == "Gbg"
 
     # Deletes city
     response, body = delete(client, "/api/misc/cities/3", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 2
-    assert body["items"][0]["name"] == "Linköping" and body["items"][1]["name"] == "Testköping"
+    assert response.status_code == http_codes.OK
+    assert len(body) >= 2
+    assert body[0]["name"] == "Linköping" and body[1]["name"] == "Testköping"
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_competition_api(client):
     add_default_values()
 
     # Login in with default user
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Create competition
     data = {"name": "c1", "year": 2020, "city_id": 1}
     response, body = post(client, "/api/competitions", data, headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert body["name"] == "c1"
     competition_id = body["id"]
 
     # Get competition
     response, body = get(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert body["name"] == "c1"
 
     response, body = post(client, f"/api/competitions/{competition_id}/slides", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     response, body = get(client, f"/api/competitions/{competition_id}/slides", headers=headers)
-    assert response.status_code == codes.OK
-    assert len(body["items"]) == 2
+    assert response.status_code == http_codes.OK
+    assert len(body) == 2
 
     """
     response, body = put(client, f"/api/competitions/{competition_id}/slides/{2}/order", {"order": 1}, headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     """
 
     response, body = post(client, f"/api/competitions/{competition_id}/teams", {"name": "t1"}, headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     response, body = get(client, f"/api/competitions/{competition_id}/teams", headers=headers)
-    assert response.status_code == codes.OK
-    assert len(body["items"]) == 1
-    assert body["items"][0]["name"] == "t1"
+    assert response.status_code == http_codes.OK
+    assert len(body) == 1
+    assert body[0]["name"] == "t1"
 
     response, body = delete(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.NO_CONTENT
 
     # Get competition
     competition_id = 2
     response, body = get(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     # Copies competition
     for _ in range(3):
         response, _ = post(client, f"/api/competitions/{competition_id}/copy", headers=headers)
-        assert response.status_code == codes.OK
+        assert response.status_code == http_codes.OK
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_auth_and_user_api(client):
     add_default_values()
 
     # Login in with default user
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Login in with default user but wrong password
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password1"})
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Create user
     register_data = {"email": "test1@test.se", "password": "abc123", "role_id": 2, "city_id": 1}
-    response, body = post(client, "/api/auth/signup", register_data, headers)
-    assert response.status_code == codes.OK
+    response, body = post(client, "/api/users", register_data, headers)
+    assert response.status_code == http_codes.OK
     assert body["id"] == 2
     assert "password" not in body
     assert "_password" not in body
 
     # Try to create user with same email
     register_data = {"email": "test1@test.se", "password": "354213", "role_id": 1, "city_id": 1}
-    response, body = post(client, "/api/auth/signup", register_data, headers)
-    assert response.status_code == codes.BAD_REQUEST
+    response, body = post(client, "/api/users", register_data, headers)
+    assert response.status_code == http_codes.CONFLICT
 
-    # Try loggin with wrong PASSWORD
+    # Try login with wrong PASSWORD
     response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc1234"})
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
-    # Try loggin with wrong Email
+    # Try login with wrong Email
     response, body = post(client, "/api/auth/login", {"email": "testx@test.se", "password": "abc1234"})
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.NOT_FOUND
 
-    # Try loggin with right PASSWORD
+    # Login with right PASSWORD
     response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc123"})
-    assert response.status_code == codes.OK
-    # refresh_token = body["refresh_token"]
+    assert response.status_code == http_codes.OK
+
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Get the current user
     response, body = get(client, "/api/users", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert body["email"] == "test1@test.se"
 
     # Edit current user name
     response, body = put(client, "/api/users", {"name": "carl carlsson", "city_id": 2, "role_id": 1}, headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert body["name"] == "Carl Carlsson"
-    assert body["city_id"] == 2 and body["role_id"] == 1
+    assert body["city_id"] == 2
+    assert body["role_id"] == 1
 
     # Find other user
     response, body = get(
@@ -196,14 +200,14 @@ def test_auth_and_user_api(client):
         query_string={"name": "Carl Carlsson"},
         headers=headers,
     )
-    assert response.status_code == codes.OK
-    assert body["count"] == 1
+    assert response.status_code == http_codes.OK
+    assert len(body) == 1
 
     # Get user from ID
-    searched_user = body["items"][0]
+    searched_user = body[0]
     user_id = searched_user["id"]
     response, body = get(client, f"/api/users/{user_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert searched_user["name"] == body["name"]
     assert searched_user["email"] == body["email"]
     assert searched_user["role_id"] == body["role_id"]
@@ -212,46 +216,46 @@ def test_auth_and_user_api(client):
 
     # Login as admin
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Edit user from ID
     response, body = put(client, f"/api/users/{user_id}", {"email": "carl@carlsson.test"}, headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     # assert body["email"] == "carl@carlsson.test"
 
     # Edit user from ID but add the same email as other user
     response, body = put(client, f"/api/users/{user_id}", {"email": "test@test.se"}, headers=headers)
-    assert response.status_code == codes.BAD_REQUEST
+    assert response.status_code == http_codes.CONFLICT
 
     # Delete other user
-    response, body = delete(client, f"/api/auth/delete/{user_id}", headers=headers)
-    assert response.status_code == codes.OK
+    response, body = delete(client, f"/api/users/{user_id}", headers=headers)
+    assert response.status_code == http_codes.NO_CONTENT
 
     # Try to delete other user again
-    response, body = delete(client, f"/api/auth/delete/{user_id}", headers=headers)
-    assert response.status_code == codes.NOT_FOUND
+    response, body = delete(client, f"/api/users/{user_id}", headers=headers)
+    assert response.status_code == http_codes.NOT_FOUND
 
     # Logout and try to access current user
     response, body = post(client, f"/api/auth/logout", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.NO_CONTENT
 
     # TODO: Check if current users jwt (jti) is in blacklist after logging out
     response, body = get(client, "/api/users", headers=headers)
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Login in again with default user
     # response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc123"})
-    # assert response.status_code == codes.OK
+    # assert response.status_code == http_codes.OK
     # headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # # TODO: Add test for refresh api for current user
     # # response, body = post(client, "/api/auth/refresh", headers={**headers, "refresh_token": refresh_token})
-    # # assert response.status_code == codes.OK
+    # # assert response.status_code == http_codes.OK
 
     # # Find current user
     # response, body = get(client, "/api/users", headers=headers)
-    # assert response.status_code == codes.OK
+    # assert response.status_code == http_codes.OK
     # assert body["email"] == "test1@test.se"
     # assert body["city_id"] == 2
     # assert body["role_id"] == 1
@@ -259,44 +263,45 @@ def test_auth_and_user_api(client):
     # # Delete current user
     # user_id = body["id"]
     # response, body = delete(client, f"/api/auth/delete/{user_id}", headers=headers)
-    # assert response.status_code == codes.OK
+    # assert response.status_code == http_codes.OK
 
     # TODO: Check that user was blacklisted
     # Look for current users jwt in blacklist
     # Blacklist.query.filter(Blacklist.jti == )
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_slide_api(client):
     add_default_values()
 
     # Login in with default user
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Get slides from empty competition
     CID = 1
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == 1
+    assert response.status_code == http_codes.OK
+    assert len(body) == 1
 
     # Get slides
     CID = 2
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == 3
+    assert response.status_code == http_codes.OK
+    assert len(body) == 3
 
     # Add slide
     response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
-    assert body["count"] == 4
+    assert len(body) == 4
 
     # Get slide
     slide_id = 2
     response, item_slide = get(client, f"/api/competitions/{CID}/slides/{slide_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     # Edit slide
     title = "Ny titel"
@@ -308,12 +313,10 @@ def test_slide_api(client):
     response, item_slide = put(
         client,
         f"/api/competitions/{CID}/slides/{slide_id}",
-        # TODO: Implement so these commented lines can be edited
-        # {"order": order, "title": title, "body": body, "timer": timer},
         {"title": title, "timer": timer},
         headers=headers,
     )
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     # assert item_slide["order"] == order
     assert item_slide["title"] == title
     # assert item_slide["body"] == body
@@ -321,22 +324,22 @@ def test_slide_api(client):
 
     # Delete slide
     response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_id}", headers=headers)
-    assert response.status_code == codes.NO_CONTENT
+    assert response.status_code == http_codes.NO_CONTENT
     # Checks that there are fewer slides
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == 3
+    assert response.status_code == http_codes.OK
+    assert len(body) == 3
 
     # Tries to delete slide again, which will fail
     response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_id}", headers=headers)
-    assert response.status_code != codes.OK
+    assert response.status_code != http_codes.OK
 
     # Get all slides
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == 3
-    assert body["items"][0]["id"] == 3
-    assert body["items"][0]["order"] == 0
+    assert response.status_code == http_codes.OK
+    assert len(body) == 3
+    assert body[0]["id"] == 3
+    assert body[0]["order"] == 0
     slide_id = 3
 
     """
@@ -344,7 +347,7 @@ def test_slide_api(client):
     response, _ = put(
         client, f"/api/competitions/{CID}/slides/{slide_id}/order", {"order": 0}, headers=headers
     )
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     # Changes the order
     change_order_test(client, CID, slide_id, slide_id + 1, headers)
@@ -352,7 +355,7 @@ def test_slide_api(client):
     # Copies slide
     for _ in range(10):
         response, _ = post(client, f"/api/competitions/{CID}/slides/{slide_id}/copy", headers=headers)
-        assert response.status_code == codes.OK
+        assert response.status_code == http_codes.OK
     """
 
     # Get a specific component
@@ -360,7 +363,7 @@ def test_slide_api(client):
     SID = 3
     COMID = 2
     response, c1 = get(client, f"/api/competitions/{CID}/slides/{SID}/components/{COMID}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     # Copy the component to another view
     view_type_id = 3
@@ -368,7 +371,7 @@ def test_slide_api(client):
         client, f"/api/competitions/{CID}/slides/{SID}/components/{COMID}/copy/{view_type_id}", headers=headers
     )
     # Check that the components metch
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert c1 != c2
     assert c1["x"] == c2["x"]
     assert c1["y"] == c2["y"]
@@ -385,27 +388,16 @@ def test_slide_api(client):
     assert c2["view_type_id"] == 3
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_question_api(client):
     add_default_values()
 
     # Login in with default user
     response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
-    # Get questions from empty competition
-    CID = 1  # TODO: Fix api-calls so that the ones not using CID don't require one
-    slide_order = 1
-    response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == 0
-
-    # Get questions from another competition that should have some questions
-    CID = 3
-    response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     num_questions = 3
-    assert response.status_code == codes.OK
-    assert body["count"] == num_questions
 
     # Add question
     name = "Nytt namn"
@@ -413,36 +405,33 @@ def test_question_api(client):
     slide_order = 6
     response, item_question = post(
         client,
-        f"/api/competitions/{CID}/slides/{slide_order}/questions",
+        f"/api/competitions/{3}/slides/{slide_order}/questions",
         {"name": name, "type_id": type_id},
         headers=headers,
     )
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert item_question["name"] == name
     assert item_question["type_id"] == type_id
     num_questions += 1
 
-    # Checks number of questions
-    response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == num_questions
     """
     # Delete question
     response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}/questions/{QID}", headers=headers)
     num_questions -= 1
-    assert response.status_code == codes.NO_CONTENT
+    assert response.status_code == http_codes.NO_CONTENT
 
     # Checks that there are fewer questions
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     assert body["count"] == num_questions
 
     # Tries to delete question again
     response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_slide_order}/questions/{QID}", headers=headers)
-    assert response.status_code == codes.NOT_FOUND
+    assert response.status_code == http_codes.NOT_FOUND
     """
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_authorization(client):
     add_default_values()
 
@@ -451,8 +440,8 @@ def test_authorization(client):
 
     #### TEAM ####
     # Login in with team code
-    response, body = post(client, "/api/auth/login/code", {"code": "111111"})
-    assert response.status_code == codes.OK
+    response, body = post(client, "/api/auth/code", {"code": "111111"})
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     competition_id = body["competition_id"]
@@ -460,56 +449,48 @@ def test_authorization(client):
 
     # Get competition team is in
     response, body = get(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     # Try to delete competition team is in
     response, body = delete(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Try to get a different competition
     response, body = get(client, f"/api/competitions/{competition_id+1}", headers=headers)
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Get own answers
-    response, body = get(
-        client, f"/api/competitions/{competition_id}/teams/{team_id}/answers/question_alternatives", headers=headers
-    )
-    assert response.status_code == codes.OK
+    response, body = get(client, f"/api/competitions/{competition_id}/teams/{team_id}/answers", headers=headers)
+    assert response.status_code == http_codes.OK
 
     # Try to get another teams answers
-    response, body = get(
-        client, f"/api/competitions/{competition_id}/teams/{team_id+1}/answers/question_alternatives", headers=headers
-    )
-    assert response.status_code == codes.UNAUTHORIZED
+    response, body = get(client, f"/api/competitions/{competition_id}/teams/{team_id+1}/answers", headers=headers)
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     #### JUDGE ####
     # Login in with judge code
-    response, body = post(client, "/api/auth/login/code", {"code": "222222"})
-    assert response.status_code == codes.OK
+    response, body = post(client, "/api/auth/code", {"code": "222222"})
+    assert response.status_code == http_codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
     competition_id = body["competition_id"]
 
     # Get competition judge is in
     response, body = get(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     # Try to delete competition judge is in
     response, body = delete(client, f"/api/competitions/{competition_id}", headers=headers)
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Try to get a different competition
     response, body = get(client, f"/api/competitions/{competition_id+1}", headers=headers)
-    assert response.status_code == codes.UNAUTHORIZED
+    assert response.status_code == http_codes.UNAUTHORIZED
 
     # Get team answers
-    response, body = get(
-        client, f"/api/competitions/{competition_id}/teams/{team_id}/answers/question_alternatives", headers=headers
-    )
-    assert response.status_code == codes.OK
+    response, body = get(client, f"/api/competitions/{competition_id}/teams/{team_id}/answers", headers=headers)
+    assert response.status_code == http_codes.OK
 
     # Also get antoher teams answers
-    response, body = get(
-        client, f"/api/competitions/{competition_id}/teams/{team_id+1}/answers/question_alternatives", headers=headers
-    )
-    assert response.status_code == codes.OK
+    response, body = get(client, f"/api/competitions/{competition_id}/teams/{team_id+1}/answers", headers=headers)
+    assert response.status_code == http_codes.OK
diff --git a/server/tests/test_db.py b/server/tests/test_db.py
index 82a75a3f6f8916efefed99de346e22b91e1b0f54..6d000820135b88f21c5ab85071ce9be1f3bd9e4a 100644
--- a/server/tests/test_db.py
+++ b/server/tests/test_db.py
@@ -3,12 +3,14 @@ This file tests the database controller functions.
 """
 
 import app.database.controller as dbc
+import pytest
 from app.database.models import City, Code, Competition, Media, MediaType, Role, Slide, User
 
-from tests import app, client, db
+from tests import DISABLE_TESTS, app, client, db
 from tests.test_helpers import add_default_values, assert_all_slide_orders, assert_should_fail, assert_slide_order
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is False")
 def test_default_values(client):
     add_default_values()
 
@@ -40,6 +42,7 @@ def test_default_values(client):
     assert_should_fail(assert_all_slide_orders)
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is enabled")
 def test_user(client):
     add_default_values()
     item_user = User.query.filter_by(email="test@test.se").first()
@@ -57,6 +60,7 @@ def test_user(client):
     assert len(item_city.users) == 1 and item_city.users[0].id == item_user.id
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is enabled")
 def test_media(client):
     add_default_values()
     item_user = User.query.filter_by(email="test@test.se").first()
@@ -75,17 +79,18 @@ def test_media(client):
     assert item_media.upload_by.email == "test@test.se"
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is enabled")
 def test_copy(client):
     add_default_values()
 
     # Fetches a competition
-    list_item_competitions, _ = dbc.search.competition(name="Tävling 1")
+    list_item_competitions = dbc.search.competition(name="Tävling 1")
     item_competition_original = list_item_competitions[0]
 
     # Fetches the first slide in that competition
     num_slides = 3
-    item_slides, total = dbc.search.slide(competition_id=item_competition_original.id)
-    assert total == num_slides
+    item_slides = dbc.search.slide(competition_id=item_competition_original.id)
+    assert len(item_slides) == num_slides
     item_slide_original = item_slides[1]
 
     dbc.delete.slide(item_slides[0])
@@ -192,14 +197,15 @@ def check_slides_copy(item_slide_original, item_slide_copy, num_slides, order):
             assert a2.question_id == q2.id
 
     # Checks that the copy put the slide in the database
-    item_slides, total = dbc.search.slide(
+    item_slides = dbc.search.slide(
         competition_id=item_slide_copy.competition_id,
         # page_size=num_slides + 1, # Use this total > 15
     )
-    assert total == num_slides
+    assert len(item_slides) == num_slides
     assert item_slide_copy == item_slides[order]
 
 
+@pytest.mark.skipif(DISABLE_TESTS, reason="Only run when DISABLE_TESTS is enabled")
 def test_move_slides(client):
     add_default_values()
 
diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py
index f5abbfcbe1991bf379e5c9020b99088122d6d6e9..fdcef127df249587a45ad8647c62731497cdb8ff 100644
--- a/server/tests/test_helpers.py
+++ b/server/tests/test_helpers.py
@@ -1,7 +1,7 @@
 import json
 
-import app.core.http_codes as codes
 import app.database.controller as dbc
+from app.apis import http_codes
 from app.core import db
 from app.database.models import City, Code, Role, Slide
 
@@ -144,15 +144,15 @@ def assert_object_values(obj, values):
 # Changes order of slides
 def change_order_test(client, cid, slide_id, new_slide_id, h):
     response, new_order_body = get(client, f"/api/competitions/{cid}/slides/{new_slide_id}", headers=h)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
     response, order_body = get(client, f"/api/competitions/{cid}/slides/{slide_id}", headers=h)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
     new_order = new_order_body["order"]
 
     # Changes order
     response, _ = put(client, f"/api/competitions/{cid}/slides/{slide_id}/order", {"order": new_order}, headers=h)
-    assert response.status_code == codes.OK
+    assert response.status_code == http_codes.OK
 
 
 def assert_slide_order(item_comp, correct_order):