diff --git a/bfxapi/rest/__init__.py b/bfxapi/rest/__init__.py
index bb7d294..850f472 100644
--- a/bfxapi/rest/__init__.py
+++ b/bfxapi/rest/__init__.py
@@ -1,6 +1 @@
-from .endpoints import (
-    BfxRestInterface,
-    RestAuthEndpoints,
-    RestMerchantEndpoints,
-    RestPublicEndpoints,
-)
+from ._bfx_rest_interface import BfxRestInterface
diff --git a/bfxapi/rest/endpoints/bfx_rest_interface.py b/bfxapi/rest/_bfx_rest_interface.py
similarity index 50%
rename from bfxapi/rest/endpoints/bfx_rest_interface.py
rename to bfxapi/rest/_bfx_rest_interface.py
index dff3a43..49aa3ed 100644
--- a/bfxapi/rest/endpoints/bfx_rest_interface.py
+++ b/bfxapi/rest/_bfx_rest_interface.py
@@ -1,14 +1,20 @@
-from .rest_auth_endpoints import RestAuthEndpoints
-from .rest_merchant_endpoints import RestMerchantEndpoints
-from .rest_public_endpoints import RestPublicEndpoints
-
-
-class BfxRestInterface:
-    VERSION = 2
-
-    def __init__(self, host, api_key=None, api_secret=None):
-        self.public = RestPublicEndpoints(host=host)
-        self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)
-        self.merchant = RestMerchantEndpoints(
-            host=host, api_key=api_key, api_secret=api_secret
-        )
+from typing import Optional
+
+from bfxapi.rest._interfaces import (
+    RestAuthEndpoints,
+    RestMerchantEndpoints,
+    RestPublicEndpoints,
+)
+
+
+class BfxRestInterface:
+    def __init__(
+        self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
+    ):
+        self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)
+
+        self.merchant = RestMerchantEndpoints(
+            host=host, api_key=api_key, api_secret=api_secret
+        )
+
+        self.public = RestPublicEndpoints(host=host)
diff --git a/bfxapi/rest/_interface/__init__.py b/bfxapi/rest/_interface/__init__.py
new file mode 100644
index 0000000..a45df51
--- /dev/null
+++ b/bfxapi/rest/_interface/__init__.py
@@ -0,0 +1 @@
+from .interface import Interface
diff --git a/bfxapi/rest/_interface/interface.py b/bfxapi/rest/_interface/interface.py
new file mode 100644
index 0000000..2975eb4
--- /dev/null
+++ b/bfxapi/rest/_interface/interface.py
@@ -0,0 +1,10 @@
+from typing import Optional
+
+from .middleware import Middleware
+
+
+class Interface:
+    def __init__(
+        self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
+    ):
+        self._m = Middleware(host, api_key, api_secret)
diff --git a/bfxapi/rest/_interface/middleware.py b/bfxapi/rest/_interface/middleware.py
new file mode 100644
index 0000000..f2e2b83
--- /dev/null
+++ b/bfxapi/rest/_interface/middleware.py
@@ -0,0 +1,129 @@
+import hashlib
+import hmac
+import json
+from datetime import datetime
+from enum import IntEnum
+from typing import TYPE_CHECKING, Any, List, Optional
+
+import requests
+
+from bfxapi._utils.json_decoder import JSONDecoder
+from bfxapi._utils.json_encoder import JSONEncoder
+from bfxapi.exceptions import InvalidCredentialError
+from bfxapi.rest.exceptions import RequestParametersError, UnknownGenericError
+
+if TYPE_CHECKING:
+    from requests.sessions import _Params
+
+
+class _Error(IntEnum):
+    ERR_UNK = 10000
+    ERR_GENERIC = 10001
+    ERR_PARAMS = 10020
+    ERR_AUTH_FAIL = 10100
+
+
+class Middleware:
+    __TIMEOUT = 30
+
+    def __init__(
+        self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
+    ):
+        self.__host = host
+
+        self.__api_key = api_key
+
+        self.__api_secret = api_secret
+
+    def get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
+        headers = {"Accept": "application/json"}
+
+        if self.__api_key and self.__api_secret:
+            headers = {**headers, **self.__get_authentication_headers(endpoint)}
+
+        request = requests.get(
+            url=f"{self.__host}/{endpoint}",
+            params=params,
+            headers=headers,
+            timeout=Middleware.__TIMEOUT,
+        )
+
+        data = request.json(cls=JSONDecoder)
+
+        if isinstance(data, list) and len(data) > 0 and data[0] == "error":
+            self.__handle_error(data)
+
+        return data
+
+    def post(
+        self,
+        endpoint: str,
+        body: Optional[Any] = None,
+        params: Optional["_Params"] = None,
+    ) -> Any:
+        _body = body and json.dumps(body, cls=JSONEncoder) or None
+
+        headers = {"Accept": "application/json", "Content-Type": "application/json"}
+
+        if self.__api_key and self.__api_secret:
+            headers = {
+                **headers,
+                **self.__get_authentication_headers(endpoint, _body),
+            }
+
+        request = requests.post(
+            url=f"{self.__host}/{endpoint}",
+            data=_body,
+            params=params,
+            headers=headers,
+            timeout=Middleware.__TIMEOUT,
+        )
+
+        data = request.json(cls=JSONDecoder)
+
+        if isinstance(data, list) and len(data) > 0 and data[0] == "error":
+            self.__handle_error(data)
+
+        return data
+
+    def __handle_error(self, error: List[Any]) -> None:
+        if error[1] == _Error.ERR_PARAMS:
+            raise RequestParametersError(
+                "The request was rejected with the following parameter"
+                f"error: <{error[2]}>"
+            )
+
+        if error[1] == _Error.ERR_AUTH_FAIL:
+            raise InvalidCredentialError(
+                "Cannot authenticate with given API-KEY and API-SECRET."
+            )
+
+        if not error[1] or error[1] == _Error.ERR_UNK or error[1] == _Error.ERR_GENERIC:
+            raise UnknownGenericError(
+                "The server replied to the request with a generic error with "
+                f"the following message: <{error[2]}>."
+            )
+
+    def __get_authentication_headers(self, endpoint: str, data: Optional[str] = None):
+        assert (
+            self.__api_key and self.__api_secret
+        ), "API-KEY and API-SECRET must be strings."
+
+        nonce = str(round(datetime.now().timestamp() * 1_000_000))
+
+        if not data:
+            message = f"/api/v2/{endpoint}{nonce}"
+        else:
+            message = f"/api/v2/{endpoint}{nonce}{data}"
+
+        signature = hmac.new(
+            key=self.__api_secret.encode("utf8"),
+            msg=message.encode("utf8"),
+            digestmod=hashlib.sha384,
+        )
+
+        return {
+            "bfx-nonce": nonce,
+            "bfx-signature": signature.hexdigest(),
+            "bfx-apikey": self.__api_key,
+        }
diff --git a/bfxapi/rest/endpoints/__init__.py b/bfxapi/rest/_interfaces/__init__.py
similarity index 77%
rename from bfxapi/rest/endpoints/__init__.py
rename to bfxapi/rest/_interfaces/__init__.py
index 69a52a9..256c643 100644
--- a/bfxapi/rest/endpoints/__init__.py
+++ b/bfxapi/rest/_interfaces/__init__.py
@@ -1,4 +1,3 @@
-from .bfx_rest_interface import BfxRestInterface
-from .rest_auth_endpoints import RestAuthEndpoints
-from .rest_merchant_endpoints import RestMerchantEndpoints
-from .rest_public_endpoints import RestPublicEndpoints
+from .rest_auth_endpoints import RestAuthEndpoints
+from .rest_merchant_endpoints import RestMerchantEndpoints
+from .rest_public_endpoints import RestPublicEndpoints
diff --git a/bfxapi/rest/endpoints/rest_auth_endpoints.py b/bfxapi/rest/_interfaces/rest_auth_endpoints.py
similarity index 85%
rename from bfxapi/rest/endpoints/rest_auth_endpoints.py
rename to bfxapi/rest/_interfaces/rest_auth_endpoints.py
index d8bcd1c..6c34b14 100644
--- a/bfxapi/rest/endpoints/rest_auth_endpoints.py
+++ b/bfxapi/rest/_interfaces/rest_auth_endpoints.py
@@ -1,7 +1,8 @@
 from decimal import Decimal
-from typing import Dict, List, Literal, Optional, Tuple, Union
+from typing import Any, Dict, List, Literal, Optional, Tuple, Union
 
-from ...types import (
+from bfxapi.rest._interface import Interface
+from bfxapi.types import (
     BalanceAvailable,
     BaseMarginInfo,
     DepositAddress,
@@ -35,18 +36,17 @@
     Withdrawal,
     serializers,
 )
-from ...types.serializers import _Notification
-from ..middleware import Middleware
+from bfxapi.types.serializers import _Notification
 
 
-class RestAuthEndpoints(Middleware):
+class RestAuthEndpoints(Interface):
     def get_user_info(self) -> UserInfo:
-        return serializers.UserInfo.parse(*self._post("auth/r/info/user"))
+        return serializers.UserInfo.parse(*self._m.post("auth/r/info/user"))
 
     def get_login_history(self) -> List[LoginHistory]:
         return [
             serializers.LoginHistory.parse(*sub_data)
-            for sub_data in self._post("auth/r/logins/hist")
+            for sub_data in self._m.post("auth/r/logins/hist")
         ]
 
     def get_balance_available_for_orders_or_offers(
@@ -61,13 +61,13 @@ def get_balance_available_for_orders_or_offers(
         body = {"symbol": symbol, "type": type, "dir": dir, "rate": rate, "lev": lev}
 
         return serializers.BalanceAvailable.parse(
-            *self._post("auth/calc/order/avail", body=body)
+            *self._m.post("auth/calc/order/avail", body=body)
         )
 
     def get_wallets(self) -> List[Wallet]:
         return [
             serializers.Wallet.parse(*sub_data)
-            for sub_data in self._post("auth/r/wallets")
+            for sub_data in self._m.post("auth/r/wallets")
         ]
 
     def get_orders(
@@ -80,7 +80,7 @@ def get_orders(
 
         return [
             serializers.Order.parse(*sub_data)
-            for sub_data in self._post(endpoint, body={"id": ids})
+            for sub_data in self._m.post(endpoint, body={"id": ids})
         ]
 
     def submit_order(
@@ -98,6 +98,7 @@ def submit_order(
         cid: Optional[int] = None,
         flags: Optional[int] = None,
         tif: Optional[str] = None,
+        meta: Optional[Dict[str, Any]] = None,
     ) -> Notification[Order]:
         body = {
             "type": type,
@@ -112,10 +113,11 @@ def submit_order(
             "cid": cid,
             "flags": flags,
             "tif": tif,
+            "meta": meta,
         }
 
         return _Notification[Order](serializers.Order).parse(
-            *self._post("auth/w/order/submit", body=body)
+            *self._m.post("auth/w/order/submit", body=body)
         )
 
     def update_order(
@@ -150,7 +152,7 @@ def update_order(
         }
 
         return _Notification[Order](serializers.Order).parse(
-            *self._post("auth/w/order/update", body=body)
+            *self._m.post("auth/w/order/update", body=body)
         )
 
     def cancel_order(
@@ -161,7 +163,7 @@ def cancel_order(
         cid_date: Optional[str] = None,
     ) -> Notification[Order]:
         return _Notification[Order](serializers.Order).parse(
-            *self._post(
+            *self._m.post(
                 "auth/w/order/cancel", body={"id": id, "cid": cid, "cid_date": cid_date}
             )
         )
@@ -177,7 +179,7 @@ def cancel_order_multi(
         body = {"id": id, "cid": cid, "gid": gid, "all": all}
 
         return _Notification[List[Order]](serializers.Order, is_iterable=True).parse(
-            *self._post("auth/w/order/cancel/multi", body=body)
+            *self._m.post("auth/w/order/cancel/multi", body=body)
         )
 
     def get_orders_history(
@@ -198,13 +200,13 @@ def get_orders_history(
 
         return [
             serializers.Order.parse(*sub_data)
-            for sub_data in self._post(endpoint, body=body)
+            for sub_data in self._m.post(endpoint, body=body)
         ]
 
     def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
         return [
             serializers.OrderTrade.parse(*sub_data)
-            for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades")
+            for sub_data in self._m.post(f"auth/r/order/{symbol}:{id}/trades")
         ]
 
     def get_trades_history(
@@ -225,7 +227,7 @@ def get_trades_history(
 
         return [
             serializers.Trade.parse(*sub_data)
-            for sub_data in self._post(endpoint, body=body)
+            for sub_data in self._m.post(endpoint, body=body)
         ]
 
     def get_ledgers(
@@ -241,43 +243,43 @@ def get_ledgers(
 
         return [
             serializers.Ledger.parse(*sub_data)
-            for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body)
+            for sub_data in self._m.post(f"auth/r/ledgers/{currency}/hist", body=body)
         ]
 
     def get_base_margin_info(self) -> BaseMarginInfo:
         return serializers.BaseMarginInfo.parse(
-            *(self._post("auth/r/info/margin/base")[1])
+            *(self._m.post("auth/r/info/margin/base")[1])
         )
 
     def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
         return serializers.SymbolMarginInfo.parse(
-            *self._post(f"auth/r/info/margin/{symbol}")
+            *self._m.post(f"auth/r/info/margin/{symbol}")
         )
 
     def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
         return [
             serializers.SymbolMarginInfo.parse(*sub_data)
-            for sub_data in self._post("auth/r/info/margin/sym_all")
+            for sub_data in self._m.post("auth/r/info/margin/sym_all")
         ]
 
     def get_positions(self) -> List[Position]:
         return [
             serializers.Position.parse(*sub_data)
-            for sub_data in self._post("auth/r/positions")
+            for sub_data in self._m.post("auth/r/positions")
         ]
 
     def claim_position(
         self, id: int, *, amount: Optional[Union[str, float, Decimal]] = None
     ) -> Notification[PositionClaim]:
         return _Notification[PositionClaim](serializers.PositionClaim).parse(
-            *self._post("auth/w/position/claim", body={"id": id, "amount": amount})
+            *self._m.post("auth/w/position/claim", body={"id": id, "amount": amount})
         )
 
     def increase_position(
         self, symbol: str, amount: Union[str, float, Decimal]
     ) -> Notification[PositionIncrease]:
         return _Notification[PositionIncrease](serializers.PositionIncrease).parse(
-            *self._post(
+            *self._m.post(
                 "auth/w/position/increase", body={"symbol": symbol, "amount": amount}
             )
         )
@@ -286,7 +288,7 @@ def get_increase_position_info(
         self, symbol: str, amount: Union[str, float, Decimal]
     ) -> PositionIncreaseInfo:
         return serializers.PositionIncreaseInfo.parse(
-            *self._post(
+            *self._m.post(
                 "auth/r/position/increase/info",
                 body={"symbol": symbol, "amount": amount},
             )
@@ -301,7 +303,7 @@ def get_positions_history(
     ) -> List[PositionHistory]:
         return [
             serializers.PositionHistory.parse(*sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 "auth/r/positions/hist",
                 body={"start": start, "end": end, "limit": limit},
             )
@@ -316,7 +318,7 @@ def get_positions_snapshot(
     ) -> List[PositionSnapshot]:
         return [
             serializers.PositionSnapshot.parse(*sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 "auth/r/positions/snap",
                 body={"start": start, "end": end, "limit": limit},
             )
@@ -334,7 +336,7 @@ def get_positions_audit(
 
         return [
             serializers.PositionAudit.parse(*sub_data)
-            for sub_data in self._post("auth/r/positions/audit", body=body)
+            for sub_data in self._m.post("auth/r/positions/audit", body=body)
         ]
 
     def set_derivative_position_collateral(
@@ -342,7 +344,7 @@ def set_derivative_position_collateral(
     ) -> DerivativePositionCollateral:
         return serializers.DerivativePositionCollateral.parse(
             *(
-                self._post(
+                self._m.post(
                     "auth/w/deriv/collateral/set",
                     body={"symbol": symbol, "collateral": collateral},
                 )[0]
@@ -353,7 +355,7 @@ def get_derivative_position_collateral_limits(
         self, symbol: str
     ) -> DerivativePositionCollateralLimits:
         return serializers.DerivativePositionCollateralLimits.parse(
-            *self._post("auth/calc/deriv/collateral/limit", body={"symbol": symbol})
+            *self._m.post("auth/calc/deriv/collateral/limit", body={"symbol": symbol})
         )
 
     def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]:
@@ -364,7 +366,7 @@ def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOff
 
         return [
             serializers.FundingOffer.parse(*sub_data)
-            for sub_data in self._post(endpoint)
+            for sub_data in self._m.post(endpoint)
         ]
 
     def submit_funding_offer(
@@ -387,22 +389,24 @@ def submit_funding_offer(
         }
 
         return _Notification[FundingOffer](serializers.FundingOffer).parse(
-            *self._post("auth/w/funding/offer/submit", body=body)
+            *self._m.post("auth/w/funding/offer/submit", body=body)
         )
 
     def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]:
         return _Notification[FundingOffer](serializers.FundingOffer).parse(
-            *self._post("auth/w/funding/offer/cancel", body={"id": id})
+            *self._m.post("auth/w/funding/offer/cancel", body={"id": id})
         )
 
     def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
         return _Notification[Literal[None]](None).parse(
-            *self._post("auth/w/funding/offer/cancel/all", body={"currency": currency})
+            *self._m.post(
+                "auth/w/funding/offer/cancel/all", body={"currency": currency}
+            )
         )
 
     def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
         return _Notification[Literal[None]](None).parse(
-            *self._post("auth/w/funding/close", body={"id": id})
+            *self._m.post("auth/w/funding/close", body={"id": id})
         )
 
     def toggle_auto_renew(
@@ -423,7 +427,7 @@ def toggle_auto_renew(
         }
 
         return _Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse(
-            *self._post("auth/w/funding/auto", body=body)
+            *self._m.post("auth/w/funding/auto", body=body)
         )
 
     def toggle_keep_funding(
@@ -434,7 +438,7 @@ def toggle_keep_funding(
         changes: Optional[Dict[int, Literal[1, 2]]] = None,
     ) -> Notification[Literal[None]]:
         return _Notification[Literal[None]](None).parse(
-            *self._post(
+            *self._m.post(
                 "auth/w/funding/keep",
                 body={"type": type, "id": ids, "changes": changes},
             )
@@ -455,7 +459,7 @@ def get_funding_offers_history(
 
         return [
             serializers.FundingOffer.parse(*sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 endpoint, body={"start": start, "end": end, "limit": limit}
             )
         ]
@@ -468,7 +472,7 @@ def get_funding_loans(self, *, symbol: Optional[str] = None) -> List[FundingLoan
 
         return [
             serializers.FundingLoan.parse(*sub_data)
-            for sub_data in self._post(endpoint)
+            for sub_data in self._m.post(endpoint)
         ]
 
     def get_funding_loans_history(
@@ -486,7 +490,7 @@ def get_funding_loans_history(
 
         return [
             serializers.FundingLoan.parse(*sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 endpoint, body={"start": start, "end": end, "limit": limit}
             )
         ]
@@ -501,7 +505,7 @@ def get_funding_credits(
 
         return [
             serializers.FundingCredit.parse(*sub_data)
-            for sub_data in self._post(endpoint)
+            for sub_data in self._m.post(endpoint)
         ]
 
     def get_funding_credits_history(
@@ -519,7 +523,7 @@ def get_funding_credits_history(
 
         return [
             serializers.FundingCredit.parse(*sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 endpoint, body={"start": start, "end": end, "limit": limit}
             )
         ]
@@ -542,12 +546,12 @@ def get_funding_trades_history(
 
         return [
             serializers.FundingTrade.parse(*sub_data)
-            for sub_data in self._post(endpoint, body=body)
+            for sub_data in self._m.post(endpoint, body=body)
         ]
 
     def get_funding_info(self, key: str) -> FundingInfo:
         return serializers.FundingInfo.parse(
-            *(self._post(f"auth/r/info/funding/{key}")[2])
+            *(self._m.post(f"auth/r/info/funding/{key}")[2])
         )
 
     def transfer_between_wallets(
@@ -567,7 +571,7 @@ def transfer_between_wallets(
         }
 
         return _Notification[Transfer](serializers.Transfer).parse(
-            *self._post("auth/w/transfer", body=body)
+            *self._m.post("auth/w/transfer", body=body)
         )
 
     def submit_wallet_withdrawal(
@@ -581,14 +585,14 @@ def submit_wallet_withdrawal(
         }
 
         return _Notification[Withdrawal](serializers.Withdrawal).parse(
-            *self._post("auth/w/withdraw", body=body)
+            *self._m.post("auth/w/withdraw", body=body)
         )
 
     def get_deposit_address(
         self, wallet: str, method: str, op_renew: bool = False
     ) -> Notification[DepositAddress]:
         return _Notification[DepositAddress](serializers.DepositAddress).parse(
-            *self._post(
+            *self._m.post(
                 "auth/w/deposit/address",
                 body={"wallet": wallet, "method": method, "op_renew": op_renew},
             )
@@ -598,7 +602,7 @@ def generate_deposit_invoice(
         self, wallet: str, currency: str, amount: Union[str, float, Decimal]
     ) -> LightningNetworkInvoice:
         return serializers.LightningNetworkInvoice.parse(
-            *self._post(
+            *self._m.post(
                 "auth/w/deposit/invoice",
                 body={"wallet": wallet, "currency": currency, "amount": amount},
             )
@@ -619,7 +623,7 @@ def get_movements(
 
         return [
             serializers.Movement.parse(*sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 endpoint, body={"start": start, "end": end, "limit": limit}
             )
         ]
diff --git a/bfxapi/rest/endpoints/rest_merchant_endpoints.py b/bfxapi/rest/_interfaces/rest_merchant_endpoints.py
similarity index 77%
rename from bfxapi/rest/endpoints/rest_merchant_endpoints.py
rename to bfxapi/rest/_interfaces/rest_merchant_endpoints.py
index 3c4d9af..d5951e4 100644
--- a/bfxapi/rest/endpoints/rest_merchant_endpoints.py
+++ b/bfxapi/rest/_interfaces/rest_merchant_endpoints.py
@@ -1,7 +1,7 @@
 from decimal import Decimal
-from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
+from typing import Any, Dict, List, Literal, Optional, Union
 
-from bfxapi.rest.middleware import Middleware
+from bfxapi.rest._interface import Interface
 from bfxapi.types import (
     CurrencyConversion,
     InvoicePage,
@@ -11,29 +11,14 @@
     MerchantUnlinkedDeposit,
 )
 
-_CustomerInfo = TypedDict(
-    "_CustomerInfo",
-    {
-        "nationality": str,
-        "resid_country": str,
-        "resid_city": str,
-        "resid_zip_code": str,
-        "resid_street": str,
-        "resid_building_no": str,
-        "full_name": str,
-        "email": str,
-        "tos_accepted": bool,
-    },
-)
-
 
-class RestMerchantEndpoints(Middleware):
+class RestMerchantEndpoints(Interface):
     def submit_invoice(
         self,
         amount: Union[str, float, Decimal],
         currency: str,
         order_id: str,
-        customer_info: _CustomerInfo,
+        customer_info: Dict[str, Any],
         pay_currencies: List[str],
         *,
         duration: Optional[int] = None,
@@ -51,7 +36,7 @@ def submit_invoice(
             "redirectUrl": redirect_url,
         }
 
-        data = self._post("auth/w/ext/pay/invoice/create", body=body)
+        data = self._m.post("auth/w/ext/pay/invoice/create", body=body)
 
         return InvoiceSubmission.parse(data)
 
@@ -65,7 +50,7 @@ def get_invoices(
     ) -> List[InvoiceSubmission]:
         body = {"id": id, "start": start, "end": end, "limit": limit}
 
-        data = self._post("auth/r/ext/pay/invoices", body=body)
+        data = self._m.post("auth/r/ext/pay/invoices", body=body)
 
         return [InvoiceSubmission.parse(sub_data) for sub_data in data]
 
@@ -96,7 +81,7 @@ def get_invoices_paginated(
             "orderId": order_id,
         }
 
-        data = self._post("auth/r/ext/pay/invoices/paginated", body=body)
+        data = self._m.post("auth/r/ext/pay/invoices/paginated", body=body)
 
         return InvoicePage.parse(data)
 
@@ -105,7 +90,7 @@ def get_invoice_count_stats(
     ) -> List[InvoiceStats]:
         return [
             InvoiceStats(**sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 "auth/r/ext/pay/invoice/stats/count",
                 body={"status": status, "format": format},
             )
@@ -116,7 +101,7 @@ def get_invoice_earning_stats(
     ) -> List[InvoiceStats]:
         return [
             InvoiceStats(**sub_data)
-            for sub_data in self._post(
+            for sub_data in self._m.post(
                 "auth/r/ext/pay/invoice/stats/earning",
                 body={"currency": currency, "format": format},
             )
@@ -137,26 +122,26 @@ def complete_invoice(
             "ledgerId": ledger_id,
         }
 
-        data = self._post("auth/w/ext/pay/invoice/complete", body=body)
+        data = self._m.post("auth/w/ext/pay/invoice/complete", body=body)
 
         return InvoiceSubmission.parse(data)
 
     def expire_invoice(self, id: str) -> InvoiceSubmission:
         body = {"id": id}
 
-        data = self._post("auth/w/ext/pay/invoice/expire", body=body)
+        data = self._m.post("auth/w/ext/pay/invoice/expire", body=body)
 
         return InvoiceSubmission.parse(data)
 
     def get_currency_conversion_list(self) -> List[CurrencyConversion]:
         return [
             CurrencyConversion(**sub_data)
-            for sub_data in self._post("auth/r/ext/pay/settings/convert/list")
+            for sub_data in self._m.post("auth/r/ext/pay/settings/convert/list")
         ]
 
     def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
         return bool(
-            self._post(
+            self._m.post(
                 "auth/w/ext/pay/settings/convert/create",
                 body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
             )
@@ -164,7 +149,7 @@ def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
 
     def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
         return bool(
-            self._post(
+            self._m.post(
                 "auth/w/ext/pay/settings/convert/remove",
                 body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
             )
@@ -172,16 +157,16 @@ def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
 
     def set_merchant_settings(self, key: str, val: Any) -> bool:
         return bool(
-            self._post("auth/w/ext/pay/settings/set", body={"key": key, "val": val})
+            self._m.post("auth/w/ext/pay/settings/set", body={"key": key, "val": val})
         )
 
     def get_merchant_settings(self, key: str) -> Any:
-        return self._post("auth/r/ext/pay/settings/get", body={"key": key})
+        return self._m.post("auth/r/ext/pay/settings/get", body={"key": key})
 
     def list_merchant_settings(
         self, keys: Optional[List[str]] = None
     ) -> Dict[str, Any]:
-        return self._post("auth/r/ext/pay/settings/list", body={"keys": keys or []})
+        return self._m.post("auth/r/ext/pay/settings/list", body={"keys": keys or []})
 
     def get_deposits(
         self,
@@ -193,7 +178,7 @@ def get_deposits(
     ) -> List[MerchantDeposit]:
         body = {"from": start, "to": to, "ccy": ccy, "unlinked": unlinked}
 
-        data = self._post("auth/r/ext/pay/deposits", body=body)
+        data = self._m.post("auth/r/ext/pay/deposits", body=body)
 
         return [MerchantDeposit(**sub_data) for sub_data in data]
 
@@ -202,6 +187,6 @@ def get_unlinked_deposits(
     ) -> List[MerchantUnlinkedDeposit]:
         body = {"ccy": ccy, "start": start, "end": end}
 
-        data = self._post("/auth/r/ext/pay/deposits/unlinked", body=body)
+        data = self._m.post("/auth/r/ext/pay/deposits/unlinked", body=body)
 
         return [MerchantUnlinkedDeposit(**sub_data) for sub_data in data]
diff --git a/bfxapi/rest/endpoints/rest_public_endpoints.py b/bfxapi/rest/_interfaces/rest_public_endpoints.py
similarity index 84%
rename from bfxapi/rest/endpoints/rest_public_endpoints.py
rename to bfxapi/rest/_interfaces/rest_public_endpoints.py
index b8f5300..d672900 100644
--- a/bfxapi/rest/endpoints/rest_public_endpoints.py
+++ b/bfxapi/rest/_interfaces/rest_public_endpoints.py
@@ -1,7 +1,8 @@
 from decimal import Decimal
 from typing import Any, Dict, List, Literal, Optional, Union, cast
 
-from ...types import (
+from bfxapi.rest._interface import Interface
+from bfxapi.types import (
     Candle,
     DerivativesStatus,
     FundingCurrencyBook,
@@ -25,20 +26,19 @@
     TradingPairTrade,
     serializers,
 )
-from ..middleware import Middleware
 
 
-class RestPublicEndpoints(Middleware):
+class RestPublicEndpoints(Interface):
     def conf(self, config: str) -> Any:
-        return self._get(f"conf/{config}")[0]
+        return self._m.get(f"conf/{config}")[0]
 
     def get_platform_status(self) -> PlatformStatus:
-        return serializers.PlatformStatus.parse(*self._get("platform/status"))
+        return serializers.PlatformStatus.parse(*self._m.get("platform/status"))
 
     def get_tickers(
         self, symbols: List[str]
     ) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]:
-        data = self._get("tickers", params={"symbols": ",".join(symbols)})
+        data = self._m.get("tickers", params={"symbols": ",".join(symbols)})
 
         parsers = {
             "t": serializers.TradingPairTicker.parse,
@@ -83,10 +83,10 @@ def get_f_tickers(
         return cast(Dict[str, FundingCurrencyTicker], data)
 
     def get_t_ticker(self, symbol: str) -> TradingPairTicker:
-        return serializers.TradingPairTicker.parse(*self._get(f"ticker/{symbol}"))
+        return serializers.TradingPairTicker.parse(*self._m.get(f"ticker/{symbol}"))
 
     def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker:
-        return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}"))
+        return serializers.FundingCurrencyTicker.parse(*self._m.get(f"ticker/{symbol}"))
 
     def get_tickers_history(
         self,
@@ -98,7 +98,7 @@ def get_tickers_history(
     ) -> List[TickersHistory]:
         return [
             serializers.TickersHistory.parse(*sub_data)
-            for sub_data in self._get(
+            for sub_data in self._m.get(
                 "tickers/hist",
                 params={
                     "symbols": ",".join(symbols),
@@ -119,7 +119,7 @@ def get_t_trades(
         sort: Optional[int] = None,
     ) -> List[TradingPairTrade]:
         params = {"limit": limit, "start": start, "end": end, "sort": sort}
-        data = self._get(f"trades/{pair}/hist", params=params)
+        data = self._m.get(f"trades/{pair}/hist", params=params)
         return [serializers.TradingPairTrade.parse(*sub_data) for sub_data in data]
 
     def get_f_trades(
@@ -132,7 +132,7 @@ def get_f_trades(
         sort: Optional[int] = None,
     ) -> List[FundingCurrencyTrade]:
         params = {"limit": limit, "start": start, "end": end, "sort": sort}
-        data = self._get(f"trades/{currency}/hist", params=params)
+        data = self._m.get(f"trades/{currency}/hist", params=params)
         return [serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data]
 
     def get_t_book(
@@ -144,7 +144,7 @@ def get_t_book(
     ) -> List[TradingPairBook]:
         return [
             serializers.TradingPairBook.parse(*sub_data)
-            for sub_data in self._get(f"book/{pair}/{precision}", params={"len": len})
+            for sub_data in self._m.get(f"book/{pair}/{precision}", params={"len": len})
         ]
 
     def get_f_book(
@@ -156,7 +156,7 @@ def get_f_book(
     ) -> List[FundingCurrencyBook]:
         return [
             serializers.FundingCurrencyBook.parse(*sub_data)
-            for sub_data in self._get(
+            for sub_data in self._m.get(
                 f"book/{currency}/{precision}", params={"len": len}
             )
         ]
@@ -166,7 +166,7 @@ def get_t_raw_book(
     ) -> List[TradingPairRawBook]:
         return [
             serializers.TradingPairRawBook.parse(*sub_data)
-            for sub_data in self._get(f"book/{pair}/R0", params={"len": len})
+            for sub_data in self._m.get(f"book/{pair}/R0", params={"len": len})
         ]
 
     def get_f_raw_book(
@@ -174,7 +174,7 @@ def get_f_raw_book(
     ) -> List[FundingCurrencyRawBook]:
         return [
             serializers.FundingCurrencyRawBook.parse(*sub_data)
-            for sub_data in self._get(f"book/{currency}/R0", params={"len": len})
+            for sub_data in self._m.get(f"book/{currency}/R0", params={"len": len})
         ]
 
     def get_stats_hist(
@@ -187,7 +187,7 @@ def get_stats_hist(
         limit: Optional[int] = None,
     ) -> List[Statistic]:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"stats1/{resource}/hist", params=params)
+        data = self._m.get(f"stats1/{resource}/hist", params=params)
         return [serializers.Statistic.parse(*sub_data) for sub_data in data]
 
     def get_stats_last(
@@ -200,7 +200,7 @@ def get_stats_last(
         limit: Optional[int] = None,
     ) -> Statistic:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"stats1/{resource}/last", params=params)
+        data = self._m.get(f"stats1/{resource}/last", params=params)
         return serializers.Statistic.parse(*data)
 
     def get_candles_hist(
@@ -214,7 +214,7 @@ def get_candles_hist(
         limit: Optional[int] = None,
     ) -> List[Candle]:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params)
+        data = self._m.get(f"candles/trade:{tf}:{symbol}/hist", params=params)
         return [serializers.Candle.parse(*sub_data) for sub_data in data]
 
     def get_candles_last(
@@ -228,7 +228,7 @@ def get_candles_last(
         limit: Optional[int] = None,
     ) -> Candle:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"candles/trade:{tf}:{symbol}/last", params=params)
+        data = self._m.get(f"candles/trade:{tf}:{symbol}/last", params=params)
         return serializers.Candle.parse(*data)
 
     def get_derivatives_status(
@@ -239,7 +239,7 @@ def get_derivatives_status(
         else:
             params = {"keys": ",".join(keys)}
 
-        data = self._get("status/deriv", params=params)
+        data = self._m.get("status/deriv", params=params)
 
         return {
             key: serializers.DerivativesStatus.parse(*sub_data)
@@ -257,7 +257,7 @@ def get_derivatives_status_history(
         limit: Optional[int] = None,
     ) -> List[DerivativesStatus]:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"status/deriv/{key}/hist", params=params)
+        data = self._m.get(f"status/deriv/{key}/hist", params=params)
         return [serializers.DerivativesStatus.parse(*sub_data) for sub_data in data]
 
     def get_liquidations(
@@ -269,7 +269,7 @@ def get_liquidations(
         limit: Optional[int] = None,
     ) -> List[Liquidation]:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get("liquidations/hist", params=params)
+        data = self._m.get("liquidations/hist", params=params)
         return [serializers.Liquidation.parse(*sub_data[0]) for sub_data in data]
 
     def get_seed_candles(
@@ -283,7 +283,7 @@ def get_seed_candles(
         limit: Optional[int] = None,
     ) -> List[Candle]:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params)
+        data = self._m.get(f"candles/trade:{tf}:{symbol}/hist", params=params)
         return [serializers.Candle.parse(*sub_data) for sub_data in data]
 
     def get_leaderboards_hist(
@@ -296,7 +296,7 @@ def get_leaderboards_hist(
         limit: Optional[int] = None,
     ) -> List[Leaderboard]:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"rankings/{resource}/hist", params=params)
+        data = self._m.get(f"rankings/{resource}/hist", params=params)
         return [serializers.Leaderboard.parse(*sub_data) for sub_data in data]
 
     def get_leaderboards_last(
@@ -309,7 +309,7 @@ def get_leaderboards_last(
         limit: Optional[int] = None,
     ) -> Leaderboard:
         params = {"sort": sort, "start": start, "end": end, "limit": limit}
-        data = self._get(f"rankings/{resource}/last", params=params)
+        data = self._m.get(f"rankings/{resource}/last", params=params)
         return serializers.Leaderboard.parse(*data)
 
     def get_funding_stats(
@@ -321,18 +321,18 @@ def get_funding_stats(
         limit: Optional[int] = None,
     ) -> List[FundingStatistic]:
         params = {"start": start, "end": end, "limit": limit}
-        data = self._get(f"funding/stats/{symbol}/hist", params=params)
+        data = self._m.get(f"funding/stats/{symbol}/hist", params=params)
         return [serializers.FundingStatistic.parse(*sub_data) for sub_data in data]
 
     def get_pulse_profile_details(self, nickname: str) -> PulseProfile:
-        return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}"))
+        return serializers.PulseProfile.parse(*self._m.get(f"pulse/profile/{nickname}"))
 
     def get_pulse_message_history(
         self, *, end: Optional[str] = None, limit: Optional[int] = None
     ) -> List[PulseMessage]:
         messages = []
 
-        for sub_data in self._get("pulse/hist", params={"end": end, "limit": limit}):
+        for sub_data in self._m.get("pulse/hist", params={"end": end, "limit": limit}):
             sub_data[18] = sub_data[18][0]
             message = serializers.PulseMessage.parse(*sub_data)
             messages.append(message)
@@ -347,7 +347,7 @@ def get_trading_market_average_price(
         price_limit: Optional[Union[str, float, Decimal]] = None,
     ) -> TradingMarketAveragePrice:
         return serializers.TradingMarketAveragePrice.parse(
-            *self._post(
+            *self._m.post(
                 "calc/trade/avg",
                 body={"symbol": symbol, "amount": amount, "price_limit": price_limit},
             )
@@ -362,7 +362,7 @@ def get_funding_market_average_price(
         rate_limit: Optional[Union[str, float, Decimal]] = None,
     ) -> FundingMarketAveragePrice:
         return serializers.FundingMarketAveragePrice.parse(
-            *self._post(
+            *self._m.post(
                 "calc/trade/avg",
                 body={
                     "symbol": symbol,
@@ -375,5 +375,5 @@ def get_funding_market_average_price(
 
     def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate:
         return serializers.FxRate.parse(
-            *self._post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2})
+            *self._m.post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2})
         )
diff --git a/bfxapi/rest/exceptions.py b/bfxapi/rest/exceptions.py
index c19d92e..b55bc2a 100644
--- a/bfxapi/rest/exceptions.py
+++ b/bfxapi/rest/exceptions.py
@@ -1,10 +1,6 @@
 from bfxapi.exceptions import BfxBaseException
 
 
-class NotFoundError(BfxBaseException):
-    pass
-
-
 class RequestParametersError(BfxBaseException):
     pass
 
diff --git a/bfxapi/rest/middleware/__init__.py b/bfxapi/rest/middleware/__init__.py
deleted file mode 100644
index ae3488d..0000000
--- a/bfxapi/rest/middleware/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .middleware import Middleware
diff --git a/bfxapi/rest/middleware/middleware.py b/bfxapi/rest/middleware/middleware.py
deleted file mode 100644
index 48a4da4..0000000
--- a/bfxapi/rest/middleware/middleware.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import hashlib
-import hmac
-import json
-import time
-from enum import IntEnum
-from http import HTTPStatus
-from typing import TYPE_CHECKING, Any, Optional
-
-import requests
-
-from ..._utils.json_decoder import JSONDecoder
-from ..._utils.json_encoder import JSONEncoder
-from ...exceptions import InvalidCredentialError
-from ..exceptions import NotFoundError, RequestParametersError, UnknownGenericError
-
-if TYPE_CHECKING:
-    from requests.sessions import _Params
-
-
-class _Error(IntEnum):
-    ERR_UNK = 10000
-    ERR_GENERIC = 10001
-    ERR_PARAMS = 10020
-    ERR_AUTH_FAIL = 10100
-
-
-class Middleware:
-    TIMEOUT = 30
-
-    def __init__(
-        self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
-    ):
-        self.host, self.api_key, self.api_secret = host, api_key, api_secret
-
-    def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None):
-        assert isinstance(self.api_key, str) and isinstance(
-            self.api_secret, str
-        ), "API_KEY and API_SECRET must be both strings"
-
-        nonce = str(round(time.time() * 1_000_000))
-
-        if data is None:
-            path = f"/api/v2/{endpoint}{nonce}"
-        else:
-            path = f"/api/v2/{endpoint}{nonce}{data}"
-
-        signature = hmac.new(
-            self.api_secret.encode("utf8"), path.encode("utf8"), hashlib.sha384
-        ).hexdigest()
-
-        return {
-            "bfx-nonce": nonce,
-            "bfx-signature": signature,
-            "bfx-apikey": self.api_key,
-        }
-
-    def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
-        response = requests.get(
-            url=f"{self.host}/{endpoint}", params=params, timeout=Middleware.TIMEOUT
-        )
-
-        if response.status_code == HTTPStatus.NOT_FOUND:
-            raise NotFoundError(f"No resources found at endpoint <{endpoint}>.")
-
-        data = response.json(cls=JSONDecoder)
-
-        if len(data) and data[0] == "error":
-            if data[1] == _Error.ERR_PARAMS:
-                raise RequestParametersError(
-                    "The request was rejected with the "
-                    f"following parameter error: <{data[2]}>"
-                )
-
-            if (
-                data[1] is None
-                or data[1] == _Error.ERR_UNK
-                or data[1] == _Error.ERR_GENERIC
-            ):
-                raise UnknownGenericError(
-                    "The server replied to the request with "
-                    f"a generic error with message: <{data[2]}>."
-                )
-
-        return data
-
-    def _post(
-        self,
-        endpoint: str,
-        params: Optional["_Params"] = None,
-        body: Optional[Any] = None,
-        _ignore_authentication_headers: bool = False,
-    ) -> Any:
-        data = body and json.dumps(body, cls=JSONEncoder) or None
-
-        headers = {"Content-Type": "application/json"}
-
-        if self.api_key and self.api_secret and not _ignore_authentication_headers:
-            headers = {**headers, **self.__build_authentication_headers(endpoint, data)}
-
-        response = requests.post(
-            url=f"{self.host}/{endpoint}",
-            params=params,
-            data=data,
-            headers=headers,
-            timeout=Middleware.TIMEOUT,
-        )
-
-        if response.status_code == HTTPStatus.NOT_FOUND:
-            raise NotFoundError(f"No resources found at endpoint <{endpoint}>.")
-
-        data = response.json(cls=JSONDecoder)
-
-        if isinstance(data, list) and len(data) and data[0] == "error":
-            if data[1] == _Error.ERR_PARAMS:
-                raise RequestParametersError(
-                    "The request was rejected with the "
-                    f"following parameter error: <{data[2]}>"
-                )
-
-            if data[1] == _Error.ERR_AUTH_FAIL:
-                raise InvalidCredentialError(
-                    "Cannot authenticate with given API-KEY and API-SECRET."
-                )
-
-            if (
-                data[1] is None
-                or data[1] == _Error.ERR_UNK
-                or data[1] == _Error.ERR_GENERIC
-            ):
-                raise UnknownGenericError(
-                    "The server replied to the request with "
-                    f"a generic error with message: <{data[2]}>."
-                )
-
-        return data
diff --git a/bfxapi/types/labeler.py b/bfxapi/types/labeler.py
index 6c2e373..9c966ed 100644
--- a/bfxapi/types/labeler.py
+++ b/bfxapi/types/labeler.py
@@ -24,8 +24,8 @@ def __init__(self, **kwargs):
 
         if len(kwargs) != 0:
             raise TypeError(
-                f"{cls.__name__}.__init__() got an unexpected "
-                "keyword argument '{list(kwargs.keys())[0]}'"
+                f"{cls.__name__}.__init__() got an unexpected keyword argument "
+                f"'{list(kwargs.keys())[0]}'"
             )
 
     cls.__init__ = __init__
diff --git a/bfxapi/websocket/_client/bfx_websocket_inputs.py b/bfxapi/websocket/_client/bfx_websocket_inputs.py
index d618135..48a39d1 100644
--- a/bfxapi/websocket/_client/bfx_websocket_inputs.py
+++ b/bfxapi/websocket/_client/bfx_websocket_inputs.py
@@ -1,5 +1,5 @@
 from decimal import Decimal
-from typing import Any, Awaitable, Callable, List, Optional, Tuple, Union
+from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union
 
 _Handler = Callable[[str, Any], Awaitable[None]]
 
@@ -23,6 +23,7 @@ async def submit_order(
         cid: Optional[int] = None,
         flags: Optional[int] = None,
         tif: Optional[str] = None,
+        meta: Optional[Dict[str, Any]] = None,
     ) -> None:
         await self.__handle_websocket_input(
             "on",
@@ -39,6 +40,7 @@ async def submit_order(
                 "cid": cid,
                 "flags": flags,
                 "tif": tif,
+                "meta": meta,
             },
         )
 
diff --git a/setup.py b/setup.py
index 8628987..dcab900 100644
--- a/setup.py
+++ b/setup.py
@@ -39,8 +39,8 @@
         "bfxapi.websocket._handlers",
         "bfxapi.websocket._event_emitter",
         "bfxapi.rest",
-        "bfxapi.rest.endpoints",
-        "bfxapi.rest.middleware",
+        "bfxapi.rest._interface",
+        "bfxapi.rest._interfaces",
     ],
     install_requires=[
         "pyee~=9.0.4",