diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index b7172f96f3dce964059251b7b75a20d273be2133..5d6e45dc59e00258231b4975370e620c0ec108b7 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -43,6 +43,7 @@ def item_response(item, code=codes.OK):
 from flask_restx import Api
 
 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
@@ -60,5 +61,6 @@ 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/<CID>/slides")
 flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams")
+flask_api.add_namespace(code_ns, path="/api/competitions/<CID>/codes")
 flask_api.add_namespace(question_ns, path="/api/competitions/<CID>")
 flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/")
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
index 1510df642682f2d00d94bb695af45e7fb1cf24f4..10d820f88d1a5570635a05e54a2ef45492a1c645 100644
--- a/server/app/apis/auth.py
+++ b/server/app/apis/auth.py
@@ -1,7 +1,8 @@
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import admin_required, item_response, text_response
-from app.core.dto import AuthDTO
+from app.core.codes import verify_code
+from app.core.dto import AuthDTO, CodeDTO
 from app.core.parsers import create_user_parser, login_parser
 from app.database.models import User
 from flask_jwt_extended import (
@@ -69,6 +70,20 @@ class AuthLogin(Resource):
         return response
 
 
+@api.route("/login/<code>")
+@api.param("code")
+class AuthLogin(Resource):
+    def post(self, code):
+        if not verify_code(code):
+            api.abort(codes.BAD_REQUEST, "Invalid code")
+
+        item_code = dbc.get.code_by_code(code)
+        if not item_code:
+            api.abort(codes.UNAUTHORIZED, "A presentation with that code does not exist")
+
+        return item_response(CodeDTO.schema.dump(item_code)), codes.OK
+
+
 @api.route("/logout")
 class AuthLogout(Resource):
     @jwt_required
diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py
new file mode 100644
index 0000000000000000000000000000000000000000..70cf934166d81d95bd7b2f7f1b8f2ecc5621feb5
--- /dev/null
+++ b/server/app/apis/codes.py
@@ -0,0 +1,44 @@
+import app.database.controller as dbc
+from app.apis import admin_required, item_response, list_response
+from app.core import http_codes as codes
+from app.core.codes import generate_code
+from app.core.dto import CodeDTO
+from app.core.parsers import code_parser
+from app.database.models import Competition
+from flask_jwt_extended import jwt_required
+from flask_restx import Resource
+
+api = CodeDTO.api
+schema = CodeDTO.schema
+list_schema = CodeDTO.list_schema
+
+
+@api.route("/")
+@api.param("CID")
+class CodesList(Resource):
+    @jwt_required
+    def get(self, CID):
+        items = dbc.get.code_list(CID)
+        return list_response(list_schema.dump(items), len(items)), codes.OK
+
+    @jwt_required
+    def post(self, CID):
+        args = code_parser.parse_args(strict=True)
+        item = dbc.add.code(**args)
+        return item_response(schema.dump(item)), codes.OK
+
+
+@api.route("/<code_id>")
+@api.param("CID, code_id")
+class Competitions(Resource):
+    @jwt_required
+    def put(self, CID, code_id):
+        item_code = dbc.get.code(code_id)
+        item_code = dbc.edit.generate_new_code(item_code)
+        return item_response(schema.dump(item_code)), codes.OK
+
+    # @jwt_required
+    # def delete(self, CID, code_id):
+    #     item_code = dbc.get.code(code_id)
+    #     dbc.delete.code(item_code)
+    #     return {}, http_codes.NOT_FOUND
diff --git a/server/app/core/codes.py b/server/app/core/codes.py
new file mode 100644
index 0000000000000000000000000000000000000000..c13fa84e9e1ab8418cabad2743a94148b8554651
--- /dev/null
+++ b/server/app/core/codes.py
@@ -0,0 +1,15 @@
+import random
+import re
+import string
+
+CODE_LENGTH = 6
+ALLOWED_CHARS = string.ascii_uppercase + string.digits
+CODE_RE = re.compile(f"^[{ALLOWED_CHARS}]{{{CODE_LENGTH}}}$")
+
+
+def generate_code():
+    return "".join(random.choices(ALLOWED_CHARS, k=CODE_LENGTH))
+
+
+def verify_code(c):
+    return CODE_RE.search(c.upper()) is not None
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index a6899fc7e681d55010ba23b382fd2155954d507d..4533fc0caa874ef2ed20293bec1ada7ce05342fe 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -37,6 +37,12 @@ class CompetitionDTO:
     list_schema = schemas.CompetitionSchema(many=True)
 
 
+class CodeDTO:
+    api = Namespace("codes")
+    schema = rich_schemas.CodeSchemaRich(many=False)
+    list_schema = schemas.CodeSchema(many=True)
+
+
 class SlideDTO:
     api = Namespace("slides")
     schema = schemas.SlideSchema(many=False)
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index 54f54239c61f4b81c7f6903d70e6d2c38b9d1ca7..f691ea8d3924a8679068713bf85ca47e2d065376 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -60,6 +60,12 @@ question_parser.add_argument("total_score", type=int, default=None, location="js
 question_parser.add_argument("type_id", type=int, default=None, location="json")
 question_parser.add_argument("slide_id", type=int, location="json")
 
+###QUESTION####
+code_parser = reqparse.RequestParser()
+code_parser.add_argument("pointer", type=str, default=None, location="json")
+code_parser.add_argument("view_type_id", type=int, default=None, location="json")
+
+
 ###TEAM####
 team_parser = reqparse.RequestParser()
 team_parser.add_argument("name", type=str, location="json")
diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py
index 8c220e780e176c4505ebe1c625e6027e25837077..fa6daac9ef59117b76ab9d2244f80af3224dbbe5 100644
--- a/server/app/core/rich_schemas.py
+++ b/server/app/core/rich_schemas.py
@@ -43,6 +43,16 @@ class TeamSchemaRich(RichSchema):
     question_answers = fields.Nested(schemas.QuestionAnswerSchema, many=True)
 
 
+class CodeSchemaRich(RichSchema):
+    class Meta(RichSchema.Meta):
+        model = models.Code
+
+    id = ma.auto_field()
+    code = ma.auto_field()
+    pointer = ma.auto_field()
+    view_type = fields.Nested(schemas.ViewTypeSchema, many=False)
+
+
 class SlideSchemaRich(RichSchema):
     class Meta(RichSchema.Meta):
         model = models.Slide
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index ee909e4b11392386e1fa806aaf7c0481eb4a313b..4f9646a55e2e64d9498f96589b59886824845a45 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -31,6 +31,16 @@ class ComponentTypeSchema(IdNameSchema):
         model = models.ComponentType
 
 
+class CodeSchema(IdNameSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Code
+
+    id = ma.auto_field()
+    code = ma.auto_field()
+    pointer = ma.auto_field()
+    view_type_id = ma.auto_field()
+
+
 class ViewTypeSchema(IdNameSchema):
     class Meta(BaseSchema.Meta):
         model = models.ViewType
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 0e955a9e09a1a26032566ebfabb3133dd0a8359e..07b49fde49e1a7a13704f07ca8ef25555635bdfd 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -1,8 +1,10 @@
 import app.core.http_codes as codes
 from app.core import db
+from app.core.codes import generate_code
 from app.database.models import (
     Blacklist,
     City,
+    Code,
     Competition,
     Component,
     ComponentType,
@@ -75,6 +77,11 @@ def team(name, item_competition):
     return Team(name, item_competition.id)
 
 
+@db_add
+def code(pointer, view_type_id):
+    return Code(pointer, view_type_id)
+
+
 @db_add
 def mediaType(name):
     return MediaType(name)
diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py
index 3afcb4d45c8ba0c0f9cfdaab83d88b286f566215..7b3ef334427e1bf561a5ab5f4e1bc9f25e9c7c68 100644
--- a/server/app/database/controller/edit.py
+++ b/server/app/database/controller/edit.py
@@ -1,4 +1,6 @@
 from app.core import db
+from app.core.codes import generate_code
+from app.database.models import Code
 
 
 def switch_order(item1, item2):
@@ -109,3 +111,16 @@ def question(item_question, name=None, total_score=None, type_id=None, slide_id=
     db.session.refresh(item_question)
 
     return item_question
+
+
+def generate_new_code(item_code):
+
+    code = generate_code()
+    while db.session.query(Code).filter(Code.code == code).count():
+        code = generate_code()
+
+    item_code.code = code
+    db.session.commit()
+    db.session.refresh(item_code)
+
+    return item_code
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 1669f0ae3c1fdd2eb9125586a84d070eacb14acc..4e9a6aa5b337a995f2d255a46581c7ec82233e26 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -1,6 +1,7 @@
 from app.core import db
 from app.database.models import (
     City,
+    Code,
     Competition,
     Component,
     ComponentType,
@@ -11,8 +12,11 @@ from app.database.models import (
     Slide,
     Team,
     User,
+    ViewType,
 )
 
+team_view_id = ViewType.query.filter(ViewType.name == "Team").one().id
+
 
 def all(db_type):
     return db_type.query.all()
@@ -34,6 +38,25 @@ def competition(CID, required=True, error_msg=None):
     return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg)
 
 
+def code(code_id, required=True, error_msg=None):
+    return Code.query.filter(Code.id == code_id).first_extended(required, error_msg)
+
+
+def code_by_code(code):
+    return Code.query.filter(Code.code == code.upper()).first()
+
+
+def code_list(competition_id):
+    return (
+        Code.query.join(Team, (Code.view_type_id == team_view_id) & (Team.id == Code.pointer), isouter=True)
+        .filter(
+            ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))
+            | ((Code.view_type_id == team_view_id) & (competition_id == Team.competition_id))
+        )
+        .all()
+    )
+
+
 def user(UID, required=True, error_msg=None):
     return User.query.filter(User.id == UID).first_extended(required, error_msg)
 
diff --git a/server/app/database/models.py b/server/app/database/models.py
index 6396e035fb615449ca29940322457d3739dd462e..9479d92cf6909f09341e4432f1c08071418bc94d 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -1,6 +1,7 @@
 import json
 
 from app.core import bcrypt, db
+from app.core.codes import generate_code
 from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
 from sqlalchemy.orm import backref
 from sqlalchemy.types import TypeDecorator
@@ -230,7 +231,12 @@ class Code(db.Model):
 
     view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False)
 
-    def __init__(self, code, pointer, view_type_id):
+    def __init__(self, pointer, view_type_id):
+
+        code = generate_code()
+        while db.session.query(Code).filter(Code.code == code).count():
+            code = generate_code()
+
         self.code = code
         self.pointer = pointer
         self.view_type_id = view_type_id