diff --git a/dapr/clients/exceptions.py b/dapr/clients/exceptions.py
index e6afeaa07..9ea29dea9 100644
--- a/dapr/clients/exceptions.py
+++ b/dapr/clients/exceptions.py
@@ -14,7 +14,10 @@
 """
 import base64
 import json
-from typing import Optional
+from typing import TYPE_CHECKING, Optional
+
+if TYPE_CHECKING:
+    from dapr.serializers import Serializer
 
 from google.protobuf.json_format import MessageToDict
 from grpc import RpcError  # type: ignore
@@ -56,6 +59,26 @@ def as_json_safe_dict(self):
 
         return error_dict
 
+    @property
+    def message(self) -> Optional[str]:
+        """Get the error message"""
+        return self._message
+
+    @property
+    def error_code(self) -> Optional[str]:
+        """Get the error code"""
+        return self._error_code
+
+    @property
+    def raw_response_bytes(self) -> Optional[bytes]:
+        """Get the raw response bytes"""
+        return self._raw_response_bytes
+
+    def __str__(self):
+        if self._error_code != ERROR_CODE_UNKNOWN:
+            return f"('{self._message}', '{self._error_code}')"
+        return self._message or 'Unknown Dapr Error.'
+
 
 class StatusDetails:
     def __init__(self):
@@ -74,6 +97,66 @@ def as_dict(self):
         return {attr: getattr(self, attr) for attr in self.__dict__}
 
 
+class DaprHttpError(DaprInternalError):
+    """DaprHttpError encapsulates all Dapr HTTP exceptions
+
+    Attributes:
+        _status_code: HTTP status code
+        _reason: HTTP reason phrase
+    """
+
+    def __init__(
+        self,
+        serializer: 'Serializer',
+        raw_response_bytes: Optional[bytes] = None,
+        status_code: Optional[int] = None,
+        reason: Optional[str] = None,
+    ):
+        self._status_code = status_code
+        self._reason = reason
+        error_code: str = ERROR_CODE_UNKNOWN
+        message: Optional[str] = None
+        error_info: dict = None
+
+        if (raw_response_bytes is None or len(raw_response_bytes) == 0) and status_code == 404:
+            error_code = ERROR_CODE_DOES_NOT_EXIST
+            raw_response_bytes = None
+        elif raw_response_bytes:
+            try:
+                error_info = serializer.deserialize(raw_response_bytes)
+            except Exception:
+                pass
+                # ignore any errors during deserialization
+
+            if error_info and isinstance(error_info, dict):
+                message = error_info.get('message')
+                error_code = error_info.get('errorCode') or ERROR_CODE_UNKNOWN
+
+        super().__init__(
+            message or f'HTTP status code: {status_code}', error_code, raw_response_bytes
+        )
+
+    @property
+    def status_code(self) -> Optional[int]:
+        return self._status_code
+
+    @property
+    def reason(self) -> Optional[str]:
+        return self._reason
+
+    def as_dict(self):
+        error_dict = super().as_dict()
+        error_dict['status_code'] = self._status_code
+        error_dict['reason'] = self._reason
+        return error_dict
+
+    def __str__(self):
+        if self._error_code != ERROR_CODE_UNKNOWN:
+            return f'{self._message} (Error Code: {self._error_code}, Status Code: {self._status_code})'
+        else:
+            return f'Unknown Dapr Error. HTTP status code: {self._status_code}.'
+
+
 class DaprGrpcError(RpcError):
     def __init__(self, err: RpcError):
         self._status_code = err.code()
diff --git a/dapr/clients/http/client.py b/dapr/clients/http/client.py
index 6f2a8e3d9..5944e2782 100644
--- a/dapr/clients/http/client.py
+++ b/dapr/clients/http/client.py
@@ -31,7 +31,7 @@
 
 from dapr.conf import settings
 from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE
-from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_DOES_NOT_EXIST, ERROR_CODE_UNKNOWN
+from dapr.clients.exceptions import DaprHttpError, DaprInternalError
 
 
 class DaprHttpClient:
@@ -102,26 +102,12 @@ async def send_bytes(
             raise (await self.convert_to_error(r))
 
     async def convert_to_error(self, response: aiohttp.ClientResponse) -> DaprInternalError:
-        error_info = None
-        try:
-            error_body = await response.read()
-            if (error_body is None or len(error_body) == 0) and response.status == 404:
-                return DaprInternalError('Not Found', ERROR_CODE_DOES_NOT_EXIST)
-            error_info = self._serializer.deserialize(error_body)
-        except Exception:
-            return DaprInternalError(
-                f'Unknown Dapr Error. HTTP status code: {response.status}',
-                raw_response_bytes=error_body,
-            )
-
-        if error_info and isinstance(error_info, dict):
-            message = error_info.get('message')
-            error_code = error_info.get('errorCode') or ERROR_CODE_UNKNOWN
-            return DaprInternalError(message, error_code, raw_response_bytes=error_body)
-
-        return DaprInternalError(
-            f'Unknown Dapr Error. HTTP status code: {response.status}',
+        error_body = await response.read()
+        return DaprHttpError(
+            self._serializer,
             raw_response_bytes=error_body,
+            status_code=response.status,
+            reason=response.reason,
         )
 
     def get_ssl_context(self):
diff --git a/examples/invoke-http/README.md b/examples/invoke-http/README.md
index 08e8738e6..466392d20 100644
--- a/examples/invoke-http/README.md
+++ b/examples/invoke-http/README.md
@@ -51,6 +51,9 @@ expected_stdout_lines:
   - '== APP == 200'
   - '== APP == error occurred'
   - '== APP == MY_CODE'
+  - '== APP == {"message": "error occurred", "errorCode": "MY_CODE"}'
+  - '== APP == 503'
+  - '== APP == Internal Server Error'
 background: true
 sleep: 5 
 -->
diff --git a/examples/invoke-http/invoke-caller.py b/examples/invoke-http/invoke-caller.py
index ebc5876b9..380001592 100644
--- a/examples/invoke-http/invoke-caller.py
+++ b/examples/invoke-http/invoke-caller.py
@@ -2,6 +2,7 @@
 import time
 
 from dapr.clients import DaprClient
+from dapr.clients.exceptions import DaprHttpError
 
 with DaprClient() as d:
     req_data = {'id': 1, 'message': 'hello world'}
@@ -29,6 +30,9 @@
             http_verb='POST',
             data=json.dumps(req_data),
         )
-    except Exception as e:
-        print(e._message, flush=True)
-        print(e._error_code, flush=True)
+    except DaprHttpError as e:
+        print(e.message, flush=True)
+        print(e.error_code, flush=True)
+        print(e.raw_response_bytes, flush=True)
+        print(str(e.status_code), flush=True)
+        print(e.reason, flush=True)
diff --git a/tests/clients/test_http_service_invocation_client.py b/tests/clients/test_http_service_invocation_client.py
index d45c530ba..c0b43a863 100644
--- a/tests/clients/test_http_service_invocation_client.py
+++ b/tests/clients/test_http_service_invocation_client.py
@@ -205,10 +205,10 @@ def test_invoke_method_protobuf_response_case_insensitive(self):
         self.assertEqual('resp', new_resp.key)
 
     def test_invoke_method_error_returned(self):
-        error_response = b'{"errorCode":"ERR_DIRECT_INVOKE","message":"Something bad happend"}'
+        error_response = b'{"errorCode":"ERR_DIRECT_INVOKE","message":"Something bad happened"}'
         self.server.set_response(error_response, 500)
 
-        expected_msg = "('Something bad happend', 'ERR_DIRECT_INVOKE')"
+        expected_msg = 'Something bad happened (Error Code: ERR_DIRECT_INVOKE, Status Code: 500)'
 
         with self.assertRaises(DaprInternalError) as ctx:
             self.client.invoke_method(
@@ -223,7 +223,7 @@ def test_invoke_method_non_dapr_error(self):
         error_response = b'UNPARSABLE_ERROR'
         self.server.set_response(error_response, 500)
 
-        expected_msg = 'Unknown Dapr Error. HTTP status code: 500'
+        expected_msg = 'Unknown Dapr Error. HTTP status code: 500.'
 
         with self.assertRaises(DaprInternalError) as ctx:
             self.client.invoke_method(
diff --git a/tests/clients/test_secure_http_service_invocation_client.py b/tests/clients/test_secure_http_service_invocation_client.py
index f23bc11c1..4d1bdda1f 100644
--- a/tests/clients/test_secure_http_service_invocation_client.py
+++ b/tests/clients/test_secure_http_service_invocation_client.py
@@ -23,6 +23,7 @@
 from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
 
 from dapr.clients import DaprClient, DaprGrpcClient
+from dapr.clients.exceptions import DaprInternalError
 from dapr.clients.health import DaprHealth
 from dapr.clients.http.client import DaprHttpClient
 from dapr.conf import settings
@@ -139,3 +140,68 @@ def test_timeout_exception_thrown_when_timeout_reached(self):
         self.server.set_server_delay(1.5)
         with self.assertRaises(TimeoutError):
             new_client.invoke_method(self.app_id, self.method_name, '')
+
+    def test_notfound_json_body_exception_thrown_with_status_code_and_reason(self):
+        self.server.set_response(b'{"error": "Not found"}', code=404)
+        with self.assertRaises(DaprInternalError) as context:
+            self.client.invoke_method(self.app_id, self.method_name, '')
+
+        error_dict = context.exception.as_dict()
+        self.assertEqual('HTTP status code: 404', error_dict.get('message'))
+        self.assertEqual('UNKNOWN', error_dict.get('errorCode'))
+        self.assertEqual(b'{"error": "Not found"}', error_dict.get('raw_response_bytes'))
+        self.assertEqual(404, error_dict.get('status_code'))
+        self.assertEqual('Not Found', error_dict.get('reason'))
+
+    def test_notfound_no_body_exception_thrown_with_status_code_and_reason(self):
+        self.server.set_response(b'', code=404)
+        with self.assertRaises(DaprInternalError) as context:
+            self.client.invoke_method(self.app_id, self.method_name, '')
+
+        error_dict = context.exception.as_dict()
+        self.assertEqual('HTTP status code: 404', error_dict.get('message'))
+        self.assertEqual('ERR_DOES_NOT_EXIST', error_dict.get('errorCode'))
+        self.assertEqual(None, error_dict.get('raw_response_bytes'))
+        self.assertEqual(404, error_dict.get('status_code'))
+        self.assertEqual('Not Found', error_dict.get('reason'))
+
+    def test_internal_error_no_body_exception_thrown_with_status_code_and_reason(self):
+        self.server.set_response(b'', code=500)
+        with self.assertRaises(DaprInternalError) as context:
+            self.client.invoke_method(self.app_id, self.method_name, '')
+
+        error_dict = context.exception.as_dict()
+        self.assertEqual('HTTP status code: 500', error_dict.get('message'))
+        self.assertEqual('UNKNOWN', error_dict.get('errorCode'))
+        self.assertEqual(b'', error_dict.get('raw_response_bytes'))
+        self.assertEqual(500, error_dict.get('status_code'))
+        self.assertEqual('Internal Server Error', error_dict.get('reason'))
+
+    def test_notfound_no_json_body_exception_thrown_with_status_code_and_reason(self):
+        self.server.set_response(b'Not found', code=404)
+        with self.assertRaises(DaprInternalError) as context:
+            self.client.invoke_method(self.app_id, self.method_name, '')
+
+        error_dict = context.exception.as_dict()
+        self.assertEqual('HTTP status code: 404', error_dict.get('message'))
+        self.assertEqual('UNKNOWN', error_dict.get('errorCode'))
+        self.assertEqual(b'Not found', error_dict.get('raw_response_bytes'))
+        self.assertEqual(404, error_dict.get('status_code'))
+        self.assertEqual('Not Found', error_dict.get('reason'))
+
+    def test_notfound_json_body_w_message_exception_thrown_with_status_code_and_reason(self):
+        self.server.set_response(
+            b'{"message": "My message", "errorCode": "MY_ERROR_CODE"}', code=404
+        )
+        with self.assertRaises(DaprInternalError) as context:
+            self.client.invoke_method(self.app_id, self.method_name, '')
+
+        error_dict = context.exception.as_dict()
+        self.assertEqual('My message', error_dict.get('message'))
+        self.assertEqual('MY_ERROR_CODE', error_dict.get('errorCode'))
+        self.assertEqual(
+            b'{"message": "My message", "errorCode": "MY_ERROR_CODE"}',
+            error_dict.get('raw_response_bytes'),
+        )
+        self.assertEqual(404, error_dict.get('status_code'))
+        self.assertEqual('Not Found', error_dict.get('reason'))