diff --git a/Cargo.lock b/Cargo.lock
index 75bf5260f4a88d197a5c85186334ff41ee8f4add..0400103b8eacdf18ffe6ad92f1d5606b2f3d3176 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -443,6 +443,7 @@ name = "mquery"
 version = "0.2.0"
 dependencies = [
  "dotenv",
+ "enum-as-inner",
  "prometheus-http-query",
  "reqwest",
  "tokio",
diff --git a/Cargo.toml b/Cargo.toml
index 3bdc678143e05580a8a0a3681c7ee49d7c76cb14..08fef249d36b8d162e6b15d945510fea91028e02 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+enum-as-inner = "0.6.0"
 prometheus-http-query = "0.8.0"
 reqwest = "0.11.22"
 url = "2.5.0"
diff --git a/src/lib.rs b/src/lib.rs
index baaef7194d09247dd69551013dcec6792cd2976d..44c6480f1a218d45aa484047f8c8f1f051e68363 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,10 +1,12 @@
 #![doc = include_str!("../README.md")]
 
 pub mod query;
+pub mod result;
 
-use prometheus_http_query::{response::PromqlResult, Client, Error};
+use prometheus_http_query::Client;
 use query::IntoQuery;
 use reqwest::header::HeaderValue;
+use result::{IntoQryResult, QryResult};
 use url::Url;
 
 /// The primary way to send queries and get deserialized responses from the metrics server
@@ -65,7 +67,7 @@ impl QueryManager {
     /// The trait is implemented for `&str`, `String`, `&String`, and __mquery's__
     /// internal types such as [`query::Metric`], [`query::Scalar`] and others that result
     /// from the query builder API.
-    pub async fn query<Q: IntoQuery>(&self, query: Q) -> Result<PromqlResult, Error> {
+    pub async fn query<Q: IntoQuery>(&self, query: Q) -> QryResult {
         let mut builder = self.client.query(query.into_query().as_ref());
         if let Some((name, val)) = self.auth.get_header() {
             builder = builder.header(name, val);
@@ -74,8 +76,8 @@ impl QueryManager {
             builder = builder.timeout(*timeout);
         }
         match self.method {
-            Method::Get => builder.get().await,
-            Method::Post => builder.post().await,
+            Method::Get => builder.get().await.into_qry_result(),
+            Method::Post => builder.post().await.into_qry_result(),
         }
     }
 
@@ -91,7 +93,7 @@ impl QueryManager {
         start: i64,
         end: i64,
         step: f64,
-    ) -> Result<PromqlResult, Error> {
+    ) -> QryResult {
         let mut builder = self
             .client
             .query_range(query.into_query().as_ref(), start, end, step);
@@ -102,8 +104,8 @@ impl QueryManager {
             builder = builder.timeout(*timeout);
         }
         match self.method {
-            Method::Get => builder.get().await,
-            Method::Post => builder.post().await,
+            Method::Get => builder.get().await.into_qry_result(),
+            Method::Post => builder.post().await.into_qry_result(),
         }
     }
 }
diff --git a/src/result.rs b/src/result.rs
new file mode 100644
index 0000000000000000000000000000000000000000..949d5ad80b4413991409e39c6e3157dbb064d6bd
--- /dev/null
+++ b/src/result.rs
@@ -0,0 +1,233 @@
+//! `Data` and the `Error` types which are essentially clones of the types
+//! with the same name from the `prometheus-http-query` crate with slight modifications
+//! to suit the needs of `mqeury`.
+//!
+//! Another reason why this crate maintains its own types which are the same (almost) as the ones
+//! provided by the other crate that is used internally, is to ensure that the public
+//! API remains stable even if the internal crate API changes.
+
+use enum_as_inner::EnumAsInner;
+use prometheus_http_query::{error, response};
+use std::fmt::{self, Display};
+
+pub type QryResult = std::result::Result<Data, Error>;
+
+/// Internal trait used for converting the `prometheus-http-query` crate's query result into the internal result type
+pub(crate) trait IntoQryResult {
+    fn into_qry_result(self) -> QryResult;
+}
+
+impl IntoQryResult for Result<response::PromqlResult, error::Error> {
+    fn into_qry_result(self) -> QryResult {
+        self.map(|r| r.into()).map_err(|e| e.into())
+    }
+}
+
+/// A wrapper for possible result types of expression queries ([`Client::query`](crate::Client::query) and [`Client::query_range`](crate::Client::query_range)).
+#[derive(Clone, Debug, EnumAsInner)]
+pub enum Data {
+    Vector(Vec<response::InstantVector>),
+    Matrix(Vec<response::RangeVector>),
+    Scalar(response::Sample),
+}
+
+impl From<response::PromqlResult> for Data {
+    fn from(value: response::PromqlResult) -> Self {
+        let (data, _) = value.into_inner();
+        match data {
+            response::Data::Vector(v) => Data::Vector(v),
+            response::Data::Matrix(m) => Data::Matrix(m),
+            response::Data::Scalar(s) => Data::Scalar(s),
+        }
+    }
+}
+
+impl Data {
+    /// This is a shortcut to check if the query returned any data at all regardless of the exact type.
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Data::Vector(v) => v.is_empty(),
+            Data::Matrix(v) => v.is_empty(),
+            Data::Scalar(_) => false,
+        }
+    }
+}
+
+/// A global error enum that contains all errors that are returned by this
+/// library. Some errors are wrappers for errors from underlying libraries.
+/// All errors (this enum as well as all contained structs) implement [`std::error::Error`].
+#[non_exhaustive]
+#[derive(Debug, Clone)]
+pub enum Error {
+    /// Wraps errors from the underlying [`reqwest::Client`] that cannot be mapped
+    /// to a more specific error type. Deserialization errors also fall into this
+    /// category.
+    Client(ClientError),
+    /// Occurs when Prometheus responds with e.g. HTTP 4xx (e.g. due to a syntax error in a PromQL query).<br>
+    /// Details on the error as reported by Prometheus are included in [`PrometheusError`].
+    Prometheus(PrometheusError),
+    /// Occurs when the [`Client::series`](crate::Client::series) method is called with an empty set of
+    /// series [`Selector`](crate::selector::Selector)s. According to the Prometheus API description at least one
+    /// [`Selector`](crate::selector::Selector) must be provided.
+    EmptySeriesSelector,
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::Client(e) => e.fmt(f),
+            Self::Prometheus(e) => e.fmt(f),
+            Self::EmptySeriesSelector => f.write_str("at least one series selector must be provided in order to query the series endpoint"),
+        }
+    }
+}
+
+impl std::error::Error for Error {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        match self {
+            Self::Client(e) => e.source(),
+            Self::Prometheus(p) => p.source(),
+            Self::EmptySeriesSelector => None,
+        }
+    }
+}
+
+impl From<prometheus_http_query::Error> for Error {
+    fn from(value: prometheus_http_query::Error) -> Self {
+        match value {
+            prometheus_http_query::Error::Client(c) => Self::Client(c.into()),
+            prometheus_http_query::Error::Prometheus(p) => Self::Prometheus(p.into()),
+            prometheus_http_query::Error::EmptySeriesSelector => Self::EmptySeriesSelector,
+            _ => unreachable!(),
+        }
+    }
+}
+
+/// This error is thrown when the JSON response's `status` field contains `error`.<br>
+/// The error-related information from the JSON body is included in this error.
+#[derive(Debug, Clone, PartialEq)]
+pub struct PrometheusError {
+    pub(crate) error_type: PrometheusErrorType,
+    pub(crate) message: String,
+}
+
+impl std::error::Error for PrometheusError {}
+
+impl fmt::Display for PrometheusError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}: {}", self.error_type, self.message)
+    }
+}
+
+impl From<error::PrometheusError> for PrometheusError {
+    fn from(value: error::PrometheusError) -> Self {
+        let error_type = PrometheusErrorType::from(value.error_type());
+        let message = value.message().to_owned();
+        PrometheusError {
+            error_type,
+            message,
+        }
+    }
+}
+
+impl PrometheusError {
+    /// Returns the parsed version of the error type that was given by the Prometheus API.
+    pub fn error_type(&self) -> PrometheusErrorType {
+        self.error_type
+    }
+
+    /// Returns the error message that was given by the Prometheus API.
+    pub fn message(&self) -> &str {
+        &self.message
+    }
+
+    pub fn is_timeout(&self) -> bool {
+        self.error_type == PrometheusErrorType::Timeout
+    }
+
+    pub fn is_canceled(&self) -> bool {
+        self.error_type == PrometheusErrorType::Canceled
+    }
+
+    pub fn is_execution(&self) -> bool {
+        self.error_type == PrometheusErrorType::Execution
+    }
+
+    pub fn is_bad_data(&self) -> bool {
+        self.error_type == PrometheusErrorType::BadData
+    }
+
+    pub fn is_internal(&self) -> bool {
+        self.error_type == PrometheusErrorType::Internal
+    }
+
+    pub fn is_unavailable(&self) -> bool {
+        self.error_type == PrometheusErrorType::Unavailable
+    }
+
+    pub fn is_not_found(&self) -> bool {
+        self.error_type == PrometheusErrorType::NotFound
+    }
+}
+
+/// The parsed error type as returned by the Prometheus API.
+#[non_exhaustive]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum PrometheusErrorType {
+    Timeout,
+    Canceled,
+    Execution,
+    BadData,
+    Internal,
+    Unavailable,
+    NotFound,
+}
+
+impl From<error::PrometheusErrorType> for PrometheusErrorType {
+    fn from(value: error::PrometheusErrorType) -> Self {
+        match value {
+            error::PrometheusErrorType::Timeout => PrometheusErrorType::Timeout,
+            error::PrometheusErrorType::Canceled => PrometheusErrorType::Canceled,
+            error::PrometheusErrorType::Execution => PrometheusErrorType::Execution,
+            error::PrometheusErrorType::BadData => PrometheusErrorType::BadData,
+            error::PrometheusErrorType::Internal => PrometheusErrorType::Internal,
+            error::PrometheusErrorType::Unavailable => PrometheusErrorType::Unavailable,
+            error::PrometheusErrorType::NotFound => PrometheusErrorType::NotFound,
+            _ => unreachable!(),
+        }
+    }
+}
+
+impl fmt::Display for PrometheusErrorType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::Timeout => f.write_str("timeout"),
+            Self::Canceled => f.write_str("canceled"),
+            Self::Execution => f.write_str("execution"),
+            Self::BadData => f.write_str("bad_data"),
+            Self::Internal => f.write_str("internal"),
+            Self::Unavailable => f.write_str("unavailable"),
+            Self::NotFound => f.write_str("not_found"),
+        }
+    }
+}
+
+/// Is thrown when the [`Client`](crate::Client) or the underlying
+/// [`reqwest::Error`] fail to build or execute a request.
+#[derive(Debug, Clone)]
+pub struct ClientError(String);
+
+impl From<prometheus_http_query::error::ClientError> for ClientError {
+    fn from(value: prometheus_http_query::error::ClientError) -> Self {
+        let message = value.to_string();
+        ClientError(message)
+    }
+}
+
+impl fmt::Display for ClientError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&self.0)
+    }
+}
+
+impl std::error::Error for ClientError {}
diff --git a/tests/utils.rs b/tests/utils.rs
index dbaa3a86db7e4f85e5c5b53bf574c6e2e51ef0e0..e686901e0bdb3f6f0d62e35b4217f3b5dd01760c 100644
--- a/tests/utils.rs
+++ b/tests/utils.rs
@@ -1,9 +1,8 @@
-use mquery::{query::IntoQuery, QueryManager};
-use prometheus_http_query::{response::PromqlResult, Error};
+use mquery::{query::IntoQuery, result::QryResult, QueryManager};
 
 // local victoria metrics server
 pub static URL: &str = "http://localhost:8428";
 
-pub async fn send_query(query: impl IntoQuery) -> Result<PromqlResult, Error> {
+pub async fn send_query(query: impl IntoQuery) -> QryResult {
     QueryManager::new(URL.parse().unwrap()).query(query).await
 }