FEATURE client-side funtions
diff --git a/src/io.c b/src/io.c
index b6f2ac9..4d76a11 100644
--- a/src/io.c
+++ b/src/io.c
@@ -573,7 +573,7 @@
     int count, i;
     const char *attrs;
     struct lyd_node *content;
-    struct nc_rpc_server *rpc;
+    struct nc_server_rpc *rpc;
     const char **capabilities;
     uint32_t *sid = NULL;
     char *buf = NULL;
@@ -618,7 +618,7 @@
         break;
 
     case NC_MSG_REPLY:
-        rpc = va_arg(ap, struct nc_rpc_server *);
+        rpc = va_arg(ap, struct nc_server_rpc *);
         switch (session->ti_type) {
         case NC_TI_FD:
             write(session->ti.fd.out, "<rpc-reply", 10);
@@ -743,7 +743,7 @@
     int count;
     const char *attrs;
     struct lyd_node *content;
-    struct nc_rpc_server *rpc;
+    struct nc_server_rpc *rpc;
     char *buf = NULL;
     struct wclb_arg arg;
 
@@ -764,7 +764,7 @@
         session->msgid++;
         break;
     case NC_MSG_REPLY:
-        rpc = va_arg(ap, struct nc_rpc_server *);
+        rpc = va_arg(ap, struct nc_server_rpc *);
         write_clb((void *)&arg, "<rpc-reply", 10);
         lyxml_dump_clb(write_clb, (void *)&arg, rpc->root, LYXML_DUMP_ATTRS);
         write_clb((void *)&arg, ">", 1);
diff --git a/src/messages.c b/src/messages.c
index d222128..81287b6 100644
--- a/src/messages.c
+++ b/src/messages.c
@@ -33,54 +33,12 @@
 const char *rpcedit_testopt2str[] = {NULL, "test-then-set", "set", "test-only"};
 const char *rpcedit_erropt2str[] = {NULL, "stop-on-error", "continue-on-error", "rollback-on-error"};
 
-API struct nc_filter *
-nc_filter_new(NC_FILTER type, char *data, int constdata)
-{
-    struct nc_filter *filter;
-
-    if (!data) {
-        data = "";
-        constdata = 1;
-    }
-
-    filter = malloc(sizeof *filter);
-    if (!filter) {
-        ERRMEM;
-        return NULL;
-    }
-
-    filter->type = type;
-    filter->refs = 1;
-    if (constdata) {
-        filter->data = strdup(data);
-    } else {
-        filter->data = data;
-    }
-
-    return filter;
-}
-
-API void
-nc_filter_free(struct nc_filter *filter)
-{
-    if (!filter) {
-        return;
-    }
-
-    filter->refs--;
-
-    if (!filter->refs) {
-        free(filter->data);
-        free(filter);
-    }
-}
-
 API struct nc_rpc *
-nc_rpc_generic(struct lyd_node *data)
+nc_rpc_generic(const struct lyd_node *data, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_generic *rpc;
 
-    if (data->prev != data) {
+    if (data->next || (data->prev != data)) {
         ERR("Generic RPC must have a single root node.");
         return NULL;
     }
@@ -92,13 +50,18 @@
     }
 
     rpc->type = NC_RPC_GENERIC;
-    rpc->data = data;
+    if (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE) {
+        rpc->data = lyd_dup(data, 1);
+    } else {
+        rpc->data = (struct lyd_node *)data;
+    }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_generic_xml(const char *xml_str)
+nc_rpc_generic_xml(const char *xml_str, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_generic_xml *rpc;
 
@@ -109,17 +72,27 @@
     }
 
     rpc->type = NC_RPC_GENERIC_XML;
-    rpc->xml_str = strdup(xml_str);
+    if (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE) {
+        rpc->xml_str = strdup(xml_str);
+    } else {
+        rpc->xml_str = (char *)xml_str;
+    }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_getconfig(NC_DATASTORE source, struct nc_filter *filter)
+nc_rpc_getconfig(NC_DATASTORE source, const char *filter, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_getconfig *rpc;
 
-    rpc = calloc(1, sizeof *rpc);
+    if ((filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
+        ERR("Filter must either be an XML subtree or an XPath expression.");
+        return NULL;
+    }
+
+    rpc = malloc(sizeof *rpc);
     if (!rpc) {
         ERRMEM;
         return NULL;
@@ -127,17 +100,19 @@
 
     rpc->type = NC_RPC_GETCONFIG;
     rpc->source = source;
-    if (filter) {
-        filter->refs++;
-        rpc->filter = filter;
+    if (filter && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->filter = strdup(filter);
+    } else {
+        rpc->filter = (char *)filter;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
 nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TESTOPT test_opt,
-            NC_RPC_EDIT_ERROPT error_opt, const char *edit_content)
+            NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_edit *rpc;
 
@@ -146,7 +121,7 @@
         return NULL;
     }
 
-    rpc = calloc(1, sizeof *rpc);
+    rpc = malloc(sizeof *rpc);
     if (!rpc) {
         ERRMEM;
         return NULL;
@@ -157,13 +132,19 @@
     rpc->default_op = default_op;
     rpc->test_opt = test_opt;
     rpc->error_opt = error_opt;
-    rpc->edit_cont = strdup(edit_content);
+    if (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE) {
+        rpc->edit_cont = strdup(edit_content);
+    } else {
+        rpc->edit_cont = (char *)edit_content;
+    }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const char *url_or_config_src)
+nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const char *url_or_config_src,
+            NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_copy *rpc;
 
@@ -172,7 +153,7 @@
         return NULL;
     }
 
-    rpc = calloc(1, sizeof *rpc);
+    rpc = malloc(sizeof *rpc);
     if (!rpc) {
         ERRMEM;
         return NULL;
@@ -180,27 +161,28 @@
 
     rpc->type = NC_RPC_COPY;
     rpc->target = target;
-    if (url_trg) {
+    if (url_trg && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->url_trg = strdup(url_trg);
     } else {
-        rpc->url_trg = NULL;
+        rpc->url_trg = (char *)url_trg;
     }
     rpc->source = source;
-    if (url_or_config_src) {
+    if (url_or_config_src && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->url_config_src = strdup(url_or_config_src);
     } else {
-        rpc->url_config_src = NULL;
+        rpc->url_config_src = (char *)url_or_config_src;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_delete(NC_DATASTORE target, char *url)
+nc_rpc_delete(NC_DATASTORE target, const char *url, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_delete *rpc;
 
-    rpc = calloc(1, sizeof *rpc);
+    rpc = malloc(sizeof *rpc);
     if (!rpc) {
         ERRMEM;
         return NULL;
@@ -208,11 +190,12 @@
 
     rpc->type = NC_RPC_DELETE;
     rpc->target = target;
-    if (url) {
+    if (url && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->url = strdup(url);
     } else {
-        rpc->url = NULL;
+        rpc->url = (char *)url;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
@@ -252,21 +235,28 @@
 }
 
 API struct nc_rpc *
-nc_rpc_get(struct nc_filter *filter)
+nc_rpc_get(const char *filter, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_get *rpc;
 
-    rpc = calloc(1, sizeof *rpc);
+    if ((filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
+        ERR("Filter must either be an XML subtree or an XPath expression.");
+        return NULL;
+    }
+
+    rpc = malloc(sizeof *rpc);
     if (!rpc) {
         ERRMEM;
         return NULL;
     }
 
     rpc->type = NC_RPC_GET;
-    if (filter) {
-        filter->refs++;
-        rpc->filter = filter;
+    if (filter && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->filter = strdup(filter);
+    } else {
+        rpc->filter = (char *)filter;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
@@ -289,7 +279,8 @@
 }
 
 API struct nc_rpc *
-nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id)
+nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id,
+              NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_commit *rpc;
 
@@ -302,16 +293,17 @@
     rpc->type = NC_RPC_COMMIT;
     rpc->confirmed = confirmed;
     rpc->confirm_timeout = confirm_timeout;
-    if (persist) {
+    if (persist && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->persist = strdup(persist);
     } else {
-        rpc->persist = NULL;
+        rpc->persist = (char *)persist;
     }
-    if (persist_id) {
+    if (persist_id && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->persist_id = strdup(persist_id);
     } else {
-        rpc->persist_id = NULL;
+        rpc->persist_id = (char *)persist_id;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
@@ -333,7 +325,7 @@
 }
 
 API struct nc_rpc *
-nc_rpc_cancel(const char *persist_id)
+nc_rpc_cancel(const char *persist_id, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_cancel *rpc;
 
@@ -344,17 +336,18 @@
     }
 
     rpc->type = NC_RPC_CANCEL;
-    if (persist_id) {
+    if (persist_id && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->persist_id = strdup(persist_id);
     } else {
-        rpc->persist_id = NULL;
+        rpc->persist_id = (char *)persist_id;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_validate(NC_DATASTORE source, const char *url_or_config)
+nc_rpc_validate(NC_DATASTORE source, const char *url_or_config, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_validate *rpc;
 
@@ -371,17 +364,18 @@
 
     rpc->type = NC_RPC_VALIDATE;
     rpc->source = source;
-    if (url_or_config) {
+    if (url_or_config && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->url_config_src = strdup(url_or_config);
     } else {
-        rpc->url_config_src = NULL;
+        rpc->url_config_src = (char *)url_or_config;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_getschema(const char *identifier, const char *version, const char *format)
+nc_rpc_getschema(const char *identifier, const char *version, const char *format, NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_getschema *rpc;
 
@@ -392,26 +386,37 @@
     }
 
     rpc->type = NC_RPC_GETSCHEMA;
-    rpc->identifier = strdup(identifier);
-    if (version) {
+    if (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE) {
+        rpc->identifier = strdup(identifier);
+    } else {
+        rpc->identifier = (char *)identifier;
+    }
+    if (version && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->version = strdup(version);
     } else {
-        rpc->version = NULL;
+        rpc->version = (char *)version;
     }
-    if (format) {
+    if (format && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->format = strdup(format);
     } else {
-        rpc->format = NULL;
+        rpc->format = (char *)format;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
 API struct nc_rpc *
-nc_rpc_subscribe(const char *stream_name, struct nc_filter *filter, const char *start_time, const char *stop_time)
+nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_time, const char *stop_time,
+                 NC_RPC_PARAMTYPE paramtype)
 {
     struct nc_rpc_subscribe *rpc;
 
+    if ((filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
+        ERR("Filter must either be an XML subtree or an XPath expression.");
+        return NULL;
+    }
+
     rpc = malloc(sizeof *rpc);
     if (!rpc) {
         ERRMEM;
@@ -419,35 +424,41 @@
     }
 
     rpc->type = NC_RPC_SUBSCRIBE;
-    if (stream_name) {
+    if (stream_name && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->stream = strdup(stream_name);
     } else {
-        rpc->stream = NULL;
+        rpc->stream = (char *)stream_name;
     }
-    if (filter) {
-        filter->refs++;
-        rpc->filter = filter;
+    if (filter && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->filter = strdup(filter);
     } else {
-        filter = NULL;
+        rpc->filter = (char *)filter;
     }
-    if (start_time) {
+    if (start_time && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->start = strdup(start_time);
     } else {
-        rpc->start = NULL;
+        rpc->start = (char *)start_time;
     }
-    if (stop_time) {
+    if (stop_time && (paramtype == NC_RPC_PARAMTYPE_DUP_AND_FREE)) {
         rpc->stop = strdup(stop_time);
     } else {
-        rpc->stop = NULL;
+        rpc->stop = (char *)stop_time;
     }
+    rpc->free = (paramtype == NC_RPC_PARAMTYPE_CONST ? 0 : 1);
 
     return (struct nc_rpc *)rpc;
 }
 
+static void
+nc_server_rpc_free(struct nc_server_rpc *rpc)
+{
+    lyxml_free(rpc->tree->schema->module->ctx, rpc->root);
+    lyd_free(rpc->tree);
+}
+
 API void
 nc_rpc_free(struct nc_rpc *rpc)
 {
-    struct nc_rpc_server *rpc_server;
     struct nc_rpc_generic *rpc_generic;
     struct nc_rpc_generic_xml *rpc_generic_xml;
     struct nc_rpc_getconfig *rpc_getconfig;
@@ -465,65 +476,84 @@
         return;
     }
 
-    switch(rpc->type) {
-    case NC_RPC_SERVER:
-        rpc_server = (struct nc_rpc_server *)rpc;
-        lyxml_free_elem(rpc_server->tree->schema->module->ctx, rpc_server->root);
-        lyd_free(rpc_server->tree);
-        break;
+    switch (rpc->type) {
     case NC_RPC_GENERIC:
         rpc_generic = (struct nc_rpc_generic *)rpc;
-        lyd_free(rpc_generic->data);
+        if (rpc_generic->free) {
+            lyd_free(rpc_generic->data);
+        }
         break;
     case NC_RPC_GENERIC_XML:
         rpc_generic_xml = (struct nc_rpc_generic_xml *)rpc;
-        free(rpc_generic_xml->xml_str);
+        if (rpc_generic_xml->free) {
+            free(rpc_generic_xml->xml_str);
+        }
         break;
     case NC_RPC_GETCONFIG:
         rpc_getconfig = (struct nc_rpc_getconfig *)rpc;
-        nc_filter_free(rpc_getconfig->filter);
+        if (rpc_getconfig->free) {
+            free(rpc_getconfig->filter);
+        }
         break;
     case NC_RPC_EDIT:
         rpc_edit = (struct nc_rpc_edit *)rpc;
-        free(rpc_edit->edit_cont);
+        if (rpc_edit->free) {
+            free(rpc_edit->edit_cont);
+        }
         break;
     case NC_RPC_COPY:
         rpc_copy = (struct nc_rpc_copy *)rpc;
-        free(rpc_copy->url_config_src);
+        if (rpc_copy->free) {
+            free(rpc_copy->url_config_src);
+        }
         break;
     case NC_RPC_DELETE:
         rpc_delete = (struct nc_rpc_delete *)rpc;
-        free(rpc_delete->url);
+        if (rpc_delete->free) {
+            free(rpc_delete->url);
+        }
         break;
     case NC_RPC_GET:
         rpc_get = (struct nc_rpc_get *)rpc;
-        nc_filter_free(rpc_get->filter);
+        if (rpc_get->free) {
+            free(rpc_get->filter);
+        }
         break;
     case NC_RPC_COMMIT:
         rpc_commit = (struct nc_rpc_commit *)rpc;
-        free(rpc_commit->persist);
-        free(rpc_commit->persist_id);
+        if (rpc_commit->free) {
+            free(rpc_commit->persist);
+            free(rpc_commit->persist_id);
+        }
         break;
     case NC_RPC_CANCEL:
         rpc_cancel = (struct nc_rpc_cancel *)rpc;
-        free(rpc_cancel->persist_id);
+        if (rpc_cancel->free) {
+            free(rpc_cancel->persist_id);
+        }
         break;
     case NC_RPC_VALIDATE:
         rpc_validate = (struct nc_rpc_validate *)rpc;
-        free(rpc_validate->url_config_src);
+        if (rpc_validate->free) {
+            free(rpc_validate->url_config_src);
+        }
         break;
     case NC_RPC_GETSCHEMA:
         rpc_getschema = (struct nc_rpc_getschema *)rpc;
-        free(rpc_getschema->identifier);
-        free(rpc_getschema->version);
-        free(rpc_getschema->format);
+        if (rpc_getschema->free) {
+            free(rpc_getschema->identifier);
+            free(rpc_getschema->version);
+            free(rpc_getschema->format);
+        }
         break;
     case NC_RPC_SUBSCRIBE:
         rpc_subscribe = (struct nc_rpc_subscribe *)rpc;
-        free(rpc_subscribe->stream);
-        nc_filter_free(rpc_subscribe->filter);
-        free(rpc_subscribe->start);
-        free(rpc_subscribe->stop);
+        if (rpc_subscribe->free) {
+            free(rpc_subscribe->stream);
+            free(rpc_subscribe->filter);
+            free(rpc_subscribe->start);
+            free(rpc_subscribe->stop);
+        }
         break;
     case NC_RPC_KILL:
     case NC_RPC_DISCARD:
@@ -540,9 +570,9 @@
 nc_reply_free(struct nc_reply *reply)
 {
     struct nc_reply_error *error;
-    struct nc_reply_ok *ok;
     struct nc_reply_data *data;
     struct lyd_node *node;
+    int i, j;
 
     if (!reply) {
         return;
@@ -551,20 +581,42 @@
     switch(reply->type) {
     case NC_REPLY_DATA:
         data = (struct nc_reply_data *)reply;
-        lyxml_free_elem(data->data->schema->module->ctx, data->root);
         for (node = data->data; data->data; node = data->data) {
             data->data = node->next;
             lyd_free(node);
         }
         break;
     case NC_REPLY_OK:
-        ok = (struct nc_reply_ok *)reply;
-        lyxml_free_elem(ok->ctx, ok->root);
+        /* nothing to free */
         break;
     case NC_REPLY_ERROR:
         error = (struct nc_reply_error *)reply;
-        (void)error;
-        /* TODO */
+        for (i = 0; i < error->err_count; ++i) {
+            lydict_remove(error->ctx, error->err[i].type);
+            lydict_remove(error->ctx, error->err[i].tag);
+            lydict_remove(error->ctx, error->err[i].severity);
+            lydict_remove(error->ctx, error->err[i].apptag);
+            lydict_remove(error->ctx, error->err[i].path);
+            lydict_remove(error->ctx, error->err[i].message);
+            lydict_remove(error->ctx, error->err[i].message_lang);
+            lydict_remove(error->ctx, error->err[i].sid);
+            for (j = 0; j < error->err[i].attr_count; ++j) {
+                lydict_remove(error->ctx, error->err[i].attr[j]);
+            }
+            free(error->err[i].attr);
+            for (j = 0; j < error->err[i].elem_count; ++j) {
+                lydict_remove(error->ctx, error->err[i].elem[j]);
+            }
+            free(error->err[i].elem);
+            for (j = 0; j < error->err[i].ns_count; ++j) {
+                lydict_remove(error->ctx, error->err[i].ns[j]);
+            }
+            free(error->err[i].ns);
+            for (j = 0; j < error->err[i].other_count; ++j) {
+                lyxml_free(error->ctx, error->err[i].other[j]);
+            }
+            free(error->err[i].other);
+        }
         break;
     case NC_REPLY_NOTIF:
         nc_notif_free((struct nc_notif *)reply);
@@ -580,7 +632,6 @@
         return;
     }
 
-    lyxml_free_elem(notif->tree->schema->module->ctx, notif->root);
     lyd_free(notif->tree);
     free(notif);
 }
diff --git a/src/messages.h b/src/messages.h
index 08a10a3..3917f96 100644
--- a/src/messages.h
+++ b/src/messages.h
@@ -23,6 +23,15 @@
 #ifndef NC_MESSAGES_H_
 #define NC_MESSAGES_H_
 
+#include <stdint.h>
+
+typedef enum {
+    NC_REPLY_ERROR,
+    NC_REPLY_OK,
+    NC_REPLY_DATA,
+    NC_REPLY_NOTIF
+} NC_REPLY_TYPE;
+
 typedef enum {
     NC_RPC_EDIT_DFLTOP_UNKNOWN = 0,
     NC_RPC_EDIT_DFLTOP_MERGE,
@@ -45,50 +54,94 @@
 } NC_RPC_EDIT_ERROPT;
 
 typedef enum {
-    NC_FILTER_SUBTREE,
-    NC_FILTER_XPATH
-} NC_FILTER;
-
-struct nc_filter;
+    NC_RPC_PARAMTYPE_CONST,
+    NC_RPC_PARAMTYPE_FREE,
+    NC_RPC_PARAMTYPE_DUP_AND_FREE
+} NC_RPC_PARAMTYPE;
 
 /**
- * @brief Create NETCONF filter for \<get\> or \<get-config\> RPCs.
- *
- * The returned object can be used repeatedly. Caller is supposed to free it using nc_filter_free().
- *
- * @param[in] type Filter type of the \p data. #NC_FILTER_SUBTREE and #NC_FILTER_XPATH are supported.
- *                 Note that #NC_FILTER_XPATH is accepted only on sessions supporting the :xpath capability.
- * @param[in] data Content of the filter. Serialized XML data in case of #NC_FILTER_SUBTREE and XPath query
- *                 in case of #NC_FILTER_XPATH (use YANG schema names as namespace prefixes).
- * @param[in] constdata Flag for handling \p data. If set, the \p data is handled as const char* and the string
- *                 is duplicated for internal use. If not set, \p data is not duplicated but caller is supposed
- *                 to forget about the provided string.
- * @return Created filter structure to be used in nc_rpc_getconfig() and nc_rpc_get(). NULL in case of failure.
+ * @brief NETCONF error structure representation
  */
-struct nc_filter *nc_filter_new(NC_FILTER type, char *data, int constdata);
+struct nc_err {
+    /**
+     * @brief \<error-type\>, error layer where the error occurred.
+     */
+    const char *type;
+    /**
+     * @brief \<error-tag\>.
+     */
+    const char *tag;
+    /**
+     * @brief \<error-severity\>.
+     */
+    const char *severity;
+    /**
+     * @brief \<error-app-tag\>, the data-model-specific or implementation-specific error condition, if one exists.
+     */
+    const char *apptag;
+    /**
+     * @brief \<error-path\>, XPATH expression identifying the element with the error.
+     */
+    const char *path;
+    /**
+     * @brief \<error-message\>, Human-readable description of the error.
+     */
+    const char *message;
+    const char *message_lang;
 
-/**
- * @brief Free the NETCONF filter object.
- *
- * @param[in] filter Object to free.
- */
-void nc_filter_free(struct nc_filter *filter);
+    /* <error-info> */
+
+    /**
+     * @brief \<session-id\>, session ID of the session holding the requested lock.
+     */
+    const char *sid;
+    /**
+     * @brief \<bad-attr\>, the name of the data-model-specific XML attribute that caused the error.
+     */
+    const char **attr;
+    int attr_count;
+    /**
+     * @brief \<bad-element\>, the name of the data-model-specific XML element that caused the error.
+     */
+    const char **elem;
+    int elem_count;
+    /**
+     * @brief \<bad-namespace\>, the name of the unexpected XML namespace that caused the error.
+     */
+    const char **ns;
+    int ns_count;
+    /**
+     * @brief Remaining non-standard elements.
+     */
+    struct lyxml_elem **other;
+    int other_count;
+};
+
+struct nc_reply {
+    NC_REPLY_TYPE type;
+};
+
+struct nc_reply_error {
+    NC_REPLY_TYPE type;      /**< NC_REPLY_ERROR */
+    struct ly_ctx *ctx;
+    struct nc_err *err;      /**< errors, any of the values inside can be NULL */
+    int err_count;
+};
+
+struct nc_reply_data {
+    NC_REPLY_TYPE type;      /**< NC_REPLY_DATA */
+    struct lyd_node *data;   /**< libyang data tree */
+};
+
+struct nc_notif {
+    NC_REPLY_TYPE type;      /**< NC_REPLY_NOTIF */
+    struct lyd_node *tree;   /**< libyang data tree of the message */
+};
 
 /**
  * @brief NETCONF RPC object
  */
 struct nc_rpc;
-struct nc_rpc_server;
-
-/**
- * @brief NETCONF RPC reply object
- */
-struct nc_reply;
-
-/**
- * @brief NETCONF Notification object
- */
-struct nc_notif;
 
 /**
  * @brief Create a generic NETCONF RPC
@@ -96,10 +149,11 @@
  * Note that created object can be sent via any NETCONF session that shares the context
  * of the \p data.
  *
- * @param[in] data NETCONF RPC data. Their ownership is passed to the RPC (they are freed with it).
+ * @param[in] data NETCONF RPC data as a data tree.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_generic(struct lyd_node *data);
+struct nc_rpc *nc_rpc_generic(const struct lyd_node *data, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create a generic NETCONF RPC from an XML string
@@ -110,10 +164,11 @@
  * check. Created object can be sent via any NETCONF session which supports all the
  * needed NETCONF capabilities for the RPC.
  *
- * @param[in] data NETCONF RPC data. Their ownership is passed to the RPC (they are freed with it).
+ * @param[in] xml_str NETCONF RPC data as an XML string.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_generic_xml(const char *xml_str);
+struct nc_rpc *nc_rpc_generic_xml(const char *xml_str, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<get-config\>
@@ -125,10 +180,11 @@
  * needed NETCONF capabilities for the RPC.
  *
  * @param[in] source Source datastore being queried.
- * @param[in] filter Optional filter data, see nc_filter_new().
+ * @param[in] filter Optional filter data, an XML subtree or XPath expression.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_getconfig(NC_DATASTORE source, struct nc_filter *filter);
+struct nc_rpc *nc_rpc_getconfig(NC_DATASTORE source, const char *filter, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<edit-config\>
@@ -144,10 +200,11 @@
  * @param[in] test_opt Optional test option.
  * @param[in] error_opt Optional error option.
  * @param[in] edit_content Config or URL where the config to perform is to be found.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TESTOPT test_opt,
-                           NC_RPC_EDIT_ERROPT error_opt, const char *edit_content);
+                           NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<copy-config\>
@@ -162,9 +219,11 @@
  * @param[in] url_trg Used instead \p target if the target is an URL.
  * @param[in] source Source datastore.
  * @param[in] url_or_config_src Used instead \p source if the source is an URL or a config.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const char *url_or_config_src);
+struct nc_rpc *nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source,
+                           const char *url_or_config_src, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<delete-config\>
@@ -177,9 +236,10 @@
  *
  * @param[in] target Target datastore to delete.
  * @param[in] url Used instead \p target if the target is an URL.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_delete(NC_DATASTORE target, char *url);
+struct nc_rpc *nc_rpc_delete(NC_DATASTORE target, const char *url, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<lock\>
@@ -218,10 +278,11 @@
  * check. Created object can be sent via any NETCONF session which supports all the
  * needed NETCONF capabilities for the RPC.
  *
- * @param[in] filter Optional filter data, see nc_filter_new().
+ * @param[in] filter Optional filter data, an XML subtree or XPath expression.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_get(struct nc_filter *filter);
+struct nc_rpc *nc_rpc_get(const char *filter, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<kill-session\>
@@ -250,9 +311,11 @@
  * @param[in] confirm_timeout Optional confirm timeout.
  * @param[in] persist Optional identification string of a new persistent confirmed commit.
  * @param[in] persist_id Optional identification string of a persistent confirmed commit to be commited.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id);
+struct nc_rpc *nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id,
+                             NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<discard-changes\>
@@ -277,9 +340,10 @@
  * needed NETCONF capabilities for the RPC.
  *
  * @param[in] persist_id Optional identification string of a persistent confirmed commit.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_cancel(const char *persist_id);
+struct nc_rpc *nc_rpc_cancel(const char *persist_id, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<validate\>
@@ -292,9 +356,10 @@
  *
  * @param[in] source Source datastore being validated.
  * @param[in] url_or_config Usedn instead \p source if the source is an URL or a config.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_validate(NC_DATASTORE source, const char *url_or_config);
+struct nc_rpc *nc_rpc_validate(NC_DATASTORE source, const char *url_or_config, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<get-schema\>
@@ -308,9 +373,10 @@
  * @param[in] identifier Requested model identifier.
  * @param[in] version Optional model version, either YANG version (1.0/1.1) or revision date.
  * @param[in] format Optional format of the model (default is YANG).
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_getschema(const char *identifier, const char *version, const char *format);
+struct nc_rpc *nc_rpc_getschema(const char *identifier, const char *version, const char *format, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<create-subscription\>
@@ -322,13 +388,14 @@
  * needed NETCONF capabilities for the RPC.
  *
  * @param[in] stream_name Optional name of a NETCONF stream to subscribe to.
- * @param[in] filter Optional filter data, see nc_filter_new().
+ * @param[in] filter Optional filter data, an XML subtree or XPath expression.
  * @param[in] start_time Optional YANG datetime identifying the start of the subscription.
  * @param[in] stop_time Optional YANG datetime identifying the end of the subscription.
+ * @param[in] paramtype How to further manage data parameters.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
-struct nc_rpc *nc_rpc_subscribe(const char *stream_name, struct nc_filter *filter, const char *start_time,
-								const char *stop_time);
+struct nc_rpc *nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_time,
+								const char *stop_time, NC_RPC_PARAMTYPE paramtype);
 
 /**
  * @brief Free the NETCONF RPC object.
diff --git a/src/messages_p.h b/src/messages_p.h
index 599f080..8264466 100644
--- a/src/messages_p.h
+++ b/src/messages_p.h
@@ -32,51 +32,59 @@
 extern const char *rpcedit_erropt2str[];
 
 typedef enum {
-    NC_RPC_SERVER,      /**< server-side RPC object, see #nc_rpc_server. All other values define client-side RPC object. */
-    NC_RPC_GENERIC,     /**< user-defined generic RPC with content as data, see #nc_rpc_generic. */
-    NC_RPC_GENERIC_XML, /**< user-defined generic RPC with content as an XML string, see #nc_rpc_generic_xml. */
+    NC_RPC_GENERIC,     /**< user-defined generic RPC with content as data. */
+    NC_RPC_GENERIC_XML, /**< user-defined generic RPC with content as an XML string. */
 
     /* ietf-netconf */
-    NC_RPC_GETCONFIG,   /**< \<get-config\> RPC, see #nc_rpc_getconfig. */
-    NC_RPC_EDIT,        /**< \<edit-config\> RPC, see #nc_rpc_edit. */
-    NC_RPC_COPY,        /**< \<copy-config\> RPC, see #nc_rpc_copy. */
-    NC_RPC_DELETE,      /**< \<delete-config\> RPC, see #nc_rpc_delete. */
-    NC_RPC_LOCK,        /**< \<lock\> RPC, see #nc_rpc_lock. */
-    NC_RPC_UNLOCK,      /**< \<unlock\> RPC, see #nc_rpc_lock. */
-    NC_RPC_GET,         /**< \<get\> RPC, see #nc_rpc_get. */
+    NC_RPC_GETCONFIG,   /**< \<get-config\> RPC. */
+    NC_RPC_EDIT,        /**< \<edit-config\> RPC. */
+    NC_RPC_COPY,        /**< \<copy-config\> RPC. */
+    NC_RPC_DELETE,      /**< \<delete-config\> RPC. */
+    NC_RPC_LOCK,        /**< \<lock\> RPC. */
+    NC_RPC_UNLOCK,      /**< \<unlock\> RPC. */
+    NC_RPC_GET,         /**< \<get\> RPC. */
     /* NC_RPC_CLOSE is not defined since sending \<close-session\> is done by nc_session_free() */
-    NC_RPC_KILL,        /**< \<kill-session\> RPC, see #nc_rpc_kill. */
-    NC_RPC_COMMIT,      /**< \<commit\> RPC, see #nc_rpc_commit. */
-    NC_RPC_DISCARD,     /**< \<discard-changes\> RPC, #nc_rpc with this type. */
-    NC_RPC_CANCEL,      /**< \<cancel-commit\> RPC, see #c_rpc_cancel. */
-    NC_RPC_VALIDATE,    /**< \<validate\> RPC, see #nc_rpc_validate. */
+    NC_RPC_KILL,        /**< \<kill-session\> RPC. */
+    NC_RPC_COMMIT,      /**< \<commit\> RPC. */
+    NC_RPC_DISCARD,     /**< \<discard-changes\> RPC. */
+    NC_RPC_CANCEL,      /**< \<cancel-commit\> RPC. */
+    NC_RPC_VALIDATE,    /**< \<validate\> RPC. */
 
     /* ietf-netconf-monitoring */
-    NC_RPC_GETSCHEMA,   /**< \<get-schema\> RPC, see #nc_rpc_getschema. */
+    NC_RPC_GETSCHEMA,   /**< \<get-schema\> RPC. */
 
     /* notifications */
-    NC_RPC_SUBSCRIBE    /**< \<create-subscription\> RPC, see #nc_rpc_subscribe. */
+    NC_RPC_SUBSCRIBE    /**< \<create-subscription\> RPC. */
 } NC_RPC_TYPE;
 
 typedef enum {
-    NC_REPLY_ERROR,
-    NC_REPLY_OK,
-    NC_REPLY_DATA,
-    NC_REPLY_NOTIF
-} NC_REPLY_TYPE;
-
-struct nc_filter {
-    NC_FILTER type;   /**< filter type */
-    int refs;         /**< number of references */
-    char *data;       /**< filter data according to type */
-};
+    NC_ERR_EMPTY,
+    NC_ERR_IN_USE,
+    NC_ERR_INVALID_VALUE,
+    NC_ERR_TOO_BIG,
+    NC_ERR_MISSING_ATTR,
+    NC_ERR_BAD_ATTR,
+    NC_ERR_UNKNOWN_ATTR,
+    NC_ERR_MISSING_ELEM,
+    NC_ERR_BAD_ELEM,
+    NC_ERR_UNKNOWN_ELEM,
+    NC_ERR_UNKNOWN_NS,
+    NC_ERR_ACCESS_DENIED,
+    NC_ERR_LOCK_DENIED,
+    NC_ERR_RES_DENIED,
+    NC_ERR_ROLLBACK_FAILED,
+    NC_ERR_DATA_EXISTS,
+    NC_ERR_DATA_MISSING,
+    NC_ERR_OP_NOT_SUPPORTED,
+    NC_ERR_OP_FAILED,
+    NC_ERR_MALFORMED_MSG
+} NC_ERR;
 
 struct nc_rpc {
     NC_RPC_TYPE type;
 };
 
-struct nc_rpc_server {
-    NC_RPC_TYPE type;        /**< NC_RPC_SERVER */
+struct nc_server_rpc {
     struct lyxml_elem *root; /**< RPC element of the received XML message */
     struct lyd_node *tree;   /**< libyang data tree of the message (NETCONF operation) */
 };
@@ -84,17 +92,20 @@
 struct nc_rpc_generic {
     NC_RPC_TYPE type;       /**< NC_RPC_GENERIC */
     struct lyd_node *data;  /**< RPC data */
+    char free;
 };
 
 struct nc_rpc_generic_xml {
     NC_RPC_TYPE type;       /**< NC_RPC_GENERIC_XML */
     char *xml_str;
+    char free;
 };
 
 struct nc_rpc_getconfig {
     NC_RPC_TYPE type;        /**< NC_RPC_GETCONFIG */
     NC_DATASTORE source;     /**< NETCONF datastore being queried */
-    struct nc_filter *filter;/**< data filter */
+    char *filter;            /**< either XML subtree (starts with '<') or an XPath (starts with '/' or an alpha) */
+    char free;
 };
 
 struct nc_rpc_edit {
@@ -104,6 +115,7 @@
     NC_RPC_EDIT_TESTOPT test_opt;
     NC_RPC_EDIT_ERROPT error_opt;
     char *edit_cont;         /**< either URL (starts with aplha) or config (starts with '<') */
+    char free;
 };
 
 struct nc_rpc_copy {
@@ -112,12 +124,14 @@
     char *url_trg;
     NC_DATASTORE source;
     char *url_config_src;    /**< either URL (starts with aplha) or config (starts with '<') */
+    char free;
 };
 
 struct nc_rpc_delete {
     NC_RPC_TYPE type;        /**< NC_RPC_DELETE */
     NC_DATASTORE target;
     char *url;
+    char free;
 };
 
 struct nc_rpc_lock {
@@ -127,7 +141,8 @@
 
 struct nc_rpc_get {
     NC_RPC_TYPE type;        /**< NC_RPC_GET */
-    struct nc_filter *filter;/**< data filter */
+    char *filter;            /**< either XML subtree (starts with '<') or an XPath (starts with '/' or an alpha) */
+    char free;
 };
 
 struct nc_rpc_kill {
@@ -141,17 +156,20 @@
     uint32_t confirm_timeout;
     char *persist;
     char *persist_id;
+    char free;
 };
 
 struct nc_rpc_cancel {
     NC_RPC_TYPE type;        /**< NC_RPC_CANCEL */
     char *persist_id;
+    char free;
 };
 
 struct nc_rpc_validate {
     NC_RPC_TYPE type;        /**< NC_RPC_VALIDATE */
     NC_DATASTORE source;
     char *url_config_src;    /**< either URL (starts with alpha) or config (starts with '<') */
+    char free;
 };
 
 struct nc_rpc_getschema {
@@ -159,43 +177,16 @@
     char *identifier;        /**< requested model identifier */
     char *version;           /**< either YANG version (1.0/1.1) or revision date */
     char *format;            /**< model format */
+    char free;
 };
 
 struct nc_rpc_subscribe {
     NC_RPC_TYPE type;        /**< NC_RPC_SUBSCRIBE */
     char *stream;            /**< stream name */
-    struct nc_filter *filter;
+    char *filter;            /**< either XML subtree (starts with '<') or an XPath (starts with '/' or an alpha) */
     char *start;
     char *stop;
-};
-
-struct nc_reply {
-    NC_REPLY_TYPE type;
-    struct lyxml_elem *root;
-};
-
-struct nc_reply_error {
-    NC_REPLY_TYPE type;      /**< NC_REPLY_ERROR */
-    struct lyxml_elem *root;
-    /* TODO */
-};
-
-struct nc_reply_ok {
-    NC_REPLY_TYPE type;      /**< NC_REPLY_OK */
-    struct lyxml_elem *root;
-    struct ly_ctx *ctx;
-};
-
-struct nc_reply_data {
-    NC_REPLY_TYPE type;      /**< NC_REPLY_DATA */
-    struct lyxml_elem *root; /**< only the top reply element */
-    struct lyd_node *data;   /**< libyang data tree */
-};
-
-struct nc_notif {
-    NC_REPLY_TYPE type;      /**< NC_REPLY_NOTIF */
-    struct lyxml_elem *root; /**< only the top notification element */
-    struct lyd_node *tree;   /**< libyang data tree of the message */
+    char free;
 };
 
 #endif /* NC_MESSAGES_P_H_ */
diff --git a/src/session.c b/src/session.c
index e7ca1da..1d0b3e6 100644
--- a/src/session.c
+++ b/src/session.c
@@ -41,7 +41,7 @@
 
 static NC_MSG_TYPE nc_send_hello_(struct nc_session *session);
 static NC_MSG_TYPE nc_recv_hello(struct nc_session *session);
-static NC_MSG_TYPE nc_send_rpc_(struct nc_session *session, struct lyd_node *op);
+static NC_MSG_TYPE nc_send_msg(struct nc_session *session, struct lyd_node *op);
 
 static char *schema_searchpath = NULL;
 
@@ -125,7 +125,7 @@
 static int
 ctx_load_model(struct nc_session *session, const char *cpblt)
 {
-    struct lys_module *module;
+    const struct lys_module *module;
     char *ptr, *ptr2;
     char *model_name, *revision = NULL, *features = NULL;
 
@@ -153,8 +153,11 @@
         revision = strndup(ptr, ptr2 - ptr);
     }
 
-    /* load module */
-    module = ly_ctx_load_module(session->ctx, model_name, revision);
+    /* load module if needed */
+    module = ly_ctx_get_module(session->ctx, model_name, revision);
+    if (!module) {
+        module = ly_ctx_load_module(session->ctx, model_name, revision);
+    }
 
     free(model_name);
     free(revision);
@@ -201,20 +204,13 @@
 static int
 ctx_load_ietf_netconf(struct ly_ctx *ctx, const char **cpblts)
 {
-    int fd, i;
-    struct lys_module *ietfnc;
+    int i;
+    const struct lys_module *ietfnc;
 
-    fd = open(SCHEMAS_DIR"ietf-netconf.yin", O_RDONLY);
-    if (fd < 0) {
-        ERR("Loading base NETCONF schema (%s) failed (%s).", SCHEMAS_DIR"ietf-netconf", strerror(errno));
+    if (!(ietfnc = ly_ctx_load_module(ctx, "ietf-netconf", NULL))) {
+        ERR("Loading base NETCONF schema failed.");
         return 1;
     }
-    if (!(ietfnc = lys_read(ctx, fd, LYS_IN_YIN))) {
-        ERR("Loading base NETCONF schema (%s) failed.", SCHEMAS_DIR"ietf-netconf");
-        close(fd);
-        return 1;
-    }
-    close(fd);
 
     /* set supported capabilities from ietf-netconf */
     for (i = 0; cpblts[i]; ++i) {
@@ -249,14 +245,16 @@
     struct nc_session *session = (struct nc_session *)user_data;
     struct nc_rpc *rpc;
     struct nc_reply *reply;
+    struct nc_reply_data *data_rpl;
     NC_MSG_TYPE msg;
     char *model_data;
+    uint64_t msgid;
 
     /* TODO later replace with yang to reduce model size? */
-    rpc = nc_rpc_getschema(name, revision, "yin");
+    rpc = nc_rpc_getschema(name, revision, "yin", NC_RPC_PARAMTYPE_CONST);
     *format = LYS_IN_YIN;
 
-    while ((msg = nc_send_rpc(session, rpc)) == NC_MSG_WOULDBLOCK) {
+    while ((msg = nc_send_rpc(session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) {
         usleep(1000);
     }
     if (msg == NC_MSG_ERROR) {
@@ -264,9 +262,9 @@
         nc_rpc_free(rpc);
         return NULL;
     }
-    nc_rpc_free(rpc);
 
-    msg = nc_recv_reply(session, 250, &reply);
+    msg = nc_recv_reply(session, rpc, msgid, 250, &reply);
+    nc_rpc_free(rpc);
     if (msg == NC_MSG_WOULDBLOCK) {
         ERR("Timeout for receiving reply to a <get-schema> expired.");
         return NULL;
@@ -275,8 +273,9 @@
         return NULL;
     }
 
-    /* TODO get data from reply */
-    model_data = NULL;
+    data_rpl = (struct nc_reply_data *)reply;
+    model_data = lyxml_serialize(((struct lyd_node_anyxml *)data_rpl->data)->value);
+    nc_reply_free(reply);
     *free_model_data = NULL;
 
     return model_data;
@@ -295,8 +294,14 @@
     /* check if get-schema is supported */
     for (i = 0; session->cpblts[i]; ++i) {
         if (!strncmp(session->cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", 51)) {
-            old_clb = ly_ctx_get_module_clb(session->ctx, &old_data);
-            ly_ctx_set_module_clb(session->ctx, &libyang_module_clb, session);
+            /* it is supported, load local ietf-netconf-monitoring so we can create <get-schema> RPCs */
+            if (ly_ctx_load_module(session->ctx, "ietf-netconf-monitoring", NULL)) {
+                /* set module retrieval using <get-schema> */
+                old_clb = ly_ctx_get_module_clb(session->ctx, &old_data);
+                ly_ctx_set_module_clb(session->ctx, &libyang_module_clb, session);
+            } else {
+                WRN("Loading NETCONF monitoring schema failed, cannot use <get-schema>.");
+            }
             break;
         }
     }
@@ -370,6 +375,7 @@
     if (nc_handshake(session)) {
         goto fail;
     }
+    session->status = NC_STATUS_RUNNING;
 
     /* check/fill libyang context */
     if (session->flags & NC_SESSION_SHAREDCTX) {
@@ -382,7 +388,6 @@
         }
     }
 
-    session->status = NC_STATUS_RUNNING;
     return session;
 
 fail:
@@ -451,11 +456,10 @@
     int r, i;
     int multisession = 0; /* flag for more NETCONF session on a single SSH session */
     struct nc_session *siter;
-    struct nc_notif_cont *ntfiter;
-    struct nc_reply_cont *rpliter;
+    struct nc_msg_cont *contiter;
     struct lyxml_elem *rpl, *child;
     struct lyd_node *close_rpc;
-    struct lys_module *ietfnc;
+    const struct lys_module *ietfnc;
     void *p;
 
     if (!session || session->status == NC_STATUS_CLOSING) {
@@ -481,30 +485,30 @@
     if (session->side == NC_CLIENT && session->status == NC_STATUS_RUNNING) {
         /* cleanup message queues */
         /* notifications */
-        for (ntfiter = session->notifs; ntfiter; ) {
-            nc_notif_free(ntfiter->msg);
+        for (contiter = session->notifs; contiter; ) {
+            lyxml_free(session->ctx, contiter->msg);
 
-            p = ntfiter;
-            ntfiter = ntfiter->next;
+            p = contiter;
+            contiter = contiter->next;
             free(p);
         }
 
         /* rpc replies */
-        for (rpliter = session->replies; rpliter; ) {
-            nc_reply_free(rpliter->msg);
+        for (contiter = session->replies; contiter; ) {
+            lyxml_free(session->ctx, contiter->msg);
 
-            p = rpliter;
-            rpliter = rpliter->next;
+            p = contiter;
+            contiter = contiter->next;
             free(p);
         }
 
         /* send closing info to the other side */
         ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
         if (!ietfnc) {
-            WRN("%s: Missing ietf-netconf schema in context (session %u), unable to send <close-session\\>", session->id);
+            WRN("%s: Missing ietf-netconf schema in context (session %u), unable to send <close-session\\>", __func__, session->id);
         } else {
             close_rpc = lyd_new(NULL, ietfnc, "close-session");
-            nc_send_rpc_(session, close_rpc);
+            nc_send_msg(session, close_rpc);
             lyd_free(close_rpc);
             switch (nc_read_msg(session, 200, &rpl)) {
             case NC_MSG_REPLY:
@@ -516,7 +520,7 @@
                 if (!child) {
                     WRN("The reply to <close-session\\> was not <ok\\> as expected.");
                 }
-                lyxml_free_elem(session->ctx, rpl);
+                lyxml_free(session->ctx, rpl);
                 break;
             case NC_MSG_WOULDBLOCK:
                 WRN("Timeout for receiving a reply to <close-session\\> elapsed.");
@@ -742,19 +746,19 @@
     }
 
     /* cleanup */
-    lyxml_free_elem(session->ctx, xml);
+    lyxml_free(session->ctx, xml);
 
     return msgtype;
 
 error:
     /* cleanup */
-    lyxml_free_elem(session->ctx, xml);
+    lyxml_free(session->ctx, xml);
 
     return NC_MSG_ERROR;
 }
 
-API NC_MSG_TYPE
-nc_recv_rpc(struct nc_session *session, int timeout, struct nc_rpc_server **rpc)
+NC_MSG_TYPE
+nc_recv_rpc(struct nc_session *session, int timeout, struct nc_server_rpc **rpc)
 {
     int r;
     struct lyxml_elem *xml = NULL;
@@ -783,9 +787,8 @@
     switch(msgtype) {
     case NC_MSG_RPC:
         *rpc = malloc(sizeof **rpc);
-        (*rpc)->type = NC_RPC_SERVER;
         (*rpc)->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
-        (*rpc)->root = xml;
+        lyxml_free(session->ctx, xml);
         break;
     case NC_MSG_HELLO:
         ERR("SESSION %u: Received another <hello> message.", session->id);
@@ -808,78 +811,407 @@
 error:
 
     /* cleanup */
-    lyxml_free_elem(session->ctx, xml);
+    lyxml_free(session->ctx, xml);
 
     return NC_MSG_ERROR;
 }
 
-static struct nc_reply *
-parse_reply(struct ly_ctx *ctx, struct lyxml_elem *xml)
+static NC_MSG_TYPE
+get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_elem **msg)
 {
-    struct lyxml_elem *iter;
-    struct nc_reply_error *error;
-    struct nc_reply_ok *ok;
-    struct nc_reply_data *data;
-    struct nc_reply *reply;
+    int r;
+    char *ptr;
+    const char *str_msgid;
+    uint64_t cur_msgid;
+    struct lyxml_elem *xml;
+    struct nc_msg_cont *cont, *prev_cont, **cont_ptr;
+    NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
+
+next_message:
+    if (msgtype) {
+        /* second run, wait and give a chance to nc_recv_reply() */
+        usleep(TIMEOUT_STEP);
+        timeout = timeout - (TIMEOUT_STEP);
+    }
+    r = session_ti_lock(session, timeout);
+    if (r > 0) {
+        /* error */
+        return NC_MSG_ERROR;
+    } else if (r < 0) {
+        /* timeout */
+        return NC_MSG_WOULDBLOCK;
+    }
+
+    /* try to get notification from the session's queue */
+    if (!msgid && session->notifs) {
+        cont = session->notifs;
+        session->notifs = cont->next;
+
+        session_ti_unlock(session);
+
+        *msg = cont->msg;
+        free(cont);
+
+        return NC_MSG_NOTIF;
+    }
+
+    /* try to get rpc-reply from the session's queue */
+    if (msgid && session->replies) {
+        prev_cont = NULL;
+        for (cont = session->replies; cont; cont = cont->next) {
+            /* errors checked in the condition below */
+            str_msgid = lyxml_get_attr(cont->msg, "message-id", NULL);
+            cur_msgid = strtoul(str_msgid, &ptr, 10);
+
+            if (cur_msgid == msgid) {
+                if (!prev_cont) {
+                    session->replies = cont->next;
+                } else {
+                    prev_cont->next = cont->next;
+                }
+                session_ti_unlock(session);
+
+                *msg = cont->msg;
+                free(cont);
+
+                return NC_MSG_REPLY;
+            }
+
+            prev_cont = cont;
+        }
+    }
+
+    /* read message from wire */
+    msgtype = nc_read_msg(session, timeout, &xml);
+
+    /* we read rpc-reply, want a notif */
+    if (!msgid && (msgtype == NC_MSG_REPLY)) {
+        /* just check that message-id is fine */
+        str_msgid = lyxml_get_attr(xml, "message-id", NULL);
+        if (!str_msgid) {
+            session_ti_unlock(session);
+            ERR("SESSION %u: Received a <rpc-reply> with no message-id, discarding.", session->id);
+            lyxml_free(session->ctx, xml);
+            goto next_message;
+        }
+        cur_msgid = strtoul(str_msgid, &ptr, 10);
+        if (ptr[0]) {
+            session_ti_unlock(session);
+            ERR("SESSION %u: Received a <rpc-reply> with an invalid message-id (\"%s\"), discarding.", session->id, str_msgid);
+            lyxml_free(session->ctx, xml);
+            goto next_message;
+        }
+
+        cont_ptr = &session->replies;
+        while (*cont_ptr) {
+            cont_ptr = &((*cont_ptr)->next);
+        }
+        *cont_ptr = malloc(sizeof **cont_ptr);
+        (*cont_ptr)->msg = xml;
+        (*cont_ptr)->next = NULL;
+    }
+
+    /* we read notif, want a rpc-reply */
+    if (msgid && (msgtype == NC_MSG_NOTIF)) {
+        if (!session->notif) {
+            session_ti_unlock(session);
+            ERR("SESSION %u: Received a <notification> but session is not subscribed.", session->id);
+            lyxml_free(session->ctx, xml);
+            goto next_message;
+        }
+
+        cont_ptr = &session->notifs;
+        while (*cont_ptr) {
+            cont_ptr = &((*cont_ptr)->next);
+        }
+        *cont_ptr = malloc(sizeof **cont_ptr);
+        (*cont_ptr)->msg = xml;
+        (*cont_ptr)->next = NULL;
+    }
+
+    session_ti_unlock(session);
+
+    switch (msgtype) {
+    case NC_MSG_NOTIF:
+        /* we want a rpc-reply */
+        if (msgid) {
+            goto next_message;
+        }
+        *msg = xml;
+        break;
+
+    case NC_MSG_REPLY:
+        /* we want a notif */
+        if (!msgid) {
+            goto next_message;
+        }
+        *msg = xml;
+        break;
+
+    case NC_MSG_HELLO:
+        ERR("SESSION %u: Received another <hello> message.", session->id);
+        lyxml_free(session->ctx, xml);
+        goto next_message;
+
+    case NC_MSG_RPC:
+        ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
+        lyxml_free(session->ctx, xml);
+        goto next_message;
+
+    default:
+        /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
+         * NC_MSG_NONE is not returned by nc_read_msg()
+         */
+        break;
+    }
+
+    return msgtype;
+}
+
+/* cannot strictly fail, but does not need to fill any error parameter at all */
+static void
+parse_rpc_error(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_err *err)
+{
+    struct lyxml_elem *iter, *next, *info;
 
     LY_TREE_FOR(xml->child, iter) {
-        if (!iter->ns || strcmp(iter->ns->value, NC_NS_BASE)) {
+        if (!iter->ns) {
+            if (iter->content) {
+                WRN("<rpc-error> child \"%s\" with value \"%s\" without namespace.", iter->name, iter->content);
+            } else {
+                WRN("<rpc-error> child \"%s\" without namespace.", iter->name);
+            }
+            continue;
+        } else if (strcmp(iter->ns->value, NC_NS_BASE)) {
+            if (iter->content) {
+                WRN("<rpc-error> child \"%s\" with value \"%s\" in an unknown namespace \"%s\".",
+                    iter->name, iter->content, iter->ns->value);
+            } else {
+                WRN("<rpc-error> child \"%s\" in an unknown namespace \"%s\".", iter->name, iter->ns->value);
+            }
             continue;
         }
 
-        if (!strcmp(iter->name, "ok")) {
-            if (reply) {
-                ERR("Unexpected content of the <rpc-reply>.");
-                goto error;
+        if (!strcmp(iter->name, "error-type")) {
+            if (!iter->content || (strcmp(iter->content, "transport") && strcmp(iter->content, "rpc")
+                    && strcmp(iter->content, "protocol") && strcmp(iter->content, "application"))) {
+                WRN("<rpc-error> <error-type> unknown value \"%s\".", (iter->content ? iter->content : ""));
+            } else if (err->type) {
+                WRN("<rpc-error> <error-type> duplicated.");
+            } else {
+                err->type = lydict_insert(ctx, iter->content, 0);
             }
-            ok = malloc(sizeof *ok);
-            ok->type = NC_REPLY_OK;
-            ok->ctx = ctx;
-            ok->root = xml;
-            reply = (struct nc_reply *)ok;
-        } else if (!strcmp(iter->name, "data")) {
-            if (reply) {
-                ERR("Unexpected content of the <rpc-reply>.");
-                goto error;
+        } else if (!strcmp(iter->name, "error-tag")) {
+            if (!iter->content || (strcmp(iter->content, "in-use") && strcmp(iter->content, "invalid-value")
+                    && strcmp(iter->content, "too-big") && strcmp(iter->content, "missing-attribute")
+                    && strcmp(iter->content, "bad-attribute") && strcmp(iter->content, "unknown-attribute")
+                    && strcmp(iter->content, "missing-element") && strcmp(iter->content, "bad-element")
+                    && strcmp(iter->content, "unknown-element") && strcmp(iter->content, "unknown-namespace")
+                    && strcmp(iter->content, "access-denied") && strcmp(iter->content, "lock-denied")
+                    && strcmp(iter->content, "resource-denied") && strcmp(iter->content, "rollback-failed")
+                    && strcmp(iter->content, "data-exists") && strcmp(iter->content, "data-missing")
+                    && strcmp(iter->content, "operation-not-supported") && strcmp(iter->content, "operation-failed")
+                    && strcmp(iter->content, "malformed-message"))) {
+                WRN("<rpc-error> <error-tag> unknown value \"%s\".", (iter->content ? iter->content : ""));
+            } else if (err->tag) {
+                WRN("<rpc-error> <error-tag> duplicated.");
+            } else {
+                err->tag = lydict_insert(ctx, iter->content, 0);
             }
-            data = malloc(sizeof *data);
-            data->type = NC_REPLY_DATA;
-            data->root = xml;
-            data->data = lyd_parse_xml(ctx, iter, LYD_OPT_DESTRUCT);
-            reply = (struct nc_reply *)data;
-        } else if (!strcmp(iter->name, "rpc-error")) {
-            if (reply && (reply->type != NC_REPLY_ERROR)) {
-                ERR("<rpc-reply> content mismatch.");
-                goto error;
+        } else if (!strcmp(iter->name, "error-severity")) {
+            if (!iter->content || (strcmp(iter->content, "error") && strcmp(iter->content, "warning"))) {
+                WRN("<rpc-error> <error-severity> unknown value \"%s\".", (iter->content ? iter->content : ""));
+            } else if (err->severity) {
+                WRN("<rpc-error> <error-severity> duplicated.");
+            } else {
+                err->severity = lydict_insert(ctx, iter->content, 0);
             }
-            /* TODO */
-            error = malloc(sizeof *error);
-            error->type = NC_REPLY_ERROR;
-            reply = (struct nc_reply *)error;
+        } else if (!strcmp(iter->name, "error-app-tag")) {
+            if (err->apptag) {
+                WRN("<rpc-error> <error-app-tag> duplicated.");
+            } else {
+                err->apptag = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
+            }
+        } else if (!strcmp(iter->name, "error-path")) {
+            if (err->path) {
+                WRN("<rpc-error> <error-path> duplicated.");
+            } else {
+                err->path = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
+            }
+        } else if (!strcmp(iter->name, "error-message")) {
+            if (err->message) {
+                WRN("<rpc-error> <error-message> duplicated.");
+            } else {
+                err->message_lang = lyxml_get_attr(iter, "xml:lang", NULL);
+                if (!err->message_lang) {
+                    VRB("<rpc-error> <error-message> without the recommended \"xml:lang\" attribute.");
+                }
+                err->message = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
+            }
+        } else if (!strcmp(iter->name, "error-info")) {
+            LY_TREE_FOR_SAFE(iter->child, next, info) {
+                if (info->ns && !strcmp(info->ns->value, NC_NS_BASE)) {
+                    if (!strcmp(info->name, "session-id")) {
+                        if (err->sid) {
+                            WRN("<rpc-error> <error-info> <session-id> duplicated.");
+                        } else {
+                            err->sid = lydict_insert(ctx, (info->content ? info->content : ""), 0);
+                        }
+                    } else if (!strcmp(info->name, "bad-attr")) {
+                        ++err->attr_count;
+                        err->attr = realloc(err->attr, err->attr_count * sizeof *err->attr);
+                        err->attr[err->attr_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
+                    } else if (!strcmp(info->name, "bad-element")) {
+                        ++err->elem_count;
+                        err->elem = realloc(err->elem, err->elem_count * sizeof *err->elem);
+                        err->elem[err->elem_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
+                    } else if (!strcmp(info->name, "bad-namespace")) {
+                        ++err->ns_count;
+                        err->ns = realloc(err->ns, err->ns_count * sizeof *err->ns);
+                        err->ns[err->ns_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
+                    } else {
+                        if (info->content) {
+                            WRN("<rpc-error> <error-info> unknown child \"%s\" with value \"%s\".",
+                                info->name, info->content);
+                        } else {
+                            WRN("<rpc-error> <error-info> unknown child \"%s\".", info->name);
+                        }
+                    }
+                } else {
+                    lyxml_unlink(ctx, info);
+                    ++err->other_count;
+                    err->other = realloc(err->other, err->other_count * sizeof *err->other);
+                    err->other[err->other_count - 1] = info;
+                }
+            }
+        } else {
+            if (iter->content) {
+                WRN("<rpc-error> unknown child \"%s\" with value \"%s\".", iter->name, iter->content);
+            } else {
+                WRN("<rpc-error> unknown child \"%s\".", iter->name);
+            }
         }
     }
+}
 
-    if (!reply) {
-        ERR("Invalid content of the <rpc-reply>.");
+static struct nc_reply *
+parse_reply(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_rpc *rpc)
+{
+    struct lyxml_elem *iter;
+    const struct lys_node *schema;
+    struct lyd_node *data;
+    struct nc_reply_error *error_rpl;
+    struct nc_reply_data *data_rpl;
+    struct nc_reply *reply = NULL;
+    int i;
+
+    if (!xml->child) {
+        ERR("An empty <rpc-reply>.");
+        return NULL;
     }
+
+    /* rpc-error */
+    if (!strcmp(xml->child->name, "rpc-error") && xml->child->ns && !strcmp(xml->child->ns->value, NC_NS_BASE)) {
+        /* count and check elements */
+        i = 0;
+        LY_TREE_FOR(xml->child, iter) {
+            if (strcmp(iter->name, "rpc-error")) {
+                ERR("<rpc-reply> content mismatch (<rpc-error> and <%s>).", iter->name);
+                return NULL;
+            } else if (!iter->ns) {
+                ERR("<rpc-reply> content mismatch (<rpc-error> without namespace).");
+                return NULL;
+            } else if (strcmp(iter->ns->value, NC_NS_BASE)) {
+                ERR("<rpc-reply> content mismatch (<rpc-error> with NS \"%s\").", iter->ns->value);
+                return NULL;
+            }
+            ++i;
+        }
+
+        error_rpl = malloc(sizeof *error_rpl);
+        error_rpl->type = NC_REPLY_ERROR;
+        error_rpl->ctx = ctx;
+        error_rpl->err = calloc(i, sizeof *error_rpl->err);
+        error_rpl->err_count = i;
+        reply = (struct nc_reply *)error_rpl;
+
+        i = 0;
+        LY_TREE_FOR(xml->child, iter) {
+            parse_rpc_error(ctx, iter, error_rpl->err + i);
+            ++i;
+        }
+
+    /* ok */
+    } else if (!strcmp(xml->child->name, "ok") && xml->child->ns && !strcmp(xml->child->ns->value, NC_NS_BASE)) {
+        if (xml->child->next) {
+            ERR("<rpc-reply> content mismatch (<ok> and <%s>).", xml->child->next->name);
+            return NULL;
+        }
+        reply = malloc(sizeof *reply);
+        reply->type = NC_REPLY_OK;
+
+    /* some RPC output */
+    } else {
+        switch (rpc->type) {
+        case NC_RPC_GENERIC:
+            schema = ((struct nc_rpc_generic *)rpc)->data->schema;
+            break;
+        case NC_RPC_GENERIC_XML:
+            data = lyd_parse(ctx, ((struct nc_rpc_generic_xml *)rpc)->xml_str, LYD_XML, 0);
+            if (!data) {
+                ERR("Failed to parse a generic RPC XML.");
+                return NULL;
+            }
+            schema = data->schema;
+            lyd_free(data);
+            break;
+        case NC_RPC_GETCONFIG:
+            schema = ly_ctx_get_node(ctx, "/ietf-netconf:get-config");
+            break;
+        case NC_RPC_GET:
+            schema = ly_ctx_get_node(ctx, "/ietf-netconf:get");
+            break;
+        case NC_RPC_GETSCHEMA:
+            schema = ly_ctx_get_node(ctx, "/ietf-netconf-monitoring:get-schema");
+            break;
+        case NC_RPC_EDIT:
+        case NC_RPC_COPY:
+        case NC_RPC_DELETE:
+        case NC_RPC_LOCK:
+        case NC_RPC_UNLOCK:
+        case NC_RPC_KILL:
+        case NC_RPC_COMMIT:
+        case NC_RPC_DISCARD:
+        case NC_RPC_CANCEL:
+        case NC_RPC_VALIDATE:
+        case NC_RPC_SUBSCRIBE:
+            /* there is no output defined */
+            break;
+        }
+
+        if (!schema) {
+            ERR("%s: internal error (%s:%d).", __func__, __FILE__, __LINE__);
+            return NULL;
+        }
+
+        data_rpl = malloc(sizeof *data_rpl);
+        data_rpl->type = NC_REPLY_DATA;
+        data_rpl->data = lyd_parse_output_xml(schema, xml, LYD_OPT_DESTRUCT);
+        if (!data_rpl->data) {
+            ERR("Failed to parse <rpc-reply>.");
+            free(data_rpl);
+            return NULL;
+        }
+        reply = (struct nc_reply *)data_rpl;
+    }
+
     return reply;
-
-error:
-    if (reply) {
-        reply->root = NULL;
-        nc_reply_free(reply);
-    }
-    return NULL;
 }
 
 API NC_MSG_TYPE
-nc_recv_reply(struct nc_session *session, int timeout, struct nc_reply **reply)
+nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int timeout, struct nc_reply **reply)
 {
-    int r;
     struct lyxml_elem *xml;
-    struct nc_reply_cont *cont_r;
-    struct nc_notif_cont **cont_n;
-    struct nc_notif *notif;
     NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
 
     if (!session || !reply) {
@@ -891,102 +1223,25 @@
     }
     *reply = NULL;
 
-    do {
-        if (msgtype && session->notif) {
-            /* second run, wait and give a chance to nc_recv_notif() */
-            usleep(TIMEOUT_STEP);
-            timeout = timeout - (TIMEOUT_STEP);
-        }
-        r = session_ti_lock(session, timeout);
-        if (r > 0) {
-            /* error */
+    msgtype = get_msg(session, timeout, msgid, &xml);
+    if (msgtype == NC_MSG_WOULDBLOCK) {
+        return NC_MSG_WOULDBLOCK;
+    }
+
+    if (msgtype == NC_MSG_REPLY) {
+        *reply = parse_reply(session->ctx, xml, rpc);
+        if (!(*reply)) {
             return NC_MSG_ERROR;
-        } else if (r < 0) {
-            /* timeout */
-            return NC_MSG_WOULDBLOCK;
         }
-
-        /* try to get message from the session's queue */
-        if (session->notifs) {
-            cont_r = session->replies;
-            session->replies = cont_r->next;
-
-            session_ti_unlock(session);
-
-            *reply = cont_r->msg;
-            free(cont_r);
-
-            return NC_MSG_REPLY;
-        }
-
-        /* read message from wire */
-        msgtype = nc_read_msg(session, timeout, &xml);
-        if (msgtype == NC_MSG_NOTIF) {
-            if (!session->notif) {
-                session_ti_unlock(session);
-                ERR("SESSION %u: Received Notification but session is not subscribed.", session->id);
-                goto error;
-            }
-
-            /* create notification object */
-            notif = malloc(sizeof *notif);
-            notif->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
-            notif->root = xml;
-
-            /* store the message for nc_recv_notif() */
-            cont_n = &session->notifs;
-            while(*cont_n) {
-                cont_n = &((*cont_n)->next);
-            }
-            *cont_n = malloc(sizeof **cont_n);
-            (*cont_n)->msg = notif;
-            (*cont_n)->next = NULL;
-        }
-
-        session_ti_unlock(session);
-
-        switch(msgtype) {
-        case NC_MSG_REPLY:
-            /* distinguish between data / ok / error reply */
-            *reply = parse_reply(session->ctx, xml);
-            if (!(*reply)) {
-                goto error;
-            }
-            break;
-        case NC_MSG_HELLO:
-            ERR("SESSION %u: Received another <hello> message.", session->id);
-            goto error;
-        case NC_MSG_RPC:
-            ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
-            goto error;
-        default:
-            /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
-             * NC_MSG_NOTIF already handled before the switch;
-             * NC_MSG_NONE is not returned by nc_read_msg()
-             */
-            break;
-        }
-
-    } while(msgtype == NC_MSG_NOTIF);
+    }
 
     return msgtype;
-
-error:
-
-    /* cleanup */
-    lyxml_free_elem(session->ctx, xml);
-
-    return NC_MSG_ERROR;
 }
 
 API NC_MSG_TYPE
 nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
 {
-    int r;
     struct lyxml_elem *xml;
-    struct nc_notif_cont *cont_n;
-    struct nc_reply_cont **cont_r;
-    struct nc_reply *reply;
     NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
 
     if (!session || !notif) {
@@ -997,85 +1252,18 @@
         return NC_MSG_ERROR;
     }
 
-    do {
-        if (msgtype) {
-            /* second run, wait and give a chance to nc_recv_reply() */
-            usleep(TIMEOUT_STEP);
-            timeout = timeout - (TIMEOUT_STEP);
-        }
-        r = session_ti_lock(session, timeout);
-        if (r > 0) {
-            /* error */
-            return NC_MSG_ERROR;
-        } else if (r < 0) {
-            /* timeout */
-            return NC_MSG_WOULDBLOCK;
-        }
+    msgtype = get_msg(session, timeout, 0, &xml);
+    if (msgtype == NC_MSG_WOULDBLOCK) {
+        return NC_MSG_WOULDBLOCK;
+    }
 
-        /* try to get message from the session's queue */
-        if (session->notifs) {
-            cont_n = session->notifs;
-            session->notifs = cont_n->next;
-
-            session_ti_unlock(session);
-
-            *notif = cont_n->msg;
-            free(cont_n);
-
-            return NC_MSG_NOTIF;
-        }
-
-        /* read message from wire */
-        msgtype = nc_read_msg(session, timeout, &xml);
-        if (msgtype == NC_MSG_REPLY) {
-            /* distinguish between data / ok / error reply */
-            reply = parse_reply(session->ctx, xml);
-            if (!reply) {
-                goto error;
-            }
-
-            /* store the message for nc_recv_reply() */
-            cont_r = &session->replies;
-            while(*cont_r) {
-                cont_r = &((*cont_r)->next);
-            }
-            *cont_r = malloc(sizeof **cont_r);
-            (*cont_r)->msg = reply;
-            (*cont_r)->next = NULL;
-        }
-
-        session_ti_unlock(session);
-
-        switch(msgtype) {
-        case NC_MSG_NOTIF:
-            *notif = malloc(sizeof **notif);
-            (*notif)->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
-            (*notif)->root = xml;
-            break;
-        case NC_MSG_HELLO:
-            ERR("SESSION %u: Received another <hello> message.", session->id);
-            goto error;
-        case NC_MSG_RPC:
-            ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
-            goto error;
-        default:
-            /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
-             * NC_MSG_REPLY already handled before the switch;
-             * NC_MSG_NONE is not returned by nc_read_msg()
-             */
-            break;
-        }
-
-    } while(msgtype == NC_MSG_NOTIF);
+    if (msgtype == NC_MSG_NOTIF) {
+        *notif = malloc(sizeof **notif);
+        (*notif)->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
+        lyxml_free(session->ctx, xml);
+    }
 
     return msgtype;
-
-error:
-
-    /* cleanup */
-    lyxml_free_elem(session->ctx, xml);
-
-    return NC_MSG_ERROR;
 }
 
 static NC_MSG_TYPE
@@ -1104,7 +1292,7 @@
 }
 
 static NC_MSG_TYPE
-nc_send_rpc_(struct nc_session *session, struct lyd_node *op)
+nc_send_msg(struct nc_session *session, struct lyd_node *op)
 {
     int r;
 
@@ -1124,7 +1312,7 @@
 }
 
 API NC_MSG_TYPE
-nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc)
+nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_t *msgid)
 {
     NC_MSG_TYPE r;
     struct nc_rpc_generic *rpc_gen;
@@ -1142,10 +1330,11 @@
     struct nc_rpc_getschema *rpc_gs;
     struct nc_rpc_subscribe *rpc_sub;
     struct lyd_node *data, *node;
-    struct lys_module *ietfnc, *ietfncmon, *notifs;
+    const struct lys_module *ietfnc, *ietfncmon, *notifs;
     char str[11];
+    uint64_t cur_msgid;
 
-    if (!session || !rpc || rpc->type == NC_RPC_SERVER) {
+    if (!session || !rpc) {
         ERR("%s: Invalid parameter", __func__);
         return NC_MSG_ERROR;
     } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
@@ -1186,14 +1375,13 @@
             return NC_MSG_ERROR;
         }
         if (rpc_gc->filter) {
-            if (rpc_gc->filter->type == NC_FILTER_SUBTREE) {
-                node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter->data);
+            if (rpc_gc->filter[0] == '<') {
+                node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter);
                 lyd_insert_attr(node, "type", "subtree");
-            } else if (rpc_gc->filter->type == NC_FILTER_XPATH) {
+            } else {
                 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
-                /* TODO - handle namespaces from XPATH query */
                 lyd_insert_attr(node, "type", "xpath");
-                lyd_insert_attr(node, "select", rpc_gc->filter->data);
+                lyd_insert_attr(node, "select", rpc_gc->filter);
             }
         }
         break;
@@ -1320,14 +1508,13 @@
 
         data = lyd_new(NULL, ietfnc, "get");
         if (rpc_g->filter) {
-            if (rpc_g->filter->type == NC_FILTER_SUBTREE) {
-                node = lyd_new_anyxml(data, ietfnc, "filter", rpc_g->filter->data);
+            if (rpc_g->filter[0] == '<') {
+                node = lyd_new_anyxml(data, ietfnc, "filter", rpc_g->filter);
                 lyd_insert_attr(node, "type", "subtree");
-            } else if (rpc_g->filter->type == NC_FILTER_XPATH) {
+            } else {
                 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
-                /* TODO - handle namespaces from XPATH query */
                 lyd_insert_attr(node, "type", "xpath");
-                lyd_insert_attr(node, "select", rpc_g->filter->data);
+                lyd_insert_attr(node, "select", rpc_g->filter);
             }
             if (!node) {
                 lyd_free(data);
@@ -1460,14 +1647,13 @@
         }
 
         if (rpc_sub->filter) {
-            if (rpc_sub->filter->type == NC_FILTER_SUBTREE) {
-                node = lyd_new_anyxml(data, notifs, "filter", rpc_sub->filter->data);
+            if (rpc_sub->filter[0] == '<') {
+                node = lyd_new_anyxml(data, notifs, "filter", rpc_sub->filter);
                 lyd_insert_attr(node, "type", "subtree");
-            } else if (rpc_sub->filter->type == NC_FILTER_XPATH) {
+            } else {
                 node = lyd_new_anyxml(data, notifs, "filter", NULL);
-                /* TODO - handle namespaces from XPATH query */
                 lyd_insert_attr(node, "type", "xpath");
-                lyd_insert_attr(node, "select", rpc_sub->filter->data);
+                lyd_insert_attr(node, "select", rpc_sub->filter);
             }
             if (!node) {
                 lyd_free(data);
@@ -1491,10 +1677,6 @@
             }
         }
         break;
-
-    case NC_RPC_SERVER:
-        ERR("Internal (%s:%d)", __FILE__, __LINE__);
-        return NC_MSG_ERROR;
     }
 
     if (lyd_validate(data, LYD_OPT_STRICT)) {
@@ -1502,16 +1684,23 @@
         return NC_MSG_ERROR;
     }
 
-    r = session_ti_lock(session, 0);
+    r = session_ti_lock(session, timeout);
     if (r != 0) {
         /* error or blocking */
         r = NC_MSG_WOULDBLOCK;
     } else {
-        /* send RPC */
-        r = nc_send_rpc_(session, data);
+        /* send RPC, store its message ID */
+        r = nc_send_msg(session, data);
+        cur_msgid = session->msgid;
     }
     session_ti_unlock(session);
 
     lyd_free(data);
-    return r;
+
+    if (r != NC_MSG_RPC) {
+        return r;
+    }
+
+    *msgid = cur_msgid;
+    return NC_MSG_RPC;
 }
diff --git a/src/session.h b/src/session.h
index e2bca47..330aed1 100644
--- a/src/session.h
+++ b/src/session.h
@@ -295,19 +295,6 @@
 void nc_session_free(struct nc_session *session);
 
 /**
- * @brief Receive NETCONF RPC.
- *
- * @param[in] session NETCONF session from which the function gets data. It must be the
- *            server side session object.
- * @param[in] timeout Timeout for reading in milliseconds. Use negative value for infinite
- *            waiting and 0 for immediate return if data are not available on wire.
- * @param[out] notif Resulting object of NETCONF RPC.
- * @return NC_MSG_RPC for success, NC_MSG_WOULDBLOCK if timeout reached and NC_MSG_ERROR
- *         when reading has failed.
- */
-NC_MSG_TYPE nc_recv_rpc(struct nc_session* session, int timeout, struct nc_rpc_server **rpc);
-
-/**
  * @brief Receive NETCONF RPC reply.
  *
  * @param[in] session NETCONF session from which the function gets data. It must be the
@@ -318,7 +305,8 @@
  * @return NC_MSG_REPLY for success, NC_MSG_WOULDBLOCK if timeout reached and NC_MSG_ERROR
  *         when reading has failed.
  */
-NC_MSG_TYPE nc_recv_reply(struct nc_session* session, int timeout, struct nc_reply **reply);
+NC_MSG_TYPE nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int timeout,
+                          struct nc_reply **reply);
 
 /**
  * @brief Receive NETCONF Notification.
@@ -342,6 +330,6 @@
  * @return #NC_MSG_RPC on success, #NC_MSG_WOULDBLOCK in case of busy session
  * (try to repeat the function call) and #NC_MSG_ERROR in case of error.
  */
-NC_MSG_TYPE nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc);
+NC_MSG_TYPE nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_t *msgid);
 
 #endif /* NC_SESSION_H_ */
diff --git a/src/session_p.h b/src/session_p.h
index c5eee5b..5b68ffb 100644
--- a/src/session_p.h
+++ b/src/session_p.h
@@ -113,19 +113,11 @@
 #define NC_VERSION_10_ENDTAG_LEN 6
 
 /**
- * @brief Container to serialize RPC reply objects
+ * @brief Container to serialize PRC messages
  */
-struct nc_reply_cont {
-    struct nc_reply *msg;
-    struct nc_reply_cont *next;
-};
-
-/**
- * @brief Container to serialize Notification objects
- */
-struct nc_notif_cont {
-    struct nc_notif *msg;
-    struct nc_notif_cont *next;
+struct nc_msg_cont {
+    struct lyxml_elem *msg;
+    struct nc_msg_cont *next;
 };
 
 /**
@@ -174,8 +166,8 @@
     /* client side only data */
     uint64_t msgid;
     const char **cpblts;           /**< list of server's capabilities on client side */
-    struct nc_reply_cont *replies; /**< queue for RPC replies received instead of notifications */
-    struct nc_notif_cont *notifs;  /**< queue for notifications received instead of RPC reply */
+    struct nc_msg_cont *replies;   /**< queue for RPC replies received instead of notifications */
+    struct nc_msg_cont *notifs;    /**< queue for notifications received instead of RPC reply */
 };
 
 /**
diff --git a/src/session_ssh.c b/src/session_ssh.c
index 382cf9f..cea2d95 100644
--- a/src/session_ssh.c
+++ b/src/session_ssh.c
@@ -911,6 +911,7 @@
     if (nc_handshake(session)) {
         goto fail;
     }
+    session->status = NC_STATUS_RUNNING;
 
     /* check/fill libyang context */
     if (session->flags & NC_SESSION_SHAREDCTX) {
@@ -928,7 +929,6 @@
     session->port = port;
     session->username = lydict_insert(ctx, username, 0);
 
-    session->status = NC_STATUS_RUNNING;
     return session;
 
 fail:
@@ -1036,6 +1036,7 @@
     if (nc_handshake(session)) {
         goto fail;
     }
+    session->status = NC_STATUS_RUNNING;
 
     /* check/fill libyang context */
     if (session->flags & NC_SESSION_SHAREDCTX) {
@@ -1059,7 +1060,6 @@
         session->username = lydict_insert_zc(ctx, username);
     }
 
-    session->status = NC_STATUS_RUNNING;
     return session;
 
 fail:
@@ -1113,6 +1113,7 @@
     if (nc_handshake(new_session)) {
         goto fail;
     }
+    new_session->status = NC_STATUS_RUNNING;
 
     /* check/fill libyang context */
     if (session->flags & NC_SESSION_SHAREDCTX) {
@@ -1130,8 +1131,6 @@
     session->port = session->port;
     session->username = lydict_insert(ctx, session->username, 0);
 
-    new_session->status = NC_STATUS_RUNNING;
-
     pthread_mutex_unlock(new_session->ti_lock);
 
     /* append to the session ring list */
diff --git a/src/session_tls.c b/src/session_tls.c
index c195907..b2c8534 100644
--- a/src/session_tls.c
+++ b/src/session_tls.c
@@ -351,6 +351,7 @@
     if (nc_handshake(session)) {
         goto fail;
     }
+    session->status = NC_STATUS_RUNNING;
 
     /* check/fill libyang context */
     if (session->flags & NC_SESSION_SHAREDCTX) {
@@ -368,7 +369,6 @@
     session->port = port;
     session->username = lydict_insert(ctx, username, 0);
 
-    session->status = NC_STATUS_RUNNING;
     return session;
 
 fail:
@@ -419,6 +419,7 @@
     if (nc_handshake(session)) {
         goto fail;
     }
+    session->status = NC_STATUS_RUNNING;
 
     /* check/fill libyang context */
     if (session->flags & NC_SESSION_SHAREDCTX) {
@@ -431,7 +432,6 @@
         }
     }
 
-    session->status = NC_STATUS_RUNNING;
     return session;
 
 fail: