diff --git a/rpc/errors.go b/rpc/errors.go
index dbfde8b1965272597f6e789cb319ff581cf57d08..4c06a745fbd8b06bf04aeff53da180b8790c9745 100644
--- a/rpc/errors.go
+++ b/rpc/errors.go
@@ -18,6 +18,35 @@ package rpc
 
 import "fmt"
 
+// HTTPError is returned by client operations when the HTTP status code of the
+// response is not a 2xx status.
+type HTTPError struct {
+	StatusCode int
+	Status     string
+	Body       []byte
+}
+
+func (err HTTPError) Error() string {
+	if len(err.Body) == 0 {
+		return err.Status
+	}
+	return fmt.Sprintf("%v: %s", err.Status, err.Body)
+}
+
+// Error wraps RPC errors, which contain an error code in addition to the message.
+type Error interface {
+	Error() string  // returns the message
+	ErrorCode() int // returns the code
+}
+
+// A DataError contains some data in addition to the error message.
+type DataError interface {
+	Error() string          // returns the message
+	ErrorData() interface{} // returns the error data
+}
+
+// Error types defined below are the built-in JSON-RPC errors.
+
 var (
 	_ Error = new(methodNotFoundError)
 	_ Error = new(subscriptionNotFoundError)
diff --git a/rpc/http.go b/rpc/http.go
index 87a96e49eaf6897a8c5efd930dc10e9842081e01..32f4e7d90a25906a443f8d6c92ed4e496ced83e5 100644
--- a/rpc/http.go
+++ b/rpc/http.go
@@ -134,19 +134,11 @@ func DialHTTP(endpoint string) (*Client, error) {
 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
 	hc := c.writeConn.(*httpConn)
 	respBody, err := hc.doRequest(ctx, msg)
-	if respBody != nil {
-		defer respBody.Close()
-	}
-
 	if err != nil {
-		if respBody != nil {
-			buf := new(bytes.Buffer)
-			if _, err2 := buf.ReadFrom(respBody); err2 == nil {
-				return fmt.Errorf("%v: %v", err, buf.String())
-			}
-		}
 		return err
 	}
+	defer respBody.Close()
+
 	var respmsg jsonrpcMessage
 	if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
 		return err
@@ -194,7 +186,17 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
 		return nil, err
 	}
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
-		return resp.Body, errors.New(resp.Status)
+		var buf bytes.Buffer
+		var body []byte
+		if _, err := buf.ReadFrom(resp.Body); err == nil {
+			body = buf.Bytes()
+		}
+
+		return nil, HTTPError{
+			Status:     resp.Status,
+			StatusCode: resp.StatusCode,
+			Body:       body,
+		}
 	}
 	return resp.Body, nil
 }
diff --git a/rpc/http_test.go b/rpc/http_test.go
index b75af67c522e6502de41a9a81da12f6f8d8a1da7..97f8d44c39bca2884d22f6b871ae8f939bd605e4 100644
--- a/rpc/http_test.go
+++ b/rpc/http_test.go
@@ -123,3 +123,42 @@ func TestHTTPRespBodyUnlimited(t *testing.T) {
 		t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
 	}
 }
+
+// Tests that an HTTP error results in an HTTPError instance
+// being returned with the expected attributes.
+func TestHTTPErrorResponse(t *testing.T) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		http.Error(w, "error has occurred!", http.StatusTeapot)
+	}))
+	defer ts.Close()
+
+	c, err := DialHTTP(ts.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var r string
+	err = c.Call(&r, "test_method")
+	if err == nil {
+		t.Fatal("error was expected")
+	}
+
+	httpErr, ok := err.(HTTPError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+
+	if httpErr.StatusCode != http.StatusTeapot {
+		t.Error("unexpected status code", httpErr.StatusCode)
+	}
+	if httpErr.Status != "418 I'm a teapot" {
+		t.Error("unexpected status text", httpErr.Status)
+	}
+	if body := string(httpErr.Body); body != "error has occurred!\n" {
+		t.Error("unexpected body", body)
+	}
+
+	if errMsg := httpErr.Error(); errMsg != "418 I'm a teapot: error has occurred!\n" {
+		t.Error("unexpected error message", errMsg)
+	}
+}
diff --git a/rpc/types.go b/rpc/types.go
index bab1b3957b902434001ef25f2b9a1341a1708eb6..d1b878c7858ff0bff76458e2d9b9f993d91a512e 100644
--- a/rpc/types.go
+++ b/rpc/types.go
@@ -35,18 +35,6 @@ type API struct {
 	Public    bool        // indication if the methods must be considered safe for public use
 }
 
-// Error wraps RPC errors, which contain an error code in addition to the message.
-type Error interface {
-	Error() string  // returns the message
-	ErrorCode() int // returns the code
-}
-
-// A DataError contains some data in addition to the error message.
-type DataError interface {
-	Error() string          // returns the message
-	ErrorData() interface{} // returns the error data
-}
-
 // ServerCodec implements reading, parsing and writing RPC messages for the server side of
 // a RPC session. Implementations must be go-routine safe since the codec can be called in
 // multiple go-routines concurrently.