diff --git a/warden_server/warden_server.py b/warden_server/warden_server.py
index e52e06b2e25635103b1d2a49cf2c0a52acfdf4df..6c1b86b899c8adf29e911dd106dd8dbdb3b82b1e 100755
--- a/warden_server/warden_server.py
+++ b/warden_server/warden_server.py
@@ -51,94 +51,134 @@ class Encoder(json.JSONEncoder):
     def default(self, o):
         if isinstance(o, Error):
             return o.to_dict()
+        if isinstance(o, ErrorMessage):
+            out = o.other_args.copy()
+            out.pop("exc", None)
+            out["error"] = o.error
+            out["message"] = o.message
+            if o.events:
+                out["events"] = list(o.events)
+            return out
         return str(o)
 
+
+class ErrorMessage(Exception):
+
+    def __init__(self, error, message, events=None, unique_id=None, **kwargs):
+        super(Exception, self).__setattr__("error", error)
+        super(Exception, self).__setattr__("message", message)
+        super(Exception, self).__setattr__("unique_id", unique_id)
+        self.events = set() if events is None else set(events)
+        self.other_args = kwargs
+
+    def __repr__(self):
+        return "%s(error=%d, message=%s)" % (
+            type(self).__name__, self.error, repr(self.message)
+        )
+
+    def __str__(self):
+        if sys.version_info[0] < 3:
+            return self.str_err().encode('ascii', 'backslashereplace')
+        return self.str_err()
+
+    def str_err(self):
+        exc = self.other_args.get("exc", None)
+        if exc in (None, (None, None, None)):
+            exc_cause = ""
+        else:
+            exc_cause = " (cause was %s: %s)" % (exc[0].__name__, str(exc[1]))
+        return "Error(%s) %s%s" % (self.error, self.message, exc_cause)
+
+    def str_info(self):
+        arg_copy = self.other_args.copy()
+        arg_copy.pop("req_id", None)
+        arg_copy.pop("method", None)
+        arg_copy.pop("exc", None)
+        if arg_copy:
+            return "Detail: %s" % json.dumps(arg_copy, cls=Encoder)
+        return ""
+
+    def str_debug(self):
+        exc = self.other_args.get("exc", None)
+        if exc in (None, (None, None, None)):
+            return ""
+        exc_tb = exc[2]
+        if not exc_tb:
+            return ""
+
+        return "Traceback:\n" + "".join(format_tb(exc_tb))
+
+    def __getattr__(self, name):
+        if name in self.other_args:
+            return self.other_args[name]
+        raise AttributeError
+
+    def __setattr__(self, name, value):
+        if name in ("events", "exc", "other_args"):
+            super(Exception, self).__setattr__(name, value)
+            return
+        if name in ("error", "message", "unique_id"):
+            raise AttributeError("Cannot change the attribute %s" % name)
+        self.other_args[name] = value
+
+
 class Error(Exception):
 
     def __init__(self, method=None, req_id=None, errors=None, **kwargs):
         self.method = method
         self.req_id = req_id
-        self.errors = [kwargs] if kwargs else []
+        if "message" in kwargs:
+            kwargs.setdefault("error", 500)
+            self.errors = [ErrorMessage(**kwargs)]
+        else:
+            self.errors = []
         if errors:
             self.errors.extend(errors)
 
     def append(self, _events=None, **kwargs):
-        self.errors.append(kwargs)
+        kwargs.setdefault("message", "No further information")
+        kwargs.setdefault("error", 500)
+        self.errors.append(ErrorMessage(**kwargs))
 
     def get_http_err_msg(self):
         try:
-            err = self.errors[0]["error"]
-            msg = self.errors[0]["message"].replace("\n", " ")
-        except (IndexError, KeyError):
+            err = self.errors[0].error
+            msg = self.errors[0].message
+        except (IndexError, AttributeError):
             err = 500
             msg = "There's NO self-destruction button! Ah, you've just found it..."
-        for e in self.errors:
-            next_err = e.get("error", 500)
-            if err != next_err:
-                # errors not same, round to basic err code (400, 500)
-                # and use the highest one
-                err = max(err//100, next_err//100)*100
-            next_msg = e.get("message", "Unknown error").replace("\n", " ")
-            if msg != next_msg:
-                msg = "Multiple errors"
+            return err, msg
+
+        if not all(msg == e.message for e in self.errors):
+            # messages not the same, get Multiple errors
+            msg = "Multiple errors"
+        if not all(err == e.error for e in self.errors):
+            # errors not same, round to basic err code (400, 500)
+            # and use the highest one
+            err = max(e.error for e in self.errors) // 100 * 100
 
         msg = "".join((c if '\x20' <= c != '\x7f' else r'\x{:02x}'.format(ord(c))) for c in msg) # escape control characters
 
         return err, msg
 
     def __str__(self):
-        return "\n".join(self.str_err(e) for e in self.errors)
+        return "\n".join(str(e) for e in self.errors)
 
     def log(self, logger, prio=logging.ERROR):
         for e in self.errors:
-            logger.log(prio, self.str_err(e))
-            info = self.str_info(e)
+            logger.log(prio, e.str_err())
+            info = e.str_info()
             if info:
                 logger.info(info)
-            debug = self.str_debug(e)
+            debug = e.str_debug()
             if debug:
                 logger.debug(debug)
 
-    def str_err(self, e):
-        out = []
-        out.append("Error(%s) %s " % (e.get("error", 0), e.get("message", "Unknown error")))
-        if "exc" in e and e["exc"]:
-            out.append("(cause was %s: %s)" % (e["exc"][0].__name__, str(e["exc"][1])))
-        return "".join(out)
-
-    def str_info(self, e):
-        ecopy = dict(e)    # shallow copy
-        ecopy.pop("req_id", None)
-        ecopy.pop("method", None)
-        ecopy.pop("error", None)
-        ecopy.pop("message", None)
-        ecopy.pop("exc", None)
-        if ecopy:
-            out = "Detail: %s" % (json.dumps(ecopy, cls=Encoder))
-        else:
-            out = ""
-        return out
-
-    def str_debug(self, e):
-        out = []
-        if not e.get("exc"):
-            return ""
-        exc_tb = e["exc"][2]
-        if exc_tb:
-            out.append("Traceback:\n")
-            out.extend(format_tb(exc_tb))
-        return "".join(out)
-
     def to_dict(self):
-        errlist = []
-        for e in self.errors:
-            ecopy = dict(e)
-            ecopy.pop("exc", None)
-            errlist.append(ecopy)
         d = {
             "method": self.method,
             "req_id": self.req_id,
-            "errors": errlist
+            "errors": self.errors
         }
         return d
 
@@ -466,14 +506,15 @@ class JSONSchemaValidator(NoValidator):
 
         res = []
         for error in sorted(self.validator.iter_errors(event), key=sortkey):
-            res.append({
-                "error": 460,
-                "message": "Validation error: key \"%s\", value \"%s\"" % (
-                    "/".join(str(v) for v in error.path),
-                    error.instance
-                ),
-                "expected": error.schema.get('description', 'no additional info')
-            })
+            res.append(
+                ErrorMessage(
+                    460, "Validation error: key \"%s\", value \"%s\"" % (
+                        "/".join(map(str, error.path)),
+                        error.instance
+                    ),
+                    expected=error.schema.get('description', 'no additional info')
+                )
+            )
 
         return res
 
@@ -806,7 +847,7 @@ class DataBase(ObjectBase):
         except Exception as e:
             exception = self.req.error(message="DB error", error=500, exc=sys.exc_info(), env=self.req.env)
             exception.log(self.log)
-            return [{"error": 500, "message": "DB error %s" % type(e).__name__}]
+            return [ErrorMessage(500, "DB error %s" % type(e).__name__)]
 
     @abc.abstractmethod
     def _build_insert_last_received_id(self, client, id):
@@ -1688,28 +1729,23 @@ class WardenHandler(ObjectBase):
 
         return res
 
-    def check_node(self, event, name):
+    def check_node(self, event, event_indx, name):
         try:
             ev_id = event['Node'][0]['Name'].lower()
         except (KeyError, TypeError, IndexError):
             # Event does not bear valid Node attribute
-            return [{"error": 422, "message": "Event does not bear valid Node attribute"}]
+            return [
+                ErrorMessage(422, "Event does not bear valid Node attribute", {event_indx})
+            ]
         if ev_id != name:
-            return [{"error": 422, "message": "Node does not correspond with saving client"}]
+            return [
+                ErrorMessage(422, "Node does not correspond with saving client", {event_indx})
+            ]
         return []
 
-    def add_event_nums(self, ilist, events, errlist):
-        for err in errlist:
-            err.setdefault("events", []).extend(ilist)
-            ev_ids = err.setdefault("events_id", [])
-            for i in ilist:
-                event = events[i]
-                try:
-                    id = event["ID"]
-                except (KeyError, TypeError, ValueError):
-                    id = None
-                ev_ids.append(id)
-        return errlist
+    def add_errors(self, errs_to_add):
+        for err in errs_to_add:
+            self.errs.setdefault((err.error, err.message, err.unique_id), err).events.update(err.events)
 
     @expose(write=True)
     @json_wrapper
@@ -1717,40 +1753,55 @@ class WardenHandler(ObjectBase):
         if not isinstance(events, list):
             raise self.req.error(message="List of events expected.", error=400)
 
-        errs = []
+        self.errs = {}
         if len(events) > self.send_events_limit:
-            errs.extend(self.add_event_nums(range(self.send_events_limit, len(events)), events, [
-                {"error": 507, "message": "Too much events in one batch.", "send_events_limit": self.send_events_limit}]))
+            self.add_errors(
+                [
+                    ErrorMessage(
+                        507, "Too many events in one batch.",
+                        set(range(self.send_events_limit, len(events))),
+                        send_events_limit=self.send_events_limit
+                    )
+                ]
+            )
 
-        saved = 0
         events_tosend = []
         events_raw = []
         events_nums = []
         for i, event in enumerate(events[0:self.send_events_limit]):
             v_errs = self.validator.check(event)
             if v_errs:
-                errs.extend(self.add_event_nums([i], events, v_errs))
+                self.add_errors(v_errs)
                 continue
 
-            node_errs = self.check_node(event, self.req.client.name)
+            node_errs = self.check_node(event, i, self.req.client.name)
             if node_errs:
-                errs.extend(self.add_event_nums([i], events, node_errs))
+                self.add_errors(node_errs)
                 continue
 
             if self.req.client.test and 'Test' not in event.get('Category', []):
-                errs.extend(
-                    self.add_event_nums([i], events, [{
-                        "error": 422,
-                        "message": "You're allowed to send only messages, containing \"Test\" among categories.",
-                        "categories": event.get('Category', [])}]))
+                self.add_errors(
+                    [
+                        ErrorMessage(
+                            422, "You're allowed to send only messages containing \"Test\" among categories.", {i},
+                            # Ensure that 1the error message is contained for every combination of categories
+                            unique_id=tuple(event.get('Category', [])),
+                            categories=event.get('Category', [])
+                        )
+                    ]
+                )
                 continue
 
             raw_event = json.dumps(event)
             if len(raw_event) >= self.db.event_size_limit:
-                errs.extend(
-                    self.add_event_nums([i], events, [
-                        {"error": 413, "message": "Event too long (>%i B)" % self.db.event_size_limit}
-                    ]))
+                self.add_errors(
+                    [
+                        ErrorMessage(
+                            413, "Event too long (>%i B)" % self.db.event_size_limit, {i},
+                            event_size_limit = self.db.event_size_limit
+                        )
+                    ]
+                )
                 continue
 
             events_tosend.append(event)
@@ -1758,15 +1809,13 @@ class WardenHandler(ObjectBase):
             events_nums.append(i)
 
         db_errs = self.db.store_events(self.req.client, events_tosend, events_raw)
-        if db_errs:
-            errs.extend(self.add_event_nums(events_nums, events_tosend, db_errs))
-            saved = 0
-        else:
-            saved = len(events_tosend)
+        self.add_errors(db_errs)
+
+        saved = 0 if db_errs else len(events_tosend)
 
         self.log.info("Saved %i events" % saved)
-        if errs:
-            raise self.req.error(errors=errs)
+        if self.errs:
+            raise self.req.error(errors=self.errs.values())
 
         return {"saved": saved}