diff --git a/.gitignore b/.gitignore
index 71e1def92b37ee1e2a107c9b37e26b2794b7060e..20969d16f10cfe87950112c3dfb1a3a00e4ef5fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ __pycache__/
 *.egg-info
 .coverage
 coverage.xml
-.tox/
+.tox/device_vendor
+.vscode
\ No newline at end of file
diff --git a/geant_service_orchestrator/main.py b/geant_service_orchestrator/main.py
index e14ab09627003191a31902f1170dfc71449e7f8c..9ea59ed79b973e8cb0d4dd20d43865f58e9a2f6f 100644
--- a/geant_service_orchestrator/main.py
+++ b/geant_service_orchestrator/main.py
@@ -2,7 +2,7 @@ from orchestrator import OrchestratorCore
 from orchestrator.cli.main import app as core_cli
 from orchestrator.settings import AppSettings
 import geant_service_orchestrator.products  # noqa: F401
-# import workflows
+import geant_service_orchestrator.workflows  # noqa: F401
 
 
 app = OrchestratorCore(base_settings=AppSettings())
diff --git a/geant_service_orchestrator/migrations/versions/2023-04-05_857225661207_add_device_workflows.py b/geant_service_orchestrator/migrations/versions/2023-04-05_857225661207_add_device_workflows.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4d7d7fed664375254113c0537cdbe2ce6d90beb
--- /dev/null
+++ b/geant_service_orchestrator/migrations/versions/2023-04-05_857225661207_add_device_workflows.py
@@ -0,0 +1,39 @@
+"""add Device workflows.
+
+Revision ID: 857225661207
+Revises: f4959f32c866
+Create Date: 2023-04-05 09:16:03.725750
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '857225661207'
+down_revision = 'f4959f32c866'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_device",
+        "target": "CREATE",
+        "description": "Create Device",
+        "product_type": "Device"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/geant_service_orchestrator/products/product_types/device.py b/geant_service_orchestrator/products/product_types/device.py
index a630d2b3378ee2b37b428a239a8e05ddc53c7b42..0b20fa9080744ee682734b83bf0c0866be42c680 100644
--- a/geant_service_orchestrator/products/product_types/device.py
+++ b/geant_service_orchestrator/products/product_types/device.py
@@ -11,8 +11,8 @@ class DeviceType(strEnum):
 
 
 class DeviceVendor(strEnum):
-    Juniper = "juniper"
-    Newvendor = "newvendor"
+    Juniper = "Juniper"
+    Newvendor = "Newvendor"
 
 
 class DeviceInactive(SubscriptionModel, is_base=True):
diff --git a/geant_service_orchestrator/services/ipam.py b/geant_service_orchestrator/services/ipam.py
index 30d2a07b5edbeb4b5fb8ef9b7c45b7954b0737b8..021bcb4480859835a7fe081d294f91fa7f44f33a 100644
--- a/geant_service_orchestrator/services/ipam.py
+++ b/geant_service_orchestrator/services/ipam.py
@@ -14,8 +14,9 @@ class HostAddresses(BaseSettings):
 
 
 def new_service_networks(
-        ipam: settings.IPAMParams,
         service_params: settings.ServiceNetworkParams) -> ServiceNetworks:
+    oss = settings.load_oss_params()
+    assert oss.IPAM.INFOBLOX
     # TODO: load from ipam
     # cf. https://gitlab.geant.org/goat/gap-jenkins/-/blob/development/service-editor/gap_service_editor/ipam.py#L35-66  # noqa: E501
     return ServiceNetworks(
@@ -23,8 +24,10 @@ def new_service_networks(
         v6=ipaddress.IPv6Network('dead:beef::/120'))
 
 
-def new_host_address(fqdn: str, networks: ServiceNetworks) -> HostAddresses:
+def new_device_lo_address() -> HostAddresses:
+    oss = settings.load_oss_params()
+    assert oss.IPAM.INFOBLOX
     # TODO: load from ipam
     return HostAddresses(
-        v4=ipaddress.IPv4Address('10.0.0.1'),
-        v6=ipaddress.IPv6Address('dead:beef::1'))
+        v4=ipaddress.IPv4Address('10.10.10.10'),
+        v6=ipaddress.IPv6Address('fc00:798:aa:1::10'))
diff --git a/geant_service_orchestrator/services/provisioning_proxy.py b/geant_service_orchestrator/services/provisioning_proxy.py
index 96874ed8bb549bc5ab2407cf420a53bcafd827bf..2a3626d3976ad735a82b72759c32f7a3d662f8b9 100644
--- a/geant_service_orchestrator/services/provisioning_proxy.py
+++ b/geant_service_orchestrator/services/provisioning_proxy.py
@@ -6,7 +6,10 @@ import requests
 
 def provision_node(
         node_subscription_params: DeviceBlock,
-        pp_params=settings.OSSParams.PROVISIONING_PROXY):
+        dry_run: bool = False):
+    oss = settings.load_oss_params()
+    pp_params = oss.PROVISIONING_PROXY
+    assert pp_params
     r = requests.get(
         f'https://{pp_params.api_base}'
         f'/api/version',
diff --git a/geant_service_orchestrator/workflows/__init__.py b/geant_service_orchestrator/workflows/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d93b338a9c01be9a2e5c3fee0c46f341ef5c98ae
--- /dev/null
+++ b/geant_service_orchestrator/workflows/__init__.py
@@ -0,0 +1,5 @@
+from orchestrator.workflows import LazyWorkflowInstance
+
+LazyWorkflowInstance("workflows.device.create_device", "create_device")
+LazyWorkflowInstance("workflows.device.terminate_device", "terminate_device")
+LazyWorkflowInstance("workflows.device.get_facts", "get_facts")
diff --git a/geant_service_orchestrator/workflows/device/create_device.py b/geant_service_orchestrator/workflows/device/create_device.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8bffe75ebf21421d1f59fbfecf13171e1bf58e4
--- /dev/null
+++ b/geant_service_orchestrator/workflows/device/create_device.py
@@ -0,0 +1,200 @@
+import ipaddress
+from uuid import uuid4
+
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.workflow import inputstep
+from orchestrator.forms.validators import Accept
+from orchestrator.types import FormGenerator, State
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+from orchestrator.workflow import done, init, step, workflow
+from orchestrator.workflows.steps import resync, set_status
+from orchestrator.workflows.steps import store_process_subscription
+from orchestrator.workflows.utils import wrap_create_initial_input_form
+from geant_service_orchestrator.products.product_types import device
+from geant_service_orchestrator.services import ipam, provisioning_proxy
+
+
+def initial_input_form_generator(product_name: str) -> FormGenerator:
+    class CreateDeviceForm(FormPage):
+        class Config:
+            title = product_name
+
+        fqdn: str
+        ts_address: ipaddress.IPv4Address
+        ts_port: int
+        device_vendor: device.DeviceVendor
+
+    user_input = yield CreateDeviceForm
+
+    return user_input.dict()
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr) -> State:
+    subscription = device.DeviceInactive.from_product_id(product, uuid4())
+
+    return {
+        "subscription": subscription,
+        "subscription_id": subscription.subscription_id,
+    }
+
+
+@step("Get information from IPAM ")
+def get_info_from_ipam(subscription: device.DeviceInactive) -> State:
+    lo = ipam.new_device_lo_address()
+    subscription.device.lo_ipv4_address = lo.v4
+    subscription.device.lo_ipv6_address = lo.v6
+    # TODO: get info about how these should be generated
+    subscription.device.lo_iso_address = "49.51e5.0001.0620.4009.6047.00"
+    subscription.device.si_ipv4_network = "192.168.0.0/31"
+    subscription.device.ias_lt_ipv4_network = "192.168.1.0/31"
+    subscription.device.ias_lt_ipv6_network = "fc00:798:1::150/126"
+    return {"subscription": subscription}
+
+
+@step("get information about SNMP")
+def get_snmp_info(subscription: device.DeviceInactive) -> State:
+    country = 'Spain'
+    city = 'Barcelona'
+    country_code = 'ES'
+    latitude = '41.3743'
+    longitude = '2.1328'
+    subscription.device.site_country = country
+    subscription.device.site_city = city
+    subscription.device.site_country_code = country_code
+    subscription.device.site_latitude = latitude
+    subscription.device.site_longitude = longitude
+    subscription.device.snmp_location = (
+        f'{city.upper()},{country.upper()}[{latitude},{longitude}]'
+    )
+
+    return {"subscription": subscription}
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: device.DeviceInactive,
+    fqdn: str,
+    ts_address: ipaddress.IPv4Address,
+    ts_port: str,
+    device_vendor: device.DeviceVendor
+) -> State:
+    subscription.device.fqdn = fqdn
+    subscription.device.ts_address = str(ts_address)
+    subscription.device.ts_port = str(ts_port)
+    subscription.device_vendor = device_vendor
+    subscription.description = f"Device {fqdn} type \
+                                ({subscription.device_type})"
+    subscription = device.DeviceProvisioning.from_other_lifecycle(
+        subscription, SubscriptionLifecycle.PROVISIONING
+    )
+
+    return {"subscription": subscription}
+
+
+@step("Provision device [DRY RUN]")
+def provision_device_dry(
+    subscription: device.DeviceProvisioning,
+    fqdn: str,
+    ts_address: str,
+    ts_port: str
+) -> State:
+    # import ansible_runner
+    #
+    # r = ansible_runner.run(
+    #     private_data_dir="/opt/geant-gap-ansible",
+    #     playbook="base_config.yaml",
+    #     inventory=subscription.device.fqdn,
+    #     extravars={
+    #         "lo_ipv4_address": str(subscription.device.lo_ipv4_address),
+    #         "lo_ipv6_address": str(subscription.device.lo_ipv6_address),
+    #         "lo_iso_address": subscription.device.lo_iso_address,
+    #         "snmp_location": subscription.device.snmp_location,
+    #         "si_ipv4_network": str(subscription.device.si_ipv4_network),
+    #         "lt_ipv4_network": str(subscription.device.ias_lt_ipv4_network),
+    #         "lt_ipv6_network": str(subscription.device.ias_lt_ipv6_network),
+    #         "site_country_code": subscription.device.site_country_code,
+    #         "verb": "deploy",
+    #     },
+    # )
+    # out = r.stdout.read()
+    # out_splitted = out.splitlines()
+    # # if r.rc != 0:
+    # #    raise ValueError("Ansible has failed")
+    # return {"dry_run_output": out_splitted, "return_code": r.rc}
+    provisioning_proxy.provision_node(
+        node_subscription_params=subscription,
+        dry_run=True)
+    # TODO: figure out what to return when we are suspending & waiting
+    #       for the provisioning-proxy to call back
+    return {"return_code": 0}
+
+
+@inputstep("Confirm step", assignee="CHANGES")
+def confirm_step() -> FormGenerator:
+    class ConfirmForm(FormPage):
+        confirm: Accept
+
+    user_input = yield ConfirmForm
+
+    return {"confirm": user_input.confirm}
+
+
+@step("Provision device [FOR REAL]")
+def provision_device_real(
+    subscription: device.DeviceProvisioning,
+    fqdn: str,
+    ts_address: str,
+    ts_port: str
+) -> State:
+    # import ansible_runner
+    #
+    # r = ansible_runner.run(
+    #     private_data_dir="/opt/geant-gap-ansible",
+    #     playbook="base_config.yaml",
+    #     inventory=subscription.device.fqdn,
+    #     extravars={
+    #         "lo_ipv4_address": str(subscription.device.lo_ipv4_address),
+    #         "lo_ipv6_address": str(subscription.device.lo_ipv6_address),
+    #         "lo_iso_address": subscription.device.lo_iso_address,
+    #         "snmp_location": subscription.device.snmp_location,
+    #         "si_ipv4_network": str(subscription.device.si_ipv4_network),
+    #         "lt_ipv4_network": str(subscription.device.ias_lt_ipv4_network),
+    #         "lt_ipv6_network": str(subscription.device.ias_lt_ipv6_network),
+    #         "site_country_code": subscription.device.site_country_code,
+    #         "verb": "deploy",
+    #     },
+    # )
+    # out = r.stdout.read()
+    # out_splitted = out.splitlines()
+    #
+    # return {"real_run_output": out_splitted, "return_code": r.rc}
+    provisioning_proxy.provision_node(
+        node_subscription_params=subscription)
+    # TODO: figure out what to return when we are suspending & waiting
+    #       for the provisioning-proxy to call back
+    return {"return_code": 0}
+
+
+@workflow(
+    "Create Device",
+    initial_input_form=wrap_create_initial_input_form(
+                       initial_input_form_generator),
+    target=Target.CREATE,
+)
+def create_device():
+    return (
+        init
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> get_info_from_ipam
+        >> get_snmp_info
+        >> initialize_subscription
+        >> provision_device_dry
+        >> confirm_step
+        >> provision_device_real
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/geant_service_orchestrator/workflows/device/get_facts.py b/geant_service_orchestrator/workflows/device/get_facts.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cc1cbe21da2acb7e90af1a306bf7855e507af30
--- /dev/null
+++ b/geant_service_orchestrator/workflows/device/get_facts.py
@@ -0,0 +1,60 @@
+from orchestrator.forms import FormPage
+from orchestrator.forms.validators import Label
+from orchestrator.targets import Target
+# from orchestrator.types import SubscriptionLifecycle
+from orchestrator.types import InputForm, UUIDstr
+from orchestrator.workflow import done, init, step, workflow
+# from orchestrator.workflows.steps import (
+#     resync,
+#     set_status,
+#     store_process_subscription,
+#     unsync,
+# )
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from products import Device
+
+
+def initial_input_form_generator(
+    subscription_id: UUIDstr, organisation: UUIDstr
+) -> InputForm:
+    subscription = Device.from_subscription(subscription_id)
+
+    class TerminateForm(FormPage):
+        are_you_sure: Label = (
+            f"Are you sure you want to get facts from \
+            {subscription.description}?"
+        )
+
+    return TerminateForm
+
+
+@step("Get facts")
+def get_facts(subscription_id) -> None:
+    subscription = Device.from_subscription(subscription_id)
+    import ansible_runner
+
+    r = ansible_runner.run(
+        private_data_dir="/opt",
+        playbook="get_facts.yaml",
+        inventory=subscription.device.fqdn,
+    )
+    out = r.stdout.read()
+    out_splitted = out.splitlines()
+
+    return {"output": out_splitted}
+
+
+@workflow(
+    "Get Facts from Device",
+    initial_input_form=wrap_modify_initial_input_form(
+                       initial_input_form_generator),
+    target=Target.SYSTEM,
+)
+def get_facts_from_device():
+    return (
+        init
+        >> get_facts
+        # >> resync
+        >> done
+    )
diff --git a/geant_service_orchestrator/workflows/device/terminate_device.py b/geant_service_orchestrator/workflows/device/terminate_device.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c175778ca3e302dd13bf809001087e698fe6d27
--- /dev/null
+++ b/geant_service_orchestrator/workflows/device/terminate_device.py
@@ -0,0 +1,53 @@
+from orchestrator.forms import FormPage
+from orchestrator.forms.validators import Label
+from orchestrator.targets import Target
+from orchestrator.types import InputForm, SubscriptionLifecycle, UUIDstr
+from orchestrator.workflow import done, init, step, workflow
+from orchestrator.workflows.steps import (
+    resync,
+    set_status,
+    store_process_subscription,
+    unsync,
+)
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from geant_service_orchestrator.products.product_types.device import Device
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
+    subscription = Device.from_subscription(subscription_id)
+
+    class TerminateForm(FormPage):
+        are_you_sure: Label = (
+            f"Are you sure you want to remove {subscription.description}?"
+        )  # type:ignore
+
+    return TerminateForm
+
+
+def _deprovision_in_user_management_system(fqdn: str) -> str:
+    pass
+
+
+@step("Deprovision device")
+def deprovision_user(subscription: Device) -> None:
+    #    _deprovision_in_user_management_system(subscription.user.user_id)
+    pass
+
+
+@workflow(
+    "Terminate device",
+    initial_input_form=wrap_modify_initial_input_form(
+                       initial_input_form_generator),
+    target=Target.TERMINATE,
+)
+def terminate_user():
+    return (
+        init
+        >> store_process_subscription(Target.TERMINATE)
+        >> unsync
+        >> deprovision_user
+        >> set_status(SubscriptionLifecycle.TERMINATED)
+        >> resync
+        >> done
+    )