diff --git a/Makefile b/Makefile
index 5c9441e747d13f01ad7290d05797d5284343a1f7..4f16d4d49a538c211d248bb3f30ff05cb71b0452 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,10 @@ devserver: init-db routes
 routes:
 	flask --app aocp routes
 
+.PHONY: test-verifier
+test-verifier:
+	flask --app aocp --debug test-verifier
+
 .PHONY: init-db
 init-db:
 	flask --app aocp init-db
@@ -19,3 +23,8 @@ init-db:
 reset-db:
 	$(RM) instance/aocp.sqlite
 	flask --app aocp init-db
+
+
+.PHONY: test-aoc-py
+test-aoc-py:
+	flask --app aocp --debug test-aoc-py
\ No newline at end of file
diff --git a/aocp/__init__.py b/aocp/__init__.py
index 9ce84a4b16b8f080f579c2eb69f11b2b2666949c..195e0ddf3c5c5f9d5dd2f5d5ea8471af8ef0426b 100644
--- a/aocp/__init__.py
+++ b/aocp/__init__.py
@@ -1,5 +1,7 @@
 from . import db
 from . import auth
+from . import verifier
+from . import aoc
 
 import os
 import json
@@ -92,4 +94,10 @@ def create_app(test_config=None):
     auth.init_app(app)
     app.register_blueprint(auth.bp)
 
+    # Initialize aoc API
+    aoc.init_app(app)
+
+    # Initialize verifier
+    verifier.init_app(app)
+
     return app
diff --git a/aocp/aoc.py b/aocp/aoc.py
new file mode 100755
index 0000000000000000000000000000000000000000..6689b52af24d5e1c10fd39f6baacaf77e438a4f9
--- /dev/null
+++ b/aocp/aoc.py
@@ -0,0 +1,105 @@
+from typing import Literal
+import dateparser
+from datetime import date, datetime
+from enum import Enum
+from flask import current_app
+import requests 
+from flask import app 
+import re
+import click
+import requests
+
+current_year = current_app.config["YEAR"]
+
+def get_timeout(resp:str)->int: 
+    """
+    extracts a timeout from the response text.
+    returns the timeout in sec or 0 if parsing wasnt successful.
+    Example: get_timeout("please wait 20m") would return 1200.
+    """
+    current_time = datetime.now()
+    timeout = dateparser.parse(" ".join(re.findall(r'\d+s|\d+m|\d+h',resp)))
+    if timeout:
+        return (current_time - timeout).seconds
+    else:
+        return 0
+
+
+class Status(Enum):
+    """
+    possible status after sending a request to aoc website.
+    gets returned by submit_answer
+    """
+    PASS = 0
+    FAIL = 1
+    RATE_LIMIT = 2
+    COMPLETED = 3
+    NOT_LOGGED_IN = 4
+    UNKNOWN = 5
+    
+
+
+def submit_answer(year:int, day:int, level:int, answer:str)->tuple[Status,int|None]:
+    """
+    submits an answer to the aoc website for a given year and day.
+    returns a Status according to the result and, if there is a timeout, the timeout in seconds.
+    """
+    payload = {'level': level, 'answer': answer}
+    r = requests.post(
+        f'https://adventofcode.com/{year}/day/{day}/answer',
+        data=payload,
+        headers={
+            "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de',
+        },
+        cookies={'session': current_app.config["SESSION_COOKIE"]}
+    )
+    response = r.text
+    if "That's the right answer" in response:
+        #click.echo("CORRECT ANSWER")
+        
+        return Status.PASS, None
+    elif "That's not the right answer" in response:
+        #click.echo("WROOOOONG")
+        
+        return Status.FAIL, None
+    elif 'You gave an answer too recently' in response:
+        timeout = get_timeout(response)
+        #click.echo("RATE LIMIT: "+str(timeout))
+        
+        return Status.RATE_LIMIT, timeout
+
+    elif 'Did you already complete it?' in response:
+        #click.echo("ALREADY COMPLETED")
+        
+        return Status.COMPLETED, None
+
+    elif '[Log In]' in response:
+        #click.echo("NOT LOGGED IN")
+        
+        return Status.NOT_LOGGED_IN, None
+    
+    else:
+        #click.echo("HMMM- SUS")
+        return Status.UNKNOWN, response 
+
+def get_puzzle(year:int, day:int)->bytes:
+    """
+    returns the puzzle of a day as bytes.
+    """
+    puzzle = requests.get("https://adventofcode.com/"+str(year)+"/day/"+str(day)+"/input",allow_redirects=True, headers={
+            "User-Agent": 'https://aoc.fg.informatik.uni-goettingen.de/ by fachgruppe@informatik.uni-goettingen.de',
+        },cookies={'session': current_app.config["SESSION_COOKIE"]}).content
+    return puzzle
+
+
+@click.command('test-aoc-py')
+def test_aoc_py_command():
+    """testing commands"""
+    current_app.logger.info("testing get_puzzle")
+    current_app.logger.debug(get_puzzle(20))
+
+def init_app(app):
+    """
+    Gets called by __init__.py on initialization of the flask App.
+    """
+    app.cli.add_command(test_aoc_py_command)
diff --git a/aocp/score.py b/aocp/score.py
new file mode 100644
index 0000000000000000000000000000000000000000..63b55b1db5d33e2803c893b482735008f25bf84b
--- /dev/null
+++ b/aocp/score.py
@@ -0,0 +1,48 @@
+from datetime import datetime
+from zoneinfo import ZoneInfo
+
+# timedelta is hour after conversion!
+# return 0 points if not current day
+
+def calculate_points(day:int, timedelta:int, is_first:bool)->int:
+    """
+    Calculates the points based on day, time and if the solver was first.
+    
+    
+    Parameters:
+    day    -- Day of the solution.
+    timedelta -- Hour of the solution.
+    is_first -- Boolean (True if person was first to solve problem).
+    
+    Returns:
+    score -- the score
+    """
+    # get "base points" depending on day
+    base_points = 1+ day // 5  # base points increase by 1 every 5 days.
+    # bonus time points: (all ranges inclusive)
+    # 0-8: 2 points
+    # 8-16: 1 point
+    # 16-24: 0 points
+    bonus_time_points = (24-timedelta) // 8 
+    if is_first: # bonus point if person is first to solve on given day.
+        bonus_first = 1
+    else:
+        bonus_first = 0
+    score = base_points + bonus_time_points + bonus_first 
+    return score
+    
+def is_first_solve(sub_id:tuple[int,int,int],day:int):
+    # incomplete function 
+    first = get_first(day)
+    return sub_id == first
+
+def get_datetime_info(timestamp:datetime):
+    conv_time = timestamp.astimezone(ZoneInfo("EST")) # convert to EST time
+    day = conv_time.day
+    hour = conv_time.hour
+    return day,hour
+
+def get_first(day):
+    # missing function
+    pass
+    # query all "CORRECT"s of <DAY> and return the SUBMISSION ID first one
diff --git a/aocp/verifier.py b/aocp/verifier.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba4fcf3de8370a351e1a0797fb5e957013a8e071
--- /dev/null
+++ b/aocp/verifier.py
@@ -0,0 +1,69 @@
+import dateparser
+from datetime import date
+from flask import current_app
+from flask import app 
+import click
+from . import db as database
+import time
+from . import aoc
+from typing import cast
+
+
+# p consists of: id, timestamp, answer, status,
+def verify():
+    """
+    fetches all currently pending submissions from the database and checks 
+    if theyre correct. Checks if a solution already exists in the solution db
+    and uses it; makes a request to the aoc website otherwise.
+    if the submitted answer is correct the status of the submission in the db
+    gets updated accordingly.
+    if a ratelimit is returned this program will sleep for the remaining time.
+    """
+    db = database.get_db() # dataframe
+    pending = fetch_all_pending() # returns a dataframe
+    for index, p in pending.iterrows():
+        (uid,day,val) = cast(tuple,index)
+        year = current_app.config["YEAR"]
+        solution = db.fetch_day_solution(day) # is equal to todays solution, if there is one. Is None otherwise.
+        if solution:
+            if solution == val:
+                db.update_submission_status(userid=uid,day=day,value=val,status="CORRECT")
+            else:
+                db.update_submission_status(userid=uid,day=day,value=val,status="INCORRECT")
+        else:
+            status, timeout = aoc.submit_answer(year, day, 1, val)
+            if status == aoc.Status.PASS:
+                db.update_submission_status(userid=uid,day=day,value=val,status="CORRECT")
+                db.set_day_solution(day,val)
+            elif status == aoc.Status.FAIL:
+                db.update_submission_status(userid=uid,day=day,value=val,status="INCORRECT")
+            elif status == aoc.Status.RATE_LIMIT:
+                if timeout:
+                    time.sleep(timeout)
+            elif status == aoc.Status.NOT_LOGGED_IN:
+                current_app.logger.error("no/invalid aoc session cookie.")
+            elif status == aoc.Status.COMPLETED:
+                current_app.logger.warning("request made although puzzle already solved. not good.")
+            elif status == aoc.Status.UNKNOWN:
+                current_app.logger.warning("unknown status returned.")
+            
+        
+def fetch_all_pending():
+    return database.get_db().fetch_submissions(status="PENDING").sample(frac=1)
+    # gets all database entries with the status "pending" as a dataframe
+        
+@click.command('test-verifier')
+def test_verifier_command():
+    """Execute the verifier once for testing."""
+    click.echo('Testing verifier...')
+    #submit_answer(2022,1,1,"30")
+    current_app.logger.debug(database.get_db().set_day_puzzle(day=3,puzzle=b'203nsk20'))
+    current_app.logger.debug(database.get_db().set_day_puzzle(day=7,puzzle=b'2138'))
+    current_app.logger.debug(database.get_db().set_day_puzzle(day=20,puzzle=b'abcabca'))
+    current_app.logger.debug(database.get_db().set_day_puzzle(day=1,puzzle=b'aksjndaks'))
+    current_app.logger.debug(database.get_db().set_day_solution(3,solution="123"))
+    current_app.logger.debug(database.get_db().set_day_solution(7,solution="13"))
+    current_app.logger.debug(database.get_db().set_day_solution(20,solution="abcabca"))
+    current_app.logger.debug(verify())
+def init_app(app):
+    app.cli.add_command(test_verifier_command)
diff --git a/requirements.txt b/requirements.txt
index de3815dd61c4eec6123f8444c314cf6238c7cd9e..74149a9ea74687ee0c39463eeb2680bf9ae30b09 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,6 +7,7 @@ Jinja2>=3.1.2
 MarkupSafe>=2.1.3
 packaging>=23.2
 Werkzeug>=3.0.0
+dateparser>=1.1.8
 requests>=2.31.0
 authlib>=1.2.1
 pandas>=2.1.2