diff --git a/gso/oss-params-example.json b/gso/oss-params-example.json
index 9c48d63db1dd48d0fbc489b8260aae536ee3b641..cc46f4f52f82ac0d768772ee642733bb9873b4db 100644
--- a/gso/oss-params-example.json
+++ b/gso/oss-params-example.json
@@ -2,7 +2,9 @@
   "GENERAL": {
     "public_hostname": "https://gap.geant.org"
   },
-  "RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444",
+  "RESOURCE_MANAGEMENT": {
+    "todo": "todo"
+  },
   "IPAM": {
     "INFOBLOX": {
       "scheme": "https",
diff --git a/gso/services/resource_manager.py b/gso/services/resource_manager.py
index 2fc41f625f7595ac02dac7de4af6cb5e290f9ac2..3dadd19c08365d6b82ae6b91e45b483e775965a5 100644
--- a/gso/services/resource_manager.py
+++ b/gso/services/resource_manager.py
@@ -1,31 +1,140 @@
 # mypy: ignore-errors
-import requests
+from enum import Enum, auto
+from typing import List
 
 from gso import settings
 
+# TODO
+# - fill in the implementations
+# - consider the additional api methods
+# - decided what to do with various error conditions (currently assertions)
 
-def import_new_router(router_name: str, oss_params=settings.OSSParams) -> None:
-    r = requests.post(
-        f"{oss_params.RESOURCE_MANAGER_API_PREFIX}" f"/api/interfaces/initialize-router/{router_name}", timeout=10000
-    )
-    r.raise_for_status()
 
+class InterfaceAllocationState(Enum):
+    AVAILABLE = auto()
+    RESERVED = auto()
+    ALLOCATED = auto()
 
-def next_lag(router_name: str, oss_params=settings.OSSParams) -> dict:
-    r = requests.post(
-        f"{oss_params.RESOURCE_MANAGER_API_PREFIX}" f"/api/interfaces/next-lag/{router_name}", timeout=10000
-    )
-    r.raise_for_status()
-    response = r.json()
-    return response["name"]
 
+def _dummy_router_interfaces():
+    return {
+        'lags': [],
+        'physical': [
+            {
+                'name': f'ifc-{x}',
+                'state': InterfaceAllocationState.AVAILABLE
+            } for x in range(250)]
+    }
 
-def next_physical(router_name: str, lag_name: str, oss_params=settings.OSSParams) -> dict:
-    # TODO: speed needed (if first interface)
-    r = requests.post(
-        f"{oss_params.RESOURCE_MANAGER_API_PREFIX}" f"/api/interfaces/next-physical/{router_name}/{lag_name}",
-        timeout=10000,
-    )
-    r.raise_for_status()
-    response = r.json()
-    return response["name"]
+
+_DUMMY_INVENTORY = {
+    'fqdn-a': _dummy_router_interfaces(),
+    'fqdn-b': _dummy_router_interfaces(),
+    'fqdn-c': _dummy_router_interfaces(),
+    'fqdn-d': _dummy_router_interfaces()
+}
+
+
+def import_new_router(
+        new_router_fqdn: str, oss_params=settings.OSSParams):
+    # TODO: this is a dummy implementation
+
+    # TODO: specifiy if this should be an error (and if now, what it means)
+    assert new_router_fqdn not in _DUMMY_INVENTORY
+    _DUMMY_INVENTORY[new_router_fqdn] = _dummy_router_interfaces()
+
+
+def next_lag(router_fqdn: str, oss_params=settings.OSSParams) -> str:
+    # TODO: this is a dummy implementation
+
+    assert router_fqdn in _DUMMY_INVENTORY
+
+    lag_idx = 0
+    while True:
+        lag_name = f'ae-{lag_idx}'
+        if lag_name not in _DUMMY_INVENTORY[router_fqdn]['lags']:
+            _DUMMY_INVENTORY[router_fqdn]['lags'].append(lag_name)
+            return lag_name
+        lag_idx += 1
+
+
+def available_physical_interfaces(
+        router_fqdn: str, oss_params=settings.OSSParams) -> List[str]:
+    # TODO: this is a dummy implementation
+
+    assert router_fqdn in _DUMMY_INVENTORY
+
+    return [
+        ifc['name'] for ifc in _DUMMY_INVENTORY[router_fqdn]['physical']
+        if ifc['state'] == InterfaceAllocationState.AVAILABLE
+    ]
+
+
+def _find_physical(router_fqdn: str, interface_name: str) -> dict:
+    assert router_fqdn in _DUMMY_INVENTORY
+    for ifc in _DUMMY_INVENTORY[router_fqdn]['physical']:
+        if ifc['name'] == interface_name:
+            return ifc
+    assert False, f'interface {interface_name} not found on {router_fqdn}'
+
+
+def reserve_physical_interface(
+        router_fqdn: str,
+        interface_name: str,
+        oss_params=settings.OSSParams):
+    # TODO: this is a dummy implementation
+
+    ifc = _find_physical(router_fqdn, interface_name)
+    assert ifc['state'] == InterfaceAllocationState.AVAILABLE, \
+        f'interface {router_fqdn}:{interface_name} is not available'
+    ifc['state'] = InterfaceAllocationState.RESERVED
+
+
+def allocate_physical_interface(
+        router_fqdn: str,
+        interface_name: str,
+        oss_params=settings.OSSParams):
+    # TODO: this is a dummy implementation
+
+    ifc = _find_physical(router_fqdn, interface_name)
+    # TODO: is there a use case for moving
+    #  directly from AVAILABLE to ALLOCATED?
+    assert ifc['state'] == InterfaceAllocationState.RESERVED, \
+        f'interface {router_fqdn}:{interface_name} is not reserved'
+    ifc['state'] = InterfaceAllocationState.RESERVED
+
+
+def free_physical_interface(
+        router_fqdn: str,
+        interface_name: str,
+        oss_params=settings.OSSParams):
+    # TODO: this is a dummy implementation
+
+    ifc = _find_physical(router_fqdn, interface_name)
+    # TODO: is this really an error that should be handled?
+    #  ... or is it ok to ignore this?
+    assert ifc['state'] != InterfaceAllocationState.AVAILABLE, \
+        f'interface {router_fqdn}:{interface_name} is already available'
+    ifc['state'] = InterfaceAllocationState.AVAILABLE
+
+
+def free_lag(
+        router_fqdn: str,
+        lag_name: str,
+        oss_params=settings.OSSParams):
+    # TODO: is this a use case that should be handled?
+    # e.g. who keeps track of the bundled physical interfaces?
+    pass
+
+
+def remove_router(
+        router_fqdn: str, oss_params=settings.OSSParams):
+    # TODO: is this a use case that should be handled?
+    pass
+
+
+def all_lags(
+        router_fqdn: str, oss_params=settings.OSSParams) -> List[str]:
+    # TODO: is this a use case that should be handled?
+    assert router_fqdn in _DUMMY_INVENTORY
+    return _DUMMY_INVENTORY[router_fqdn]['lags']
diff --git a/gso/settings.py b/gso/settings.py
index 88f5dc924a4108e8dbd6aed6c49d2f5a78c64c2a..2d527ff1eddb19ef95ba1068d2deda6b604e9c4f 100644
--- a/gso/settings.py
+++ b/gso/settings.py
@@ -75,12 +75,17 @@ class ProvisioningProxyParams(BaseSettings):
     api_version: int
 
 
+class ResourceManagementParams(BaseSettings):
+    """TO DO: resource management parameters"""
+    todo: str
+
+
 class OSSParams(BaseSettings):
     """The set of parameters required for running GSO."""
 
     GENERAL: GeneralParams
     IPAM: IPAMParams
-    RESOURCE_MANAGER_API_PREFIX: str
+    RESOURCE_MANAGEMENT: ResourceManagementParams
     PROVISIONING_PROXY: ProvisioningProxyParams
 
 
diff --git a/test/conftest.py b/test/conftest.py
index a53a4c7971d0340dcd2bc193ca4dd7b5e50a95aa..1ef473a1c3cbcadb18a98932945ca3bb6c6b4ede 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -14,7 +14,9 @@ def configuration_data() -> dict:
         s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         yield {
             "GENERAL": {"public_hostname": "https://gap.geant.org"},
-            "RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444",
+            "RESOURCE_MANAGEMENT": {
+                'todo': 'todo'
+            },
             "IPAM": {
                 "INFOBLOX": {
                     "scheme": "https",
diff --git a/test/test_resource_manager.py b/test/test_resource_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..b99d547f81084544630ffb766b886d2570fb80d3
--- /dev/null
+++ b/test/test_resource_manager.py
@@ -0,0 +1,56 @@
+import random
+import string
+
+from gso.services import resource_manager
+
+
+def _random_string(
+        n=None,
+        letters=string.ascii_letters + string.digits + string.punctuation):
+    if not n:
+        n = random.randint(1, 20)
+    return ''.join(random.choices(letters, k=n))
+
+
+def test_new_router():
+    router_name = _random_string(10)
+    assert router_name not in resource_manager._DUMMY_INVENTORY
+    resource_manager.import_new_router(new_router_fqdn=router_name)
+    assert router_name in resource_manager._DUMMY_INVENTORY
+
+
+def test_new_lag():
+    router_name = list(resource_manager._DUMMY_INVENTORY.keys())[0]
+    new_lags = {
+        resource_manager.next_lag(router_fqdn=router_name)
+        for _ in range(10)
+    }
+    assert len(new_lags) == 10
+    assert new_lags <= set(
+        resource_manager._DUMMY_INVENTORY[router_name]['lags'])
+
+
+def test_physical_allocation_lifecycle_happy():
+    router_name = list(resource_manager._DUMMY_INVENTORY.keys())[0]
+
+    def _interfaces():
+        return resource_manager.available_physical_interfaces(
+            router_fqdn=router_name)
+
+    initial_available = _interfaces()
+
+    interface_name = initial_available[0]
+
+    resource_manager.reserve_physical_interface(
+        router_fqdn=router_name, interface_name=interface_name)
+
+    current_available = _interfaces()
+    assert interface_name not in current_available
+
+    resource_manager.allocate_physical_interface(
+        router_fqdn=router_name, interface_name=interface_name)
+    resource_manager.free_physical_interface(
+        router_fqdn=router_name, interface_name=interface_name)
+
+    current_available = _interfaces()
+    assert interface_name in current_available