diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..c09fe0d125212863155ea662b67b071fa39676f1
--- /dev/null
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java
@@ -0,0 +1,61 @@
+package org.duniter.core.client.model.elasticsearch;
+
+/*
+ * #%L
+ * Duniter4j :: Core Client API
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+/**
+ * Created by blavenie on 01/03/16.
+ */
+public class DeleteRecord extends Record {
+
+    public static final String PROPERTY_INDEX="index";
+    public static final String PROPERTY_TYPE="type";
+    public static final String PROPERTY_ID="id";
+
+    private String index;
+    private String type;
+    private String id;
+
+    public String getIndex() {
+        return index;
+    }
+
+    public void setIndex(String index) {
+        this.index = index;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+}
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5fd532cbe270bff4e83d5afe9fa11109d0c0189
--- /dev/null
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java
@@ -0,0 +1,34 @@
+package org.duniter.core.client.model.elasticsearch;
+
+/*
+ * #%L
+ * Duniter4j :: Core Client API
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+/**
+ * Created by blavenie on 01/03/16.
+ */
+public class UserProfile extends Record {
+
+    public static final String PROPERTY_TITLE = "title";
+    public static final String PROPERTY_DESCRIPTION="description";
+    public static final String PROPERTY_CITY="city";
+
+}
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java
index 8ef4f7a8c09f5977f4b139c47eae55178ce000ff..ab35ea1c50996b4311bfe97633fc2ab0f0d53d6d 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java
@@ -23,23 +23,45 @@ package org.duniter.elasticsearch.action;
  */
 
 import org.duniter.elasticsearch.action.currency.RestCurrencyIndexAction;
+import org.duniter.elasticsearch.action.history.RestHistoryDeleteIndexAction;
 import org.duniter.elasticsearch.action.market.RestMarketCommentIndexAction;
+import org.duniter.elasticsearch.action.market.RestMarketCommentUpdateAction;
 import org.duniter.elasticsearch.action.market.RestMarketRecordIndexAction;
+import org.duniter.elasticsearch.action.market.RestMarketRecordUpdateAction;
 import org.duniter.elasticsearch.action.registry.RestRegistryRecordIndexAction;
 import org.duniter.elasticsearch.action.security.RestSecurityAuthAction;
 import org.duniter.elasticsearch.action.security.RestSecurityGetChallengeAction;
+import org.duniter.elasticsearch.action.user.RestUserProfileIndexAction;
+import org.duniter.elasticsearch.action.user.RestUserProfileUpdateAction;
 import org.elasticsearch.common.inject.AbstractModule;
 import org.elasticsearch.common.inject.Module;
 
 public class RestModule extends AbstractModule implements Module {
 
     @Override protected void configure() {
+
+        // Currency
         bind(RestCurrencyIndexAction.class).asEagerSingleton();
+
+        // Market
         bind(RestMarketRecordIndexAction.class).asEagerSingleton();
+        bind(RestMarketRecordUpdateAction.class).asEagerSingleton();
         bind(RestMarketCommentIndexAction.class).asEagerSingleton();
+        bind(RestMarketCommentUpdateAction.class).asEagerSingleton();
+
+        // Registry
         bind(RestRegistryRecordIndexAction.class).asEagerSingleton();
 
+        // User
+        bind(RestUserProfileIndexAction.class).asEagerSingleton();
+        bind(RestUserProfileUpdateAction.class).asEagerSingleton();
+
+        // Authentication
         bind(RestSecurityGetChallengeAction.class).asEagerSingleton();
         bind(RestSecurityAuthAction.class).asEagerSingleton();
+
+        // History
+        bind(RestHistoryDeleteIndexAction.class).asEagerSingleton();
+
     }
 }
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..faea221db9ce82346ecbcf3bfaee3b06915a5412
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java
@@ -0,0 +1,71 @@
+package org.duniter.elasticsearch.action.history;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
+import org.duniter.elasticsearch.service.HistoryService;
+import org.duniter.elasticsearch.service.MarketService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.rest.*;
+
+import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestStatus.BAD_REQUEST;
+import static org.elasticsearch.rest.RestStatus.OK;
+
+public class RestHistoryDeleteIndexAction extends BaseRestHandler {
+
+    private static final ESLogger log = ESLoggerFactory.getLogger(RestHistoryDeleteIndexAction.class.getName());
+
+    private HistoryService service;
+
+    @Inject
+    public RestHistoryDeleteIndexAction(Settings settings, RestController controller, Client client, HistoryService service) {
+        super(settings, controller, client);
+        controller.registerHandler(POST, "/history/delete", this);
+        this.service = service;
+    }
+
+    @Override
+    protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception {
+
+        try {
+            String recordId = service.indexDeleteFromJson(request.content().toUtf8());
+
+            restChannel.sendResponse(new BytesRestResponse(OK, recordId));
+        }
+        catch(DuniterElasticsearchException | BusinessException e) {
+            log.error(e.getMessage(), e);
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
+        }
+        catch(Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java
index d43148bc299060125628dbcdf3967f843ca61cf2..28ff0a9c5b344546835bb1aefca0a15c40c89ab5 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java
@@ -23,6 +23,8 @@ package org.duniter.elasticsearch.action.market;
  */
 
 import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
 import org.duniter.elasticsearch.service.MarketService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
@@ -56,9 +58,9 @@ public class RestMarketCommentIndexAction extends BaseRestHandler {
 
             restChannel.sendResponse(new BytesRestResponse(OK, recordId));
         }
-        catch(BusinessException e) {
+        catch(DuniterElasticsearchException | BusinessException e) {
             log.error(e.getMessage(), e);
-            restChannel.sendResponse(new BytesRestResponse(BAD_REQUEST, String.format("{error: {ucode: 'XXX', message:'%s'}}", e.getMessage())));
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
         }
         catch(Exception e) {
             log.error(e.getMessage(), e);
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..213198cc6a05a0f12b7848f2ab7f063cd65e060f
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java
@@ -0,0 +1,69 @@
+package org.duniter.elasticsearch.action.market;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
+import org.duniter.elasticsearch.service.MarketService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.rest.*;
+
+import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestStatus.OK;
+
+public class RestMarketCommentUpdateAction extends BaseRestHandler {
+
+    private static final ESLogger log = ESLoggerFactory.getLogger(RestMarketCommentUpdateAction.class.getName());
+
+    private MarketService service;
+
+    @Inject
+    public RestMarketCommentUpdateAction(Settings settings, RestController controller, Client client, MarketService service) {
+        super(settings, controller, client);
+        controller.registerHandler(POST, "/market/comment/{id}/_update", this);
+        this.service = service;
+    }
+
+    @Override
+    protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception {
+
+        String id = request.param("id");
+        try {
+            service.updateCommentFromJson(request.content().toUtf8(), id);
+            restChannel.sendResponse(new BytesRestResponse(OK, id));
+        }
+        catch(DuniterElasticsearchException | BusinessException e) {
+            log.error(e.getMessage(), e);
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
+        }
+        catch(Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java
index 67ba7708c23f6b3cda743a7e3e8da2b1a2209fa9..bcc4bb2b0baf03b88eb34cae3ad2fff5694864f7 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java
@@ -23,6 +23,8 @@ package org.duniter.elasticsearch.action.market;
  */
 
 import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
 import org.duniter.elasticsearch.service.MarketService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
@@ -56,9 +58,9 @@ public class RestMarketRecordIndexAction extends BaseRestHandler {
 
             restChannel.sendResponse(new BytesRestResponse(OK, recordId));
         }
-        catch(BusinessException e) {
+        catch(DuniterElasticsearchException | BusinessException e) {
             log.error(e.getMessage(), e);
-            restChannel.sendResponse(new BytesRestResponse(BAD_REQUEST, String.format("{error: {ucode: 'XXX', message:'%s'}}", e.getMessage())));
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
         }
         catch(Exception e) {
             log.error(e.getMessage(), e);
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..bde8c3dd6f2f0f3f01c2ef45e0e4cd1f387371c7
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java
@@ -0,0 +1,68 @@
+package org.duniter.elasticsearch.action.market;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
+import org.duniter.elasticsearch.service.MarketService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.rest.*;
+
+import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestStatus.OK;
+
+public class RestMarketRecordUpdateAction extends BaseRestHandler {
+
+    private static final ESLogger log = ESLoggerFactory.getLogger(RestMarketRecordUpdateAction.class.getName());
+
+    private MarketService service;
+
+    @Inject
+    public RestMarketRecordUpdateAction(Settings settings, RestController controller, Client client, MarketService service) {
+        super(settings, controller, client);
+        controller.registerHandler(POST, "/market/record/{id}/_update", this);
+        this.service = service;
+    }
+
+    @Override
+    protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception {
+        String id = request.param("id");
+        try {
+            service.updateRecordFromJson(request.content().toUtf8(), id);
+            restChannel.sendResponse(new BytesRestResponse(OK, id));
+        }
+        catch(DuniterElasticsearchException | BusinessException e) {
+            log.error(e.getMessage(), e);
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
+        }
+        catch(Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java
index 858b4368df7e477e9866349da54ffdc449f4500c..92e2d6de3752882a9809b1e48c0d85c7b265563f 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java
@@ -23,6 +23,8 @@ package org.duniter.elasticsearch.action.registry;
  */
 
 import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
 import org.duniter.elasticsearch.service.RegistryService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
@@ -56,9 +58,9 @@ public class RestRegistryRecordIndexAction extends BaseRestHandler {
 
             restChannel.sendResponse(new BytesRestResponse(OK, recordId));
         }
-        catch(BusinessException e) {
+        catch(DuniterElasticsearchException | BusinessException e) {
             log.error(e.getMessage(), e);
-            restChannel.sendResponse(new BytesRestResponse(BAD_REQUEST, String.format("{error: {ucode: 'XXX', message:'%s'}}", e.getMessage())));
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
         }
         catch(Exception e) {
             log.error(e.getMessage(), e);
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d37e42f141b4db609cfbcbc8a3c9420adc2ed2a
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java
@@ -0,0 +1,71 @@
+package org.duniter.elasticsearch.action.user;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
+import org.duniter.elasticsearch.service.MarketService;
+import org.duniter.elasticsearch.service.UserService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.rest.*;
+
+import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestStatus.BAD_REQUEST;
+import static org.elasticsearch.rest.RestStatus.OK;
+
+public class RestUserProfileIndexAction extends BaseRestHandler {
+
+    private static final ESLogger log = ESLoggerFactory.getLogger(RestUserProfileIndexAction.class.getName());
+
+    private UserService service;
+
+    @Inject
+    public RestUserProfileIndexAction(Settings settings, RestController controller, Client client, UserService service) {
+        super(settings, controller, client);
+        controller.registerHandler(POST, "/user/profile", this);
+        this.service = service;
+    }
+
+    @Override
+    protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception {
+
+        try {
+            String profileId = service.indexProfileFromJson(request.content().toUtf8());
+
+            restChannel.sendResponse(new BytesRestResponse(OK, profileId));
+        }
+        catch(DuniterElasticsearchException | BusinessException e) {
+            log.error(e.getMessage(), e);
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
+        }
+        catch(Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..75174db28093f026bbb09233185ae4d637798439
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java
@@ -0,0 +1,69 @@
+package org.duniter.elasticsearch.action.user;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.exception.BusinessException;
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
+import org.duniter.elasticsearch.service.UserService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.rest.*;
+
+import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestStatus.OK;
+
+public class RestUserProfileUpdateAction extends BaseRestHandler {
+
+    private static final ESLogger log = ESLoggerFactory.getLogger(RestUserProfileUpdateAction.class.getName());
+
+    private UserService service;
+
+    @Inject
+    public RestUserProfileUpdateAction(Settings settings, RestController controller, Client client, UserService service) {
+        super(settings, controller, client);
+        controller.registerHandler(POST, "/user/profile/{id}/_update", this);
+        this.service = service;
+    }
+
+    @Override
+    protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception {
+        String id = request.param("id");
+
+        try {
+            service.updateProfileFromJson(request.content().toUtf8(), id);
+            restChannel.sendResponse(new BytesRestResponse(OK, id));
+        }
+        catch(DuniterElasticsearchException | BusinessException e) {
+            log.error(e.getMessage(), e);
+            restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
+        }
+        catch(Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java
index c4603b3e446e10627c34ebf19afac4d3383fbb1b..b2b0b67800d4ae62380ef845aa516fd3b9638afe 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java
@@ -23,27 +23,27 @@ package org.duniter.elasticsearch.exception;
  */
 
 
-import org.duniter.core.exception.BusinessException;
+import org.elasticsearch.rest.RestStatus;
 
 /**
  * Created by Benoit on 03/04/2015.
  */
-public class AccessDeniedException extends BusinessException{
+public class AccessDeniedException extends DuniterElasticsearchException{
 
-    public AccessDeniedException() {
-        super();
+    public AccessDeniedException(Throwable cause) {
+        super(cause);
     }
 
-    public AccessDeniedException(String message, Throwable cause) {
-        super(message, cause);
+    public AccessDeniedException(String msg, Object... args) {
+        super(msg, args);
     }
 
-    public AccessDeniedException(String message) {
-        super(message);
+    public AccessDeniedException(String msg, Throwable cause, Object... args) {
+        super(msg, args, cause);
     }
 
-    public AccessDeniedException(Throwable cause) {
-        super(cause);
+    @Override
+    public RestStatus status() {
+        return RestStatus.FORBIDDEN;
     }
-
 }
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java
new file mode 100644
index 0000000000000000000000000000000000000000..bab585576c45a75405d1850ba391267d1e733811
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java
@@ -0,0 +1,23 @@
+package org.duniter.elasticsearch.exception;
+
+import org.elasticsearch.ElasticsearchException;
+
+/**
+ * Created by blavenie on 28/07/16.
+ */
+public abstract class DuniterElasticsearchException extends ElasticsearchException {
+
+
+    public DuniterElasticsearchException(Throwable cause) {
+        super(cause);
+    }
+
+    public DuniterElasticsearchException(String msg, Object... args) {
+        super(msg, args);
+    }
+
+    public DuniterElasticsearchException(String msg, Throwable cause, Object... args) {
+        super(msg, args, cause);
+    }
+
+}
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java
index c72d3888afc3e93674021e2a894c6ff6fbb9a852..58d230e432452829df2e18ebf828a9a59cc232ed 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java
@@ -24,27 +24,29 @@ package org.duniter.elasticsearch.exception;
 
 
 import org.duniter.core.exception.BusinessException;
+import org.elasticsearch.rest.RestStatus;
 
 /**
  *
  * Created by Benoit on 03/04/2015.
  */
-public class DuplicateIndexIdException extends BusinessException{
+public class DuplicateIndexIdException extends DuniterElasticsearchException{
 
-    public DuplicateIndexIdException() {
-        super();
+    public DuplicateIndexIdException(Throwable cause) {
+        super(cause);
     }
 
-    public DuplicateIndexIdException(String message, Throwable cause) {
-        super(message, cause);
+    public DuplicateIndexIdException(String msg, Object... args) {
+        super(msg, args);
     }
 
-    public DuplicateIndexIdException(String message) {
-        super(message);
+    public DuplicateIndexIdException(String msg, Throwable cause, Object... args) {
+        super(msg, args, cause);
     }
 
-    public DuplicateIndexIdException(Throwable cause) {
-        super(cause);
-    }
 
+    @Override
+    public RestStatus status() {
+        return RestStatus.BAD_REQUEST;
+    }
 }
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java
index 63bba9c6bcb3b35537c542aecf38624e1d2ab92e..4d47e5141b06ff7678ebc3f211e3f819f55a5cb0 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java
@@ -23,24 +23,26 @@ package org.duniter.elasticsearch.exception;
  */
 
 import org.duniter.core.exception.BusinessException;
+import org.elasticsearch.rest.RestStatus;
 
 /**
  * Created by blavenie on 01/03/16.
  */
-public class InvalidFormatException extends BusinessException {
-    public InvalidFormatException() {
-        super();
+public class InvalidFormatException extends DuniterElasticsearchException {
+    public InvalidFormatException(Throwable cause) {
+        super(cause);
     }
 
-    public InvalidFormatException(String message, Throwable cause) {
-        super(message, cause);
+    public InvalidFormatException(String msg, Object... args) {
+        super(msg, args);
     }
 
-    public InvalidFormatException(String message) {
-        super(message);
+    public InvalidFormatException(String msg, Throwable cause, Object... args) {
+        super(msg, args, cause);
     }
 
-    public InvalidFormatException(Throwable cause) {
-        super(cause);
+    @Override
+    public RestStatus status() {
+        return RestStatus.BAD_REQUEST;
     }
 }
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java
index 09a83041fa3cb432d18dbfe6d4282150e42d694d..19476ae5e73cb9298ea0a32ca4a5e08ab41d5ebe 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java
@@ -23,27 +23,29 @@ package org.duniter.elasticsearch.exception;
  */
 
 
-import org.duniter.core.exception.BusinessException;
+import org.elasticsearch.rest.RestStatus;
 
 /**
  * Created by Benoit on 03/04/2015.
  */
-public class InvalidSignatureException extends BusinessException {
+public class InvalidSignatureException extends DuniterElasticsearchException {
 
 
-    public InvalidSignatureException() {
-        super();
+    public InvalidSignatureException(Throwable cause) {
+        super(cause);
     }
 
-    public InvalidSignatureException(String message, Throwable cause) {
-        super(message, cause);
+    public InvalidSignatureException(String msg, Object... args) {
+        super(msg, args);
     }
 
-    public InvalidSignatureException(String message) {
-        super(message);
+    public InvalidSignatureException(String msg, Throwable cause, Object... args) {
+        super(msg, args, cause);
     }
 
-    public InvalidSignatureException(Throwable cause) {
-        super(cause);
+
+    @Override
+    public RestStatus status() {
+        return RestStatus.BAD_REQUEST;
     }
 }
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..94d00b4b274892c4e2fbc33da71171e2ba98766f
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java
@@ -0,0 +1,50 @@
+package org.duniter.elasticsearch.exception;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.duniter.core.exception.BusinessException;
+import org.elasticsearch.rest.RestStatus;
+
+/**
+ * Created by Benoit on 03/04/2015.
+ */
+public class NotFoundException extends DuniterElasticsearchException{
+
+    public NotFoundException(Throwable cause) {
+        super(cause);
+    }
+
+    public NotFoundException(String msg, Object... args) {
+        super(msg, args);
+    }
+
+    public NotFoundException(String msg, Throwable cause, Object... args) {
+        super(msg, args, cause);
+    }
+
+    @Override
+    public RestStatus status() {
+        return RestStatus.NOT_FOUND;
+    }
+}
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java
index 2840c839a79d1b37ce91344997ddbc57229e8c74..d0b8745c45e76fc1d90084ca5ee830cedffc0ec4 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java
@@ -24,10 +24,7 @@ package org.duniter.elasticsearch.node;
 
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.elasticsearch.PluginSettings;
-import org.duniter.elasticsearch.service.BlockchainService;
-import org.duniter.elasticsearch.service.MarketService;
-import org.duniter.elasticsearch.service.MessageService;
-import org.duniter.elasticsearch.service.RegistryService;
+import org.duniter.elasticsearch.service.*;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.common.component.AbstractLifecycleComponent;
 import org.elasticsearch.common.inject.Inject;
@@ -90,9 +87,18 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> {
                     .deleteIndex()
                     .createIndexIfNotExists();
 
+            injector.getInstance(UserService.class)
+                    .deleteIndex()
+                    .createIndexIfNotExists();
+
+            injector.getInstance(HistoryService.class)
+                    .deleteIndex()
+                    .createIndexIfNotExists();
+
             injector.getInstance(BlockchainService.class)
                     .indexLastBlocks(peer);
 
+
             if (logger.isInfoEnabled()) {
                 logger.info("Reloading all Duniter indices... [OK]");
             }
@@ -103,8 +109,10 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> {
             }
 
             injector.getInstance(RegistryService.class).createIndexIfNotExists();
-
             injector.getInstance(MarketService.class).createIndexIfNotExists();
+            injector.getInstance(MessageService.class).createIndexIfNotExists();
+            injector.getInstance(UserService.class).createIndexIfNotExists();
+            injector.getInstance(HistoryService.class).createIndexIfNotExists();
 
             if (logger.isInfoEnabled()) {
                 logger.info("Checking Duniter indices... [OK]");
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..451dc57c71330bdbaf6562131ae14b6aeb2b065c
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java
@@ -0,0 +1,39 @@
+package org.duniter.elasticsearch.rest;
+
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.rest.RestRequest;
+
+import java.io.IOException;
+
+public class RestXContentBuilder {
+
+    public static XContentBuilder restContentBuilder(RestRequest request) throws IOException {
+        XContentType contentType = XContentType.fromRestContentType(request.param("format", request.header("Content-Type")));
+        if (contentType == null) {
+            // default to JSON
+            contentType = XContentType.JSON;
+        }
+        XContentBuilder builder = new XContentBuilder(XContentFactory.xContent(contentType),
+                new BytesStreamOutput());
+        if (request.paramAsBoolean("pretty", false)) {
+            builder.prettyPrint().lfAtEnd();
+        }
+        String casing = request.param("case");
+        if (casing != null && "camelCase".equals(casing)) {
+            builder.fieldCaseConversion(XContentBuilder.FieldCaseConversion.CAMELCASE);
+        } else {
+            // we expect all REST interfaces to write results in underscore casing, so
+            // no need for double casing
+            builder.fieldCaseConversion(XContentBuilder.FieldCaseConversion.NONE);
+        }
+        return builder;
+    }
+
+    public static XContentBuilder emptyBuilder(RestRequest request) throws IOException {
+        return restContentBuilder(request).startObject().endObject();
+    }
+
+}
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..c79558e73ce5b6acdfea094aa05428d802d912ee
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java
@@ -0,0 +1,16 @@
+package org.duniter.elasticsearch.rest;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.BytesRestResponse;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.RestStatus;
+
+import java.io.IOException;
+
+public class XContentRestResponse extends BytesRestResponse {
+
+    public XContentRestResponse(RestRequest request, RestStatus status, XContentBuilder builder) throws IOException {
+        super(status, builder);
+    }
+
+}
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..e759e7a22d65ebcbd7d658ab1d7bb50af2597aad
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java
@@ -0,0 +1,63 @@
+package org.duniter.elasticsearch.rest;
+
+import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.RestStatus;
+
+import java.io.IOException;
+
+import static org.elasticsearch.ExceptionsHelper.detailedMessage;
+import static org.duniter.elasticsearch.rest.RestXContentBuilder.restContentBuilder;
+
+public class XContentThrowableRestResponse extends XContentRestResponse {
+
+    public XContentThrowableRestResponse(RestRequest request, Throwable t) throws IOException {
+        this(request, ((t instanceof ElasticsearchException) ? ((ElasticsearchException) t).status() : ((t instanceof DuniterElasticsearchException) ? ((DuniterElasticsearchException) t).status() : RestStatus.INTERNAL_SERVER_ERROR)), t);
+    }
+
+    public XContentThrowableRestResponse(RestRequest request, RestStatus status, Throwable t) throws IOException {
+        super(request, status, convert(request, status, t));
+    }
+
+    private static XContentBuilder convert(RestRequest request, RestStatus status, Throwable t) throws IOException {
+        XContentBuilder builder = restContentBuilder(request).startObject()
+                .field("error", detailedMessage(t))
+                .field("status", status.getStatus());
+        if (t != null && request.paramAsBoolean("error_trace", false)) {
+            builder.startObject("error_trace");
+            boolean first = true;
+            while (t != null) {
+                if (!first) {
+                    builder.startObject("cause");
+                }
+                buildThrowable(t, builder);
+                if (!first) {
+                    builder.endObject();
+                }
+                t = t.getCause();
+                first = false;
+            }
+            builder.endObject();
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    private static void buildThrowable(Throwable t, XContentBuilder builder) throws IOException {
+        builder.field("message", t.getMessage());
+        for (StackTraceElement stElement : t.getStackTrace()) {
+            builder.startObject("at")
+                    .field("class", stElement.getClassName())
+                    .field("method", stElement.getMethodName());
+            if (stElement.getFileName() != null) {
+                builder.field("file", stElement.getFileName());
+            }
+            if (stElement.getLineNumber() >= 0) {
+                builder.field("line", stElement.getLineNumber());
+            }
+            builder.endObject();
+        }
+    }
+}
\ No newline at end of file
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java
index ff61ad270b05cc670a8709ca8ed70cb2cfe2ab50..bfaee8c3c4b7938727c9f8e699fc50147265d01c 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java
@@ -48,8 +48,8 @@ public class SecurityTokenStore {
 
     @Inject
     public SecurityTokenStore(Settings settings) {
-        this.prefix = settings.get("duniter4j.auth.token.prefix", "duniter4j-");
-        this.validityDurationInSeconds = settings.getAsInt("duniter4j.auth.tokenValidityDuration", 30*60 /*30min*/ );
+        this.prefix = settings.get("duniter.auth.token.prefix", "duniter-");
+        this.validityDurationInSeconds = settings.getAsInt("duniter.auth.tokenValidityDuration", 600 /*= 10min*/ );
         this.tokenCache = initGeneratedMessageCache();
     }
 
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
index 0c6ba4dbf2744482d8d5085711d07e86bb703d0a..d06108794fcd756b0628763c7595fdca478d299d 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
@@ -23,55 +23,68 @@ package org.duniter.elasticsearch.service;
  */
 
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.gson.JsonSyntaxException;
 import org.duniter.core.beans.Bean;
+import org.duniter.core.client.model.elasticsearch.Record;
 import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.exception.AccessDeniedException;
+import org.duniter.elasticsearch.exception.InvalidFormatException;
+import org.duniter.elasticsearch.exception.InvalidSignatureException;
+import org.duniter.elasticsearch.exception.NotFoundException;
+import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder;
 import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequestBuilder;
 import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
 import org.elasticsearch.action.bulk.BulkRequest;
-import org.elasticsearch.client.AdminClient;
+import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Requests;
-import org.elasticsearch.client.transport.TransportClient;
 import org.elasticsearch.common.bytes.BytesArray;
-import org.elasticsearch.common.component.AbstractComponent;
-import org.elasticsearch.common.component.Lifecycle;
-import org.elasticsearch.common.component.LifecycleComponent;
-import org.elasticsearch.common.component.LifecycleListener;
 import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.logging.ESLogger;
-import org.elasticsearch.common.logging.ESLoggerFactory;
 import org.elasticsearch.common.logging.Loggers;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.transport.InetSocketTransportAddress;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.node.NodeBuilder;
 
 import java.io.*;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Created by Benoit on 08/04/2015.
  */
 public abstract class AbstractService implements Bean {
 
+    protected static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\"";
+    protected static final String REGEX_WORD_SEPARATOR = "[-\\t@# ]+";
+    protected static final String REGEX_SPACE = "[\\t\\n\\r ]+";
+
     protected final ESLogger logger;
     protected final Client client;
     protected final PluginSettings pluginSettings;
     protected final ObjectMapper objectMapper;
+    protected final CryptoService cryptoService;
+
+    public AbstractService(Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+        this.logger = Loggers.getLogger(getClass());
+        this.client = client;
+        this.pluginSettings = pluginSettings;
+        this.cryptoService = cryptoService;
+        this.objectMapper = new ObjectMapper();
+    }
 
-    @Inject
     public AbstractService(Client client, PluginSettings pluginSettings) {
         this.logger = Loggers.getLogger(getClass());
         this.client = client;
         this.pluginSettings = pluginSettings;
+        this.cryptoService = null;
         this.objectMapper = new ObjectMapper();
     }
 
@@ -114,6 +127,65 @@ public abstract class AbstractService implements Bean {
         }
     }
 
+    protected JsonNode readAndVerifyIssuerSignature(String recordJson) throws ElasticsearchException {
+
+        try {
+            JsonNode actualObj = objectMapper.readTree(recordJson);
+            readAndVerifyIssuerSignature(recordJson, actualObj);
+            return actualObj;
+        }
+        catch(IOException | JsonSyntaxException e) {
+            throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e);
+        }
+    }
+
+    protected void readAndVerifyIssuerSignature(String recordJson, JsonNode actualObj) throws ElasticsearchException {
+
+        try {
+            Set<String> fieldNames = ImmutableSet.copyOf(actualObj.fieldNames());
+            if (!fieldNames.contains(Record.PROPERTY_ISSUER)
+                    || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) {
+                throw new InvalidFormatException(String.format("Invalid record JSON format. Required fields [%s,%s]", Record.PROPERTY_ISSUER, Record.PROPERTY_SIGNATURE));
+            }
+            String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText();
+            String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText();
+
+            String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "")
+                    .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), "");
+
+            if (!cryptoService.verify(recordNoSign, signature, issuer)) {
+                throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign);
+            }
+
+            // TODO: check issuer is in the WOT ?
+        }
+        catch(JsonSyntaxException e) {
+            throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e);
+        }
+    }
+
+    protected void checkSameDocumentIssuer(String index, String type, String id, String expectedIssuer) throws ElasticsearchException {
+
+        GetResponse response = client.prepareGet(index, type, id)
+                .setFields(Record.PROPERTY_ISSUER)
+                .execute().actionGet();
+        boolean failed = !response.isExists();
+        if (failed) {
+            throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, id));
+        } else {
+            String docIssuer = (String)response.getFields().get(Record.PROPERTY_ISSUER).getValue();
+            if (!Objects.equals(expectedIssuer, docIssuer)) {
+                throw new AccessDeniedException(String.format("Could not delete this document: not issuer."));
+            }
+        }
+    }
+
+
+
+    protected String getIssuer(JsonNode actualObj) {
+        return  actualObj.get(Record.PROPERTY_ISSUER).asText();
+    }
+
     protected void bulkFromClasspathFile(String classpathFile, String indexName, String indexType) {
         InputStream is = null;
         try {
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3995e2d363af61909b90ef9d72124339db02c605
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java
@@ -0,0 +1,226 @@
+package org.duniter.elasticsearch.service;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.duniter.core.client.model.elasticsearch.DeleteRecord;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.exception.NotFoundException;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class HistoryService extends AbstractService {
+
+    public static final String INDEX = "history";
+    public static final String DELETE_TYPE = "delete";
+
+    @Inject
+    public HistoryService(Client client, PluginSettings settings, CryptoService cryptoService) {
+        super(client, settings, cryptoService);
+    }
+
+    /**
+     * Delete blockchain index, and all data
+     * @throws JsonProcessingException
+     */
+    public HistoryService deleteIndex() {
+        deleteIndexIfExists(INDEX);
+        return this;
+    }
+
+
+    public boolean existsIndex() {
+        return super.existsIndex(INDEX);
+    }
+
+    /**
+     * Create index need for blockchain registry, if need
+     */
+    public HistoryService createIndexIfNotExists() {
+        try {
+            if (!existsIndex(INDEX)) {
+                createIndex();
+            }
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(String.format("Error while creating index [%s]", INDEX));
+        }
+
+        return this;
+    }
+
+    /**
+     * Create index need for category registry
+     * @throws JsonProcessingException
+     */
+    public HistoryService createIndex() throws JsonProcessingException {
+        logger.info(String.format("Creating index [%s/%s]", INDEX, DELETE_TYPE));
+
+        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
+        Settings indexSettings = Settings.settingsBuilder()
+                .put("number_of_shards", 2)
+                .put("number_of_replicas", 1)
+                //.put("analyzer", createDefaultAnalyzer())
+                .build();
+        createIndexRequestBuilder.setSettings(indexSettings);
+        createIndexRequestBuilder.addMapping(DELETE_TYPE, createDeleteType());
+        createIndexRequestBuilder.execute().actionGet();
+
+        return this;
+    }
+
+
+    public String indexDeleteFromJson(String recordJson) {
+        JsonNode actualObj = readAndVerifyIssuerSignature(recordJson);
+        String issuer = actualObj.get(DeleteRecord.PROPERTY_ISSUER).asText();
+
+        String index = actualObj.get(DeleteRecord.PROPERTY_INDEX).asText();
+        String type = actualObj.get(DeleteRecord.PROPERTY_TYPE).asText();
+        String id = actualObj.get(DeleteRecord.PROPERTY_ID).asText();
+
+        if (!existsIndex(index)) {
+            throw new NotFoundException(String.format("Index [%s] not exists.", index));
+        }
+
+        // Check document issuer
+        checkSameDocumentIssuer(index, type, id, issuer);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Deleting document [%s/%s/%s] - issuer [%s]", index, type, id, issuer.substring(0, 8)));
+        }
+
+        // Add deletion to history
+        IndexResponse response = client.prepareIndex(INDEX, DELETE_TYPE)
+                .setSource(recordJson)
+                .setRefresh(false)
+                .execute().actionGet();
+
+        // Delete the document
+        client.prepareDelete(index, type, id).execute().actionGet();
+
+        return response.getId();
+    }
+
+
+    /* -- Internal methods -- */
+
+
+    public XContentBuilder createDeleteType() {
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(DELETE_TYPE)
+                    .startObject("properties")
+
+                    // index
+                    .startObject("index")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // type
+                    .startObject("type")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // id
+                    .startObject("id")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // time
+                    .startObject("time")
+                    .field("type", "integer")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, DELETE_TYPE, ioe.getMessage()), ioe);
+        }
+    }
+
+    public XContentBuilder createRecordCommentType() {
+        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
+
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(DELETE_TYPE)
+                    .startObject("properties")
+
+                    // issuer
+                    .startObject("issuer")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // time
+                    .startObject("time")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // message
+                    .startObject("message")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // record
+                    .startObject("record")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // reply to
+                    .startObject("reply_to")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, DELETE_TYPE, ioe.getMessage()), ioe);
+        }
+    }
+
+}
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java
index 1795ce20d0ca4f110c68f22e8ad1bb892f52b16c..417b4207e3d7db785ad9e4ab6932b9455a6559f3 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.collect.Sets;
 import com.google.gson.JsonSyntaxException;
+import org.duniter.core.client.model.elasticsearch.DeleteRecord;
 import org.duniter.core.client.model.elasticsearch.Record;
 import org.duniter.core.client.service.bma.WotRemoteService;
 import org.duniter.core.exception.TechnicalException;
@@ -37,6 +38,7 @@ import org.duniter.elasticsearch.exception.InvalidSignatureException;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.action.update.UpdateResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
@@ -57,15 +59,12 @@ public class MarketService extends AbstractService {
     public static final String RECORD_COMMENT_TYPE = "comment";
 
     private static final String CATEGORIES_BULK_CLASSPATH_FILE = "market-categories-bulk-insert.json";
-    private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\"";
 
-    private CryptoService cryptoService;
     private WotRemoteService wotRemoteService;
 
     @Inject
     public MarketService(Client client, PluginSettings settings, CryptoService cryptoService, WotRemoteService wotRemoteService) {
-        super(client, settings);
-        this.cryptoService = cryptoService;
+        super(client, settings, cryptoService);
         this.wotRemoteService = wotRemoteService;
     }
 
@@ -145,90 +144,69 @@ public class MarketService extends AbstractService {
 
     public String indexRecordFromJson(String recordJson) {
 
-        try {
-            JsonNode actualObj = objectMapper.readTree(recordJson);
-            Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames());
-            if (!fieldNames.contains(Record.PROPERTY_ISSUER)
-                    || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) {
-                throw new InvalidFormatException("Invalid record JSON format. Required fields [issuer,signature]");
-            }
-            String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText();
+        JsonNode actualObj = readAndVerifyIssuerSignature(recordJson);
+        String issuer = getIssuer(actualObj);
 
-            String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText();
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing market record from issuer [%s]", issuer.substring(0, 8)));
+        }
 
-            String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "")
-                    .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), "");
+        IndexResponse response = client.prepareIndex(INDEX, RECORD_TYPE)
+                .setSource(recordJson)
+                .setRefresh(false)
+                .execute().actionGet();
 
-            if (!cryptoService.verify(recordNoSign, signature, issuer)) {
-                throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign);
-            }
+        return response.getId();
+    }
 
-            // TODO : check if issuer is a valid member
-            //wotRemoteService.getRequirments();
+    public void updateRecordFromJson(String recordJson, String id) {
 
-            if (logger.isDebugEnabled()) {
-                logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8)));
-            }
+        JsonNode actualObj = readAndVerifyIssuerSignature(recordJson);
+        String issuer = getIssuer(actualObj);
 
-        }
-        catch(IOException | JsonSyntaxException e) {
-            throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e);
-        }
+        // Check same document issuer
+        checkSameDocumentIssuer(INDEX, RECORD_TYPE, id, issuer);
 
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE)
-                .setSource(recordJson);
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Updating market record [%s] from issuer [%s]", id, issuer.substring(0, 8)));
+        }
 
-        // Execute indexBlocksFromNode
-        IndexResponse response = indexRequest
-                .setRefresh(false)
+        client.prepareUpdate(INDEX, RECORD_TYPE, id)
+                .setDoc(recordJson)
                 .execute().actionGet();
-
-        return response.getId();
     }
 
     public String indexCommentFromJson(String commentJson) {
 
-        try {
-            JsonNode actualObj = objectMapper.readTree(commentJson);
-            Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames());
-            if (!fieldNames.contains(Record.PROPERTY_ISSUER)
-                    || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) {
-                throw new InvalidFormatException(String.format("Invalid comment JSON format. Required fields [%s,%s]",
-                        Record.PROPERTY_ISSUER, Record.PROPERTY_SIGNATURE));
-            }
-            String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText();
-            String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText();
+        JsonNode actualObj = readAndVerifyIssuerSignature(commentJson);
+        String issuer = getIssuer(actualObj);
 
-            String recordNoSign = commentJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "")
-                    .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), "");
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a comment from issuer [%s]", issuer.substring(0, 8)));
+        }
 
-            if (!cryptoService.verify(recordNoSign, signature, issuer)) {
-                throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign);
-            }
+        IndexResponse response = client.prepareIndex(INDEX, RECORD_COMMENT_TYPE)
+                .setSource(commentJson)
+                .setRefresh(false)
+                .execute().actionGet();
+        return response.getId();
+    }
 
-            // TODO : check if issuer is a valid member
-            //wotRemoteService.getRequirments();
+    public void updateCommentFromJson(String commentJson, String id) {
 
-            if (logger.isDebugEnabled()) {
-                logger.debug(String.format("Indexing a comment from issuer [%s]", issuer.substring(0, 8)));
-            }
+        JsonNode actualObj = readAndVerifyIssuerSignature(commentJson);
+        String issuer = getIssuer(actualObj);
 
-        }
-        catch(IOException | JsonSyntaxException e) {
-            throw new InvalidFormatException("Invalid comment JSON: " + e.getMessage(), e);
-        }
+        // Check same document issuer
+        checkSameDocumentIssuer(INDEX, RECORD_COMMENT_TYPE, id, issuer);
 
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_COMMENT_TYPE)
-                .setSource(commentJson);
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Updating comment [%s] from issuer [%s]", id, issuer.substring(0, 8)));
+        }
 
         // Execute indexBlocksFromNode
-        IndexResponse response = indexRequest
-                .setRefresh(false)
+        client.prepareUpdate(INDEX, RECORD_COMMENT_TYPE, id)
                 .execute().actionGet();
-
-        return response.getId();
     }
 
     public void fillRecordCategories() {
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java
index 1f0a236953576772c028a6ef730abf79ba118722..b201a228bd43874bec311dc55ebb3510d1e3c53d 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java
@@ -56,14 +56,10 @@ public class MessageService extends AbstractService {
 
     private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\"";
 
-    private CryptoService cryptoService;
-    private WotRemoteService wotRemoteService;
 
     @Inject
-    public MessageService(Client client, PluginSettings settings, CryptoService cryptoService, WotRemoteService wotRemoteService) {
-        super(client, settings);
-        this.cryptoService = cryptoService;
-        this.wotRemoteService = wotRemoteService;
+    public MessageService(Client client, PluginSettings settings, CryptoService cryptoService) {
+        super(client, settings, cryptoService);
     }
 
     /**
@@ -118,41 +114,15 @@ public class MessageService extends AbstractService {
 
     public String indexRecordFromJson(String recordJson) {
 
-        try {
-            JsonNode actualObj = objectMapper.readTree(recordJson);
-            Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames());
-            if (!fieldNames.contains(MessageRecord.PROPERTY_ISSUER)
-                    || !fieldNames.contains(MessageRecord.PROPERTY_SIGNATURE)) {
-                throw new InvalidFormatException(String.format("Invalid record JSON format. Required fields [%s,%s]", MessageRecord.PROPERTY_ISSUER, MessageRecord.PROPERTY_SIGNATURE));
-            }
-            String issuer = actualObj.get(MessageRecord.PROPERTY_ISSUER).asText();
-
-            String signature = actualObj.get(MessageRecord.PROPERTY_SIGNATURE).asText();
-
-            String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, MessageRecord.PROPERTY_SIGNATURE), "");
-
-            if (!cryptoService.verify(recordNoSign, signature, issuer)) {
-                throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign);
-            }
-
-            // TODO : check if issuer is a valid member
-            //wotRemoteService.getRequirments();
-
-            if (logger.isDebugEnabled()) {
-                logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8)));
-            }
+        JsonNode actualObj = readAndVerifyIssuerSignature(recordJson);
+        String issuer = getIssuer(actualObj);
 
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8)));
         }
-        catch(IOException | JsonSyntaxException e) {
-            throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e);
-        }
-
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE)
-                .setSource(recordJson);
 
-        // Execute indexBlocksFromNode
-        IndexResponse response = indexRequest
+        IndexResponse response = client.prepareIndex(INDEX, RECORD_TYPE)
+                .setSource(recordJson)
                 .setRefresh(false)
                 .execute().actionGet();
 
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java
index c3324b964ae4d6b2ff8a909b370423331d1a2699..ceb4aa2bd832b8c4ae03d07c4aca0b9e858031fe 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java
@@ -31,7 +31,6 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import com.google.gson.JsonSyntaxException;
-import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.ArrayUtils;
 import org.duniter.core.client.model.bma.BlockchainBlock;
 import org.duniter.core.client.model.bma.BlockchainParameters;
@@ -50,8 +49,6 @@ import org.duniter.elasticsearch.exception.AccessDeniedException;
 import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
 import org.duniter.elasticsearch.exception.InvalidFormatException;
 import org.duniter.elasticsearch.exception.InvalidSignatureException;
-import org.duniter.elasticsearch.model.SearchResult;
-import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.index.IndexResponse;
@@ -59,8 +56,6 @@ import org.elasticsearch.action.search.SearchPhaseExecutionException;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchType;
-import org.elasticsearch.action.suggest.SuggestRequestBuilder;
-import org.elasticsearch.action.suggest.SuggestResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -68,15 +63,11 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHitField;
-import org.elasticsearch.search.highlight.HighlightField;
-import org.elasticsearch.search.sort.SortOrder;
-import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -89,14 +80,9 @@ public class RegistryService extends AbstractService {
     public static final String RECORD_TYPE = "record";
     public static final String RECORD_CATEGORY_TYPE = "category";
     public static final String CURRENCY_TYPE = "currency";
-    private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\"";
     private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json";
-    public static final String REGEX_WORD_SEPARATOR = "[-\\t@# ]+";
-    public static final String REGEX_SPACE = "[\\t\\n\\r ]+";
 
     private final Gson gson;
-    private WotRemoteService wotRemoteService;
-    private CryptoService cryptoService;
     private BlockchainRemoteService blockchainRemoteService;
 
     @Inject
@@ -105,10 +91,8 @@ public class RegistryService extends AbstractService {
                            WotRemoteService wotRemoteService,
                            CryptoService cryptoService,
                            BlockchainRemoteService blockchainRemoteService) {
-        super(client, settings);
+        super(client, settings, cryptoService);
         gson = GsonUtils.newBuilder().create();
-        this.wotRemoteService = wotRemoteService;
-        this.cryptoService = cryptoService;
         this.blockchainRemoteService = blockchainRemoteService;
     }
 
@@ -181,51 +165,20 @@ public class RegistryService extends AbstractService {
      */
     public String indexRecordFromJson(String recordJson) {
 
-        try {
-            ObjectMapper mapper = new ObjectMapper();
-            JsonNode actualObj = mapper.readTree(recordJson);
-            Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames());
-            if (!fieldNames.contains(Record.PROPERTY_ISSUER)
-                    || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) {
-                throw new InvalidFormatException("Invalid record JSON format. Required fields [issuer,signature]");
-            }
-            String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText();
-            String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText();
-
-            String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "")
-                    .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), "");
-
-            if (!cryptoService.verify(recordNoSign, signature, issuer)) {
-                throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign);
-            }
-
-            // TODO verify hash
-            //if (!cryptoService.verifyHash(recordNoSign, signature, issuer)) {
-            //    throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign);
-            //}
+        JsonNode actualObj = readAndVerifyIssuerSignature(recordJson);
+        String issuer = getIssuer(actualObj);
 
-            if (logger.isDebugEnabled()) {
-                logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8)));
-            }
-
-        }
-        catch(IOException | JsonSyntaxException e) {
-            throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e);
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a registry record from issuer [%s]", issuer.substring(0, 8)));
         }
 
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE)
-                .setSource(recordJson);
-
-        // Execute indexBlocksFromNode
-        IndexResponse response = indexRequest
+        IndexResponse response = client.prepareIndex(INDEX, RECORD_TYPE)
+                .setSource(recordJson)
                 .setRefresh(false)
                 .execute().actionGet();
-
         return response.getId();
     }
 
-
     public void insertRecordFromBulkFile(File bulkFile) {
 
         if (logger.isDebugEnabled()) {
@@ -442,10 +395,24 @@ public class RegistryService extends AbstractService {
                     .field("type", "geo_point")
                     .endObject()
 
-                    // avatar
-                    .startObject("avatar")
+                    // thumbnail
+                    .startObject("thumbnail")
+                    .field("type", "attachment")
+                    .startObject("fields") // src
+                    .startObject("content") // title
+                    .field("index", "no")
+                    .endObject()
+                    .startObject("title") // title
                     .field("type", "string")
-                    .field("index", "not_analyzed")
+                    .field("store", "no")
+                    .endObject()
+                    .startObject("author") // title
+                    .field("store", "no")
+                    .endObject()
+                    .startObject("content_type") // title
+                    .field("store", "yes")
+                    .endObject()
+                    .endObject()
                     .endObject()
 
                     // categories
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
index c22f32c32b5035c65adb12d10117bdb3fb7bca20..8dd1a7c9db1a4721a9af84d7c1f91975429b0d65 100644
--- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
@@ -52,6 +52,7 @@ public class ServiceModule extends AbstractModule implements Module {
         bind(MarketService.class);
         bind(BlockchainService.class);
         bind(MessageService.class);
+        bind(HistoryService.class);
 
         // Duniter Client API beans
         bindWithLocator(BlockchainRemoteService.class);
diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..5967d7fc6d63a197f718e4d8ad0634548491fbf3
--- /dev/null
+++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java
@@ -0,0 +1,246 @@
+package org.duniter.elasticsearch.service;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.duniter.core.client.service.bma.BlockchainRemoteService;
+import org.duniter.core.client.service.bma.WotRemoteService;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.exception.AccessDeniedException;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.action.update.UpdateResponse;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class UserService extends AbstractService {
+
+    public static final String INDEX = "user";
+    public static final String PROFILE_TYPE = "profile";
+    private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\"";
+
+    @Inject
+    public UserService(Client client,
+                       PluginSettings settings,
+                       CryptoService cryptoService) {
+        super(client, settings,cryptoService);
+    }
+
+    /**
+     * Create index need for blockchain registry, if need
+     */
+    public UserService createIndexIfNotExists() {
+        try {
+            if (!existsIndex(INDEX)) {
+                createIndex();
+            }
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(String.format("Error while creating index [%s]", INDEX));
+        }
+        return this;
+    }
+
+    /**
+     * Create index for registry
+     * @throws JsonProcessingException
+     */
+    public UserService createIndex() throws JsonProcessingException {
+        logger.info(String.format("Creating index [%s]", INDEX));
+
+        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
+        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
+                .put("number_of_shards", 3)
+                .put("number_of_replicas", 1)
+                //.put("analyzer", createDefaultAnalyzer())
+                .build();
+        createIndexRequestBuilder.setSettings(indexSettings);
+        createIndexRequestBuilder.addMapping(PROFILE_TYPE, createProfileType());
+        createIndexRequestBuilder.execute().actionGet();
+
+        return this;
+    }
+
+    public UserService deleteIndex() {
+        deleteIndexIfExists(INDEX);
+        return this;
+    }
+
+    public boolean existsIndex() {
+        return super.existsIndex(INDEX);
+    }
+
+    /**
+     *
+     * Index an user profile
+     * @param profileJson
+     * @return the profile id
+     */
+    public String indexProfileFromJson(String profileJson) {
+
+        JsonNode actualObj = readAndVerifyIssuerSignature(profileJson);
+        String issuer = getIssuer(actualObj);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a user profile from issuer [%s]", issuer.substring(0, 8)));
+        }
+
+        IndexResponse response = client.prepareIndex(INDEX, PROFILE_TYPE)
+                .setSource(profileJson)
+                .setRefresh(false)
+                .execute().actionGet();
+        return response.getId();
+    }
+
+    /**
+     * Update an user profile
+     * @param profileJson
+     */
+    public void updateProfileFromJson(String profileJson, String id) {
+
+        JsonNode actualObj = readAndVerifyIssuerSignature(profileJson);
+        String issuer = getIssuer(actualObj);
+
+        if (!Objects.equals(issuer, id)) {
+            throw new AccessDeniedException(String.format("Could not update this document: not issuer."));
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a user profile from issuer [%s]", issuer.substring(0, 8)));
+        }
+
+        UpdateResponse response = client.prepareUpdate(INDEX, PROFILE_TYPE, issuer)
+                .setDoc(profileJson)
+                .execute().actionGet();
+    }
+
+
+    /* -- Internal methods -- */
+
+
+    public XContentBuilder createProfileType() {
+        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
+
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(PROFILE_TYPE)
+                    .startObject("properties")
+
+                    // title
+                    .startObject("title")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // description
+                    .startObject("description")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // time
+                    .startObject("time")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // pubkey
+                    .startObject("pubkey")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // location
+                    .startObject("location")
+                    .field("type", "string")
+                    .endObject()
+
+                    // geoPoint
+                    .startObject("geoPoint")
+                    .field("type", "geo_point")
+                    .endObject()
+
+                    // avatar
+                    .startObject("avatar")
+                        .field("type", "attachment")
+                        .startObject("fields") // fields
+                            .startObject("content") // content
+                                .field("index", "no")
+                            .endObject()
+                            .startObject("title") // title
+                                .field("type", "string")
+                                .field("store", "no")
+                            .endObject()
+                                .startObject("author") // author
+                                .field("store", "no")
+                            .endObject()
+                            .startObject("content_type") // content_type
+                                .field("store", "yes")
+                            .endObject()
+                        .endObject()
+                    .endObject()
+
+                    // social networks
+                    .startObject("socials")
+                        .field("type", "nested")
+                        .field("dynamic", "false")
+                        .startObject("properties")
+                            .startObject("type") // type
+                                .field("type", "string")
+                                .field("index", "not_analyzed")
+                            .endObject()
+                            .startObject("url") // url
+                                .field("type", "string")
+                                .field("index", "not_analyzed")
+                            .endObject()
+                        .endObject()
+                    .endObject()
+
+                    // tags
+                    .startObject("tags")
+                        .field("type", "completion")
+                        .field("search_analyzer", "simple")
+                        .field("analyzer", "simple")
+                        .field("preserve_separators", "false")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, PROFILE_TYPE, ioe.getMessage()), ioe);
+        }
+    }
+
+}
diff --git a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml b/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml
index ce3e43751395bf626de86429284649abbe07f8f1..c799994ae98bc358eac840c0a87c112104bfedfa 100644
--- a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml
+++ b/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml
@@ -106,8 +106,14 @@ duniter.port: 9330
 
 duniter.string.analyzer: french
 
-duniter.indices.reload: true
+#duniter.indices.reload: true
 
 #duniter.dev.enable: true
 
-#script.groovy.sandbox.enabled: true
\ No newline at end of file
+#script.groovy.sandbox.enabled: true
+
+# Security token prefix (default: 'duniter-')
+#duniter.auth.token.prefix: duniter-
+
+# Token validity duration, in seconds (default: 600)
+duniter.auth.tokenValidityDuration: 3600  # = 1hour
\ No newline at end of file
diff --git a/ucoinj.iml b/ucoinj.iml
deleted file mode 100644
index 66b3d797e48191074a1789a5dd24dc3776d9ec53..0000000000000000000000000000000000000000
--- a/ucoinj.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5" inherit-compiler-output="false">
-    <output url="file://$MODULE_DIR$/target/classes" />
-    <output-test url="file://$MODULE_DIR$/target/test-classes" />
-    <content url="file://$MODULE_DIR$">
-      <excludeFolder url="file://$MODULE_DIR$/target" />
-    </content>
-    <orderEntry type="inheritedJdk" />
-    <orderEntry type="sourceFolder" forTests="false" />
-  </component>
-</module>
\ No newline at end of file