diff --git a/lso/__init__.py b/lso/__init__.py
index 746cd003a67a9418f9493ac989e391178f93e896..6c3d936488ab4a5d4717642c7d5043552b4d4454 100644
--- a/lso/__init__.py
+++ b/lso/__init__.py
@@ -6,7 +6,7 @@ from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware
 
 from lso import config, environment
-from lso.routes import default, ip_trunk, router
+from lso.routes import default, ip_trunk, playbook, router
 
 
 def create_app() -> FastAPI:
@@ -25,14 +25,15 @@ def create_app() -> FastAPI:
     )
 
     app.include_router(default.router, prefix="/api")
+    app.include_router(playbook.router, prefix="/api/playbook")
     app.include_router(router.router, prefix="/api/router")
     app.include_router(ip_trunk.router, prefix="/api/ip_trunk")
 
     # test that config params are loaded and available
     config.load()
 
-    logging.info("FastAPI app initialized")
-
     environment.setup_logging()
 
+    logging.info("FastAPI app initialized")
+
     return app
diff --git a/lso/playbook.py b/lso/playbook.py
index 51320f386f073f44129a2f85fa805400a8b7a834..a5e24c844df853a1207504830121fdef80cd7b9b 100644
--- a/lso/playbook.py
+++ b/lso/playbook.py
@@ -176,12 +176,17 @@ def _run_playbook_proc(
         logger.error(msg)
 
 
-def run_playbook(playbook_path: Path, extra_vars: dict, inventory: str, callback: HttpUrl) -> PlaybookLaunchResponse:
+def run_playbook(
+    playbook_path: Path,
+    extra_vars: dict[str, Any],
+    inventory: dict[str, Any] | str,
+    callback: HttpUrl,
+) -> PlaybookLaunchResponse:
     """Run an Ansible playbook against a specified inventory.
 
     :param Path playbook_path: playbook to be executed.
-    :param dict extra_vars: Any extra vars needed for the playbook to run.
-    :param [str] inventory: The inventory that the playbook is executed against.
+    :param dict[str, Any] extra_vars: Any extra vars needed for the playbook to run.
+    :param dict[str, Any] | str inventory: The inventory that the playbook is executed against.
     :param :class:`HttpUrl` callback: Callback URL where the playbook should send a status update when execution is
         completed. This is used for workflow-orchestrator to continue with the next step in a workflow.
     :return: Result of playbook launch, this could either be successful or unsuccessful.
diff --git a/lso/routes/default.py b/lso/routes/default.py
index d6f7292b47ab2eb44bd89e6addcdc8a412648146..25c74eee7db2b3047f267ef867ff88d5a0fe77e1 100644
--- a/lso/routes/default.py
+++ b/lso/routes/default.py
@@ -8,7 +8,7 @@ from importlib import metadata
 from fastapi import APIRouter
 from pydantic import BaseModel, constr
 
-API_VERSION = "0.1"
+API_VERSION = "0.2"
 VersionString = constr(pattern=r"\d+\.\d+")
 
 router = APIRouter()
diff --git a/lso/routes/playbook.py b/lso/routes/playbook.py
new file mode 100644
index 0000000000000000000000000000000000000000..670848e438d61abde2634de0a899d476a9c602d4
--- /dev/null
+++ b/lso/routes/playbook.py
@@ -0,0 +1,46 @@
+"""The API endpoint from which Ansible playbooks can be executed."""
+
+from typing import Any
+
+from fastapi import APIRouter
+from pydantic import BaseModel, HttpUrl
+
+from lso.playbook import PlaybookLaunchResponse, get_playbook_path, run_playbook
+
+router = APIRouter()
+
+
+class PlaybookRunParams(BaseModel):
+    """Parameters for executing an Ansible playbook."""
+
+    #: The filename of a playbook that is executed. It should be present inside the directory defined in the
+    #: configuration option ``ansible_playbooks_root_dir``.
+    playbook_name: str
+    #: The address where LSO should call back to upon completion.
+    callback: HttpUrl
+    #: The inventory to run the playbook against. This inventory can also include any host vars, if needed. When
+    #: including host vars, it should be a dictionary. Can be a simple string containing hostnames when no host vars are
+    #: needed. In the latter case, multiple hosts should be separated with a newline character.
+    inventory: dict[str, Any] | str
+    #: Extra variables that should get passed to the playbook. This includes any required configuration objects
+    #: from the workflow orchestrator, commit comments, whether this execution should be a dry run, a trouble ticket
+    #: number, etc. Which extra vars are required solely depends on what inputs the playbook requires.
+    extra_vars: dict
+
+
+@router.post("/")
+def run_playbook_endpoint(params: PlaybookRunParams) -> PlaybookLaunchResponse:
+    """Launch an Ansible playbook to modify or deploy a subscription instance.
+
+    The response will contain either a job ID, or error information.
+
+    :param params :class:`PlaybookRunParams`: Parameters for executing a playbook.
+    :return: Response from the Ansible runner, including a run ID.
+    :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
+    """
+    return run_playbook(
+        playbook_path=get_playbook_path(params.playbook_name),
+        extra_vars=params.extra_vars,
+        inventory=params.inventory,
+        callback=params.callback,
+    )