diff --git a/docs/generate.sh b/docs/generate.sh
old mode 100755
new mode 100644
diff --git a/docs/make.bat b/docs/make.bat
old mode 100644
new mode 100755
diff --git a/duniterpy/api/bma/blockchain.py b/duniterpy/api/bma/blockchain.py
index 604110e64f238bb6cfa43f6622bfe9afd61563cf..a76e587bec03fe57602d0c0783f1c6742e806db2 100644
--- a/duniterpy/api/bma/blockchain.py
+++ b/duniterpy/api/bma/blockchain.py
@@ -309,7 +309,7 @@ async def parameters(client: Client) -> dict:
     GET the blockchain parameters used by this node
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/parameters', schema=PARAMETERS_SCHEMA)
 
@@ -320,7 +320,7 @@ async def memberships(client: Client, search: str) -> dict:
 
     :param client: Client to connect to the api
     :param search: UID/Public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/memberships/%s' % search, schema=MEMBERSHIPS_SCHEMA)
 
@@ -331,17 +331,17 @@ async def membership(client: Client, membership_signed_raw: str) -> ClientRespon
 
     :param client: Client to connect to the api
     :param membership_signed_raw: Membership signed raw document
-    :rtype: aiohttp.ClientResponse
+    :return:
     """
     return await client.post(MODULE + '/membership', {'membership': membership_signed_raw}, rtype=RESPONSE_AIOHTTP)
 
 
 async def current(client: Client) -> dict:
     """
-    GET, return last accepted block
+    GET the last accepted block
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/current', schema=BLOCK_SCHEMA)
 
@@ -355,7 +355,7 @@ async def block(client: Client, number: int = 0, block_raw: str = None, signatur
     :param number: Block number to get
     :param block_raw: Block document to post
     :param signature: Signature of the block document issuer
-    :rtype: dict
+    :return:
     """
     # POST block
     if block_raw is not None and signature is not None:
@@ -372,7 +372,7 @@ async def blocks(client: Client, count: int, start: int) -> list:
     :param client: Client to connect to the api
     :param count: Number of blocks
     :param start: First block number
-    :rtype: list
+    :return:
     """
     assert type(count) is int
     assert type(start) is int
@@ -386,86 +386,86 @@ async def hardship(client: Client, pubkey: str) -> dict:
 
     :param client: Client to connect to the api
     :param pubkey:  Public key of the member
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/hardship/%s' % pubkey, schema=HARDSHIP_SCHEMA)
 
 
 async def newcomers(client: Client) -> dict:
     """
-    GET, return block numbers containing newcomers
+    GET the block numbers containing newcomers
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/newcomers', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def certifications(client: Client) -> dict:
     """
-    GET, return block numbers containing certifications
+    GET the block numbers containing certifications
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/certs', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def joiners(client: Client) -> dict:
     """
-    GET, return block numbers containing joiners
+    GET the block numbers containing joiners
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/joiners', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def actives(client: Client) -> dict:
     """
-    GET, return block numbers containing actives
+    GET the block numbers containing actives
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/actives', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def leavers(client: Client) -> dict:
     """
-    GET, return block numbers containing leavers
+    GET the block numbers containing leavers
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/leavers', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def excluded(client: Client) -> dict:
     """
-    GET, return block numbers containing excluded
+    GET the block numbers containing excluded
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/excluded', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def ud(client: Client) -> dict:
     """
-    GET, return block numbers containing universal dividend
+    GET the block numbers containing universal dividend
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/ud', schema=BLOCK_NUMBERS_SCHEMA)
 
 
 async def tx(client: Client) -> dict:
     """
-    GET, return block numbers containing transactions
+    GET the block numbers containing transactions
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/with/tx', schema=BLOCK_NUMBERS_SCHEMA)
diff --git a/duniterpy/api/bma/network.py b/duniterpy/api/bma/network.py
index bd91569b69b954ce2c19d0d7a6d672d5496e0fb1..6f6ad3d7ea52984f4c94b924fc2802598e2c664a 100644
--- a/duniterpy/api/bma/network.py
+++ b/duniterpy/api/bma/network.py
@@ -109,7 +109,7 @@ async def peering(client: Client) -> dict:
     GET peering information about a peer
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/peering', schema=PEERING_SCHEMA)
 
@@ -121,7 +121,7 @@ async def peers(client: Client, leaves: bool = False, leaf: str = "") -> dict:
     :param client: Client to connect to the api
     :param leaves: True if leaves should be requested
     :param leaf: True if leaf should be requested
-    :rtype: dict
+    :return:
     """
     if leaves is True:
         return await client.get(MODULE + '/peering/peers', {"leaves": "true"}, schema=PEERS_SCHEMA)
@@ -135,6 +135,6 @@ async def peer(client: Client, peer_signed_raw: str) -> ClientResponse:
 
     :param client: Client to connect to the api
     :param peer_signed_raw: Peer signed raw document
-    :rtype: ClientResponse
+    :return:
     """
     return await client.post(MODULE + '/peering/peers', {'peer': peer_signed_raw}, rtype=RESPONSE_AIOHTTP)
diff --git a/duniterpy/api/bma/node.py b/duniterpy/api/bma/node.py
index d42cd0a6f7df4f787b49516f0e72eccbbeca6d99..ba5ce83ca0a9b5271e32ae4276fac3a8ae34339b 100644
--- a/duniterpy/api/bma/node.py
+++ b/duniterpy/api/bma/node.py
@@ -29,7 +29,7 @@ async def summary(client: Client) -> dict:
     GET Duniter node version and infos
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     schema = {
         "type": "object",
diff --git a/duniterpy/api/bma/tx.py b/duniterpy/api/bma/tx.py
index 2a358b4e1ca74ee2dfcdd6a89a4b4859eb0d9f4c..9e17d49b6df5540e8f303ff7c2bb8dcd9a94d5b2 100644
--- a/duniterpy/api/bma/tx.py
+++ b/duniterpy/api/bma/tx.py
@@ -206,7 +206,7 @@ async def history(client: Client, pubkey: str) -> dict:
 
     :param client: Client to connect to the api
     :param pubkey: Public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/history/%s' % pubkey, schema=HISTORY_SCHEMA)
 
@@ -217,7 +217,7 @@ async def process(client: Client, transaction_signed_raw: str) -> ClientResponse
 
     :param client: Client to connect to the api
     :param transaction_signed_raw: Transaction signed raw document
-    :rtype: ClientResponse
+    :return:
     """
     return await client.post(MODULE + '/process', {'transaction': transaction_signed_raw}, rtype=RESPONSE_AIOHTTP)
 
@@ -228,7 +228,7 @@ async def sources(client: Client, pubkey: str) -> dict:
 
     :param client: Client to connect to the api
     :param pubkey: Public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/sources/%s' % pubkey, schema=SOURCES_SCHEMA)
 
@@ -241,7 +241,7 @@ async def blocks(client: Client, pubkey: str, start: int, end: int) -> dict:
     :param pubkey: Public key
     :param start: Start from block number
     :param end: End to block number
-    :return: dict
+    :return:
     """
     return await client.get(MODULE + '/history/%s/blocks/%s/%s' % (pubkey, start, end), schema=HISTORY_SCHEMA)
 
@@ -254,6 +254,6 @@ async def times(client: Client, pubkey: str, start: int, end: int) -> dict:
     :param pubkey: Public key
     :param start: Start from timestamp
     :param end: End to timestamp
-    :return: dict
+    :return:
     """
     return await client.get(MODULE + '/history/%s/times/%s/%s' % (pubkey, start, end), schema=HISTORY_SCHEMA)
diff --git a/duniterpy/api/bma/ud.py b/duniterpy/api/bma/ud.py
index 6522fc5da59138df0592d25640c0882e160778ff..9fed33be5f79ae3e31117abc8c100ff7d6bde443 100644
--- a/duniterpy/api/bma/ud.py
+++ b/duniterpy/api/bma/ud.py
@@ -72,6 +72,6 @@ async def history(client: Client, pubkey: str) -> dict:
 
     :param client: Client to connect to the api
     :param pubkey:  Public key of the member
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/history/%s' % pubkey, schema=UD_SCHEMA)
diff --git a/duniterpy/api/bma/wot.py b/duniterpy/api/bma/wot.py
index ffa8d8bf48f477e374a8f26d9bcb7efb8d325180..1599900384cae85e740e548c5bf9ad535100d8e2 100644
--- a/duniterpy/api/bma/wot.py
+++ b/duniterpy/api/bma/wot.py
@@ -305,7 +305,7 @@ async def add(client: Client, identity_signed_raw: str) -> ClientResponse:
 
     :param client: Client to connect to the api
     :param identity_signed_raw: Identity raw document
-    :rtype: aiohttp.ClientResponse
+    :return:
     """
     return await client.post(MODULE + '/add', {'identity': identity_signed_raw}, rtype=RESPONSE_AIOHTTP)
 
@@ -316,7 +316,7 @@ async def certify(client: Client, certification_signed_raw: str) -> ClientRespon
 
     :param client: Client to connect to the api
     :param certification_signed_raw: Certification raw document
-    :rtype: aiohttp.ClientResponse
+    :return:
     """
     return await client.post(MODULE + '/certify', {'cert': certification_signed_raw}, rtype=RESPONSE_AIOHTTP)
 
@@ -327,7 +327,7 @@ async def revoke(client: Client, revocation_signed_raw: str) -> ClientResponse:
 
     :param client: Client to connect to the api
     :param revocation_signed_raw: Certification raw document
-    :rtype: aiohttp.ClientResponse
+    :return:
     """
     return await client.post(MODULE + '/revoke', {'revocation': revocation_signed_raw}, rtype=RESPONSE_AIOHTTP)
 
@@ -338,7 +338,7 @@ async def lookup(client: Client, search: str) -> dict:
 
     :param client: Client to connect to the api
     :param search: UID or public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/lookup/%s' % search, schema=LOOKUP_SCHEMA)
 
@@ -349,7 +349,7 @@ async def certifiers_of(client: Client, search: str) -> dict:
 
     :param client: Client to connect to the api
     :param search: UID or public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/certifiers-of/%s' % search, schema=CERTIFICATIONS_SCHEMA)
 
@@ -360,7 +360,7 @@ async def certified_by(client: Client, search: str) -> dict:
 
     :param client: Client to connect to the api
     :param search: UID or public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/certified-by/%s' % search, schema=CERTIFICATIONS_SCHEMA)
 
@@ -370,7 +370,7 @@ async def members(client: Client) -> dict:
     GET list of all current members of the Web of Trust
 
     :param client: Client to connect to the api
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/members', schema=MEMBERS_SCHEMA)
 
@@ -381,6 +381,6 @@ async def requirements(client: Client, search: str) -> dict:
 
     :param client: Client to connect to the api
     :param search: UID or public key
-    :rtype: dict
+    :return:
     """
     return await client.get(MODULE + '/requirements/%s' % search, schema=REQUIREMENTS_SCHEMA)
diff --git a/duniterpy/api/bma/ws.py b/duniterpy/api/bma/ws.py
index 884bcc3311e493d6ed72ebb111f56ac239ec5e0f..be6c49849e3a068b5245d56f87c3e894c0ff311d 100644
--- a/duniterpy/api/bma/ws.py
+++ b/duniterpy/api/bma/ws.py
@@ -18,7 +18,7 @@
 # vit
 import logging
 
-from aiohttp import ClientWebSocketResponse
+from aiohttp.client import _WSRequestContextManager
 
 from duniterpy.api.bma.blockchain import BLOCK_SCHEMA
 from duniterpy.api.client import Client
@@ -55,21 +55,21 @@ WS_PEER_SCHEMA = {
 }
 
 
-def block(client: Client) -> ClientWebSocketResponse:
+def block(client: Client) -> _WSRequestContextManager:
     """
     Connect to block websocket
 
     :param client: Client to connect to the api
-    :rtype: ClientWebSocketResponse
+    :return:
     """
     return client.connect_ws(MODULE + '/block')
 
 
-def peer(client: Client) -> ClientWebSocketResponse:
+def peer(client: Client) -> _WSRequestContextManager:
     """
     Connect to peer websocket
 
     :param client: Client to connect to the api
-    :rtype: ClientWebSocketResponse
+    :return:
     """
     return client.connect_ws(MODULE + '/peer')
diff --git a/duniterpy/api/client.py b/duniterpy/api/client.py
index 9f534dd0f94ca021e1feb80a14f3698485e53810..cb6a924cfc9ff386206ccc507361289b2be97af5 100644
--- a/duniterpy/api/client.py
+++ b/duniterpy/api/client.py
@@ -7,8 +7,8 @@ import logging
 from typing import Callable, Union, Any, Optional
 
 import jsonschema
-from aiohttp import ClientResponse, ClientWebSocketResponse, ClientSession
-
+from aiohttp import ClientResponse, ClientSession
+from aiohttp.client import _WSRequestContextManager
 import duniterpy.api.endpoint as endpoint
 from .errors import DuniterError
 
@@ -106,7 +106,7 @@ class API(object):
 
         :param scheme: Scheme of the url
         :param path: Path of the url
-        :return: str
+        :return:
         """
         # remove starting slash in path if present
         path = path.lstrip('/')
@@ -129,7 +129,7 @@ class API(object):
         Requests GET wrapper in order to use API parameters.
 
         :param path: the request path
-        :rtype: ClientResponse
+        :return:
         """
         logging.debug("Request : {0}".format(self.reverse_url(self.connection_handler.http_scheme, path)))
         url = self.reverse_url(self.connection_handler.http_scheme, path)
@@ -150,7 +150,7 @@ class API(object):
         Requests POST wrapper in order to use API parameters.
 
         :param path: the request path
-        :rtype: ClientResponse
+        :return:
         """
         if 'self_' in kwargs:
             kwargs['self'] = kwargs.pop('self_')
@@ -165,7 +165,7 @@ class API(object):
         )
         return response
 
-    def connect_ws(self, path: str) -> ClientWebSocketResponse:
+    def connect_ws(self, path: str) -> _WSRequestContextManager:
         """
         Connect to a websocket in order to use API parameters
 
@@ -175,7 +175,7 @@ class API(object):
         and close the ClientWebSocketResponse in it.
 
         :param path: the url path
-        :rtype: ClientWebSocketResponse
+        :return:
         """
         url = self.reverse_url(self.connection_handler.ws_scheme, path)
         return self.connection_handler.session.ws_connect(url, proxy=self.connection_handler.proxy)
@@ -274,12 +274,12 @@ class Client:
         elif rtype == RESPONSE_JSON:
             return await response.json()
 
-    def connect_ws(self, path: str) -> ClientWebSocketResponse:
+    def connect_ws(self, path: str) -> _WSRequestContextManager:
         """
         Connect to a websocket in order to use API parameters
 
         :param path: the url path
-        :rtype: ClientWebSocketResponse
+        :return:
         """
         client = API(self.endpoint.conn_handler(self.session, self.proxy))
         return client.connect_ws(path)
diff --git a/duniterpy/api/endpoint.py b/duniterpy/api/endpoint.py
index c83abbda261cc0c431892ac70b65ef4743ce0948..aab5f5c7b50845d0662d6b2085d15ab89b5f3abe 100644
--- a/duniterpy/api/endpoint.py
+++ b/duniterpy/api/endpoint.py
@@ -95,6 +95,13 @@ class UnknownEndpoint(Endpoint):
         return doc
 
     def conn_handler(self, session: ClientSession, proxy: str = None) -> ConnectionHandler:
+        """
+        Return connection handler from session
+
+        :param session: AIOHTTP Session
+        :param proxy: Proxy server
+        :return:
+        """
         return ConnectionHandler("", "", "", 0, "", ClientSession())
 
     def __str__(self) -> str:
@@ -154,7 +161,7 @@ class BMAEndpoint(Endpoint):
         """
         Return BMAEndpoint instance from endpoint string
 
-        :param inline:
+        :param inline: Endpoint string
         :return:
         """
         m = BMAEndpoint.re_inline.match(inline)
@@ -238,7 +245,7 @@ class SecuredBMAEndpoint(BMAEndpoint):
         """
         Return SecuredBMAEndpoint instance from endpoint string
 
-        :param inline:
+        :param inline: Endpoint string
         :return:
         """
         m = SecuredBMAEndpoint.re_inline.match(inline)
@@ -300,6 +307,12 @@ class WS2PEndpoint(Endpoint):
 
     @classmethod
     def from_inline(cls: Type[WS2PEndpointType], inline: str) -> WS2PEndpointType:
+        """
+        Return WS2PEndpoint instance from endpoint string
+
+        :param inline: Endpoint string
+        :return:
+        """
         m = WS2PEndpoint.re_inline.match(inline)
         if m is None:
             raise MalformedDocumentError(WS2PEndpoint.API)
@@ -312,6 +325,11 @@ class WS2PEndpoint(Endpoint):
         return cls(ws2pid, server, port, path)
 
     def inline(self) -> str:
+        """
+        Return endpoint string
+
+        :return:
+        """
         inlined = [str(info) for info in (self.ws2pid, self.server, self.port, self.path) if info]
         return WS2PEndpoint.API + " " + " ".join(inlined)
 
@@ -319,9 +337,9 @@ class WS2PEndpoint(Endpoint):
         """
         Return connection handler instance for the endpoint
 
-        :param aiohttp.ClientSession session: AIOHTTP client session instance
-        :param str proxy: Proxy url
-        :rtype: ConnectionHandler
+        :param session: AIOHTTP client session instance
+        :param proxy: Proxy url
+        :return:
         """
         return ConnectionHandler("https", "wss", self.server, self.port, self.path, session, proxy)
 
@@ -355,6 +373,12 @@ class ESCoreEndpoint(Endpoint):
 
     @classmethod
     def from_inline(cls: Type[ESCoreEndpointType], inline: str) -> ESCoreEndpointType:
+        """
+        Return ESCoreEndpoint instance from endpoint string
+
+        :param inline: Endpoint string
+        :return:
+        """
         m = ESCoreEndpoint.re_inline.match(inline)
         if m is None:
             raise MalformedDocumentError(ESCoreEndpoint.API)
@@ -363,6 +387,11 @@ class ESCoreEndpoint(Endpoint):
         return cls(server, port)
 
     def inline(self) -> str:
+        """
+        Return endpoint string
+
+        :return:
+        """
         inlined = [str(info) for info in (self.server, self.port) if info]
         return ESCoreEndpoint.API + " " + " ".join(inlined)
 
@@ -370,9 +399,9 @@ class ESCoreEndpoint(Endpoint):
         """
         Return connection handler instance for the endpoint
 
-        :param aiohttp.ClientSession session: AIOHTTP client session instance
-        :param str proxy: Proxy url
-        :rtype: ConnectionHandler
+        :param session: AIOHTTP client session instance
+        :param proxy: Proxy url
+        :return:
         """
         return ConnectionHandler("https", "wss", self.server, self.port, "", session, proxy)
 
@@ -405,6 +434,12 @@ class ESUserEndpoint(Endpoint):
 
     @classmethod
     def from_inline(cls: Type[ESUserEndpointType], inline: str) -> ESUserEndpointType:
+        """
+        Return ESUserEndpoint instance from endpoint string
+
+        :param inline: Endpoint string
+        :return:
+        """
         m = ESUserEndpoint.re_inline.match(inline)
         if m is None:
             raise MalformedDocumentError(ESUserEndpoint.API)
@@ -413,6 +448,11 @@ class ESUserEndpoint(Endpoint):
         return cls(server, port)
 
     def inline(self) -> str:
+        """
+        Return endpoint string
+
+        :return:
+        """
         inlined = [str(info) for info in (self.server, self.port) if info]
         return ESUserEndpoint.API + " " + " ".join(inlined)
 
@@ -420,9 +460,9 @@ class ESUserEndpoint(Endpoint):
         """
         Return connection handler instance for the endpoint
 
-        :param aiohttp.ClientSession session: AIOHTTP client session instance
-        :param str proxy: Proxy url
-        :rtype: ConnectionHandler
+        :param session: AIOHTTP client session instance
+        :param proxy: Proxy url
+        :return:
         """
         return ConnectionHandler("https", "wss", self.server, self.port, "", session, proxy)
 
@@ -455,6 +495,12 @@ class ESSubscribtionEndpoint(Endpoint):
 
     @classmethod
     def from_inline(cls: Type[ESSubscribtionEndpointType], inline: str) -> ESSubscribtionEndpointType:
+        """
+        Return ESSubscribtionEndpoint instance from endpoint string
+
+        :param inline: Endpoint string
+        :return:
+        """
         m = ESSubscribtionEndpoint.re_inline.match(inline)
         if m is None:
             raise MalformedDocumentError(ESSubscribtionEndpoint.API)
@@ -463,6 +509,11 @@ class ESSubscribtionEndpoint(Endpoint):
         return cls(server, port)
 
     def inline(self) -> str:
+        """
+        Return endpoint string
+
+        :return:
+        """
         inlined = [str(info) for info in (self.server, self.port) if info]
         return ESSubscribtionEndpoint.API + " " + " ".join(inlined)
 
@@ -470,9 +521,9 @@ class ESSubscribtionEndpoint(Endpoint):
         """
         Return connection handler instance for the endpoint
 
-        :param aiohttp.ClientSession session: AIOHTTP client session instance
-        :param str proxy: Proxy url
-        :rtype: ConnectionHandler
+        :param session: AIOHTTP client session instance
+        :param proxy: Proxy url
+        :return:
         """
         return ConnectionHandler("https", "wss", self.server, self.port, "", session, proxy)
 
@@ -500,6 +551,12 @@ MANAGED_API = {
 
 
 def endpoint(value: Any) -> Any:
+    """
+    Convert a endpoint string to the corresponding Endpoint instance type
+
+    :param value: Endpoint string or subclass
+    :return:
+    """
     if issubclass(type(value), Endpoint):
         return value
     elif isinstance(value, str):
diff --git a/duniterpy/api/errors.py b/duniterpy/api/errors.py
index 6ee8d15c55373b26e68aa8f824762df1bf31beff..9738dd415637a0254fedd737d296f8bb4a6a79fd 100644
--- a/duniterpy/api/errors.py
+++ b/duniterpy/api/errors.py
@@ -1,46 +1,51 @@
-
 class DuniterError(Exception):
     """
-    duniter error
+    Handle duniter error
     """
-    def __init__(self, data):
+    def __init__(self, data: dict) -> None:
+        """
+        Init instance from Duniter data
+
+        :param data: Error informations
+        """
         super().__init__("Error code {0} - {1}".format(data["ucode"], data["message"]))
         self.ucode = data["ucode"]
         self.message = data["message"]
 
-UNKNOWN                              = 1001
-UNHANDLED                            = 1002
-SIGNATURE_DOES_NOT_MATCH             = 1003
-ALREADY_UP_TO_DATE                   = 1004
-WRONG_DOCUMENT                       = 1005
-HTTP_LIMITATION                      = 1006
 
-HTTP_PARAM_PUBKEY_REQUIRED           = 1101
-HTTP_PARAM_IDENTITY_REQUIRED         = 1102
-HTTP_PARAM_PEER_REQUIRED             = 1103
-HTTP_PARAM_BLOCK_REQUIRED            = 1104
-HTTP_PARAM_MEMBERSHIP_REQUIRED       = 1105
-HTTP_PARAM_TX_REQUIRED               = 1106
-HTTP_PARAM_SIG_REQUIRED              = 1107
-HTTP_PARAM_CERT_REQUIRED             = 1108
-HTTP_PARAM_REVOCATION_REQUIRED       = 1109
-HTTP_PARAM_CONF_REQUIRED             = 1110
+UNKNOWN = 1001
+UNHANDLED = 1002
+SIGNATURE_DOES_NOT_MATCH = 1003
+ALREADY_UP_TO_DATE = 1004
+WRONG_DOCUMENT = 1005
+HTTP_LIMITATION = 1006
+
+HTTP_PARAM_PUBKEY_REQUIRED = 1101
+HTTP_PARAM_IDENTITY_REQUIRED = 1102
+HTTP_PARAM_PEER_REQUIRED = 1103
+HTTP_PARAM_BLOCK_REQUIRED = 1104
+HTTP_PARAM_MEMBERSHIP_REQUIRED = 1105
+HTTP_PARAM_TX_REQUIRED = 1106
+HTTP_PARAM_SIG_REQUIRED = 1107
+HTTP_PARAM_CERT_REQUIRED = 1108
+HTTP_PARAM_REVOCATION_REQUIRED = 1109
+HTTP_PARAM_CONF_REQUIRED = 1110
 
-NO_MATCHING_IDENTITY                 = 2001
-UID_ALREADY_USED                     = 2002
-PUBKEY_ALREADY_USED                  = 2003
-NO_MEMBER_MATCHING_PUB_OR_UID        = 2004
-SELF_PEER_NOT_FOUND                  = 2005
-WRONG_SIGNATURE_MEMBERSHIP           = 2006
-ALREADY_RECEIVED_MEMBERSHIP          = 2007
+NO_MATCHING_IDENTITY = 2001
+UID_ALREADY_USED = 2002
+PUBKEY_ALREADY_USED = 2003
+NO_MEMBER_MATCHING_PUB_OR_UID = 2004
+SELF_PEER_NOT_FOUND = 2005
+WRONG_SIGNATURE_MEMBERSHIP = 2006
+ALREADY_RECEIVED_MEMBERSHIP = 2007
 MEMBERSHIP_A_NON_MEMBER_CANNOT_LEAVE = 2008
-NOT_A_MEMBER                         = 2009
-NO_CURRENT_BLOCK                     = 2010
-BLOCK_NOT_FOUND                      = 2011
-PEER_NOT_FOUND                       = 2012
-WRONG_UNLOCKER                       = 2013
-LOCKTIME_PREVENT                     = 2014
-SOURCE_ALREADY_CONSUMED              = 2015
-WRONG_AMOUNTS                        = 2016
-WRONG_OUTPUT_BASE                    = 2017
-CANNOT_ROOT_BLOCK_NO_MEMBERS         = 2018
\ No newline at end of file
+NOT_A_MEMBER = 2009
+NO_CURRENT_BLOCK = 2010
+BLOCK_NOT_FOUND = 2011
+PEER_NOT_FOUND = 2012
+WRONG_UNLOCKER = 2013
+LOCKTIME_PREVENT = 2014
+SOURCE_ALREADY_CONSUMED = 2015
+WRONG_AMOUNTS = 2016
+WRONG_OUTPUT_BASE = 2017
+CANNOT_ROOT_BLOCK_NO_MEMBERS = 2018
diff --git a/duniterpy/api/ws2p/network.py b/duniterpy/api/ws2p/network.py
index e8b8b1ff776120636e7c50bd2d5038aee8d56aaf..b7fa58aff2f4d19730b8527633f9d21e1b61d494 100644
--- a/duniterpy/api/ws2p/network.py
+++ b/duniterpy/api/ws2p/network.py
@@ -54,13 +54,4 @@ WS2P_HEADS_SCHEMA = {
     "required": ["heads"]
 }
 
-
-# fixme: ws2p heads support must be handled by websocket
-def heads(client: Client):
-    """
-    GET Certification data over a member
-
-    :param client: Client to connect to the api
-    :rtype: dict
-    """
-    return client.get(MODULE + '/ws2p/heads', schema=WS2P_HEADS_SCHEMA)
+# todo: support ws2p v1 api
diff --git a/duniterpy/documents/block.py b/duniterpy/documents/block.py
index 9487c495a1da6363a0bff96cadc6a47d34625fff..229c343a39ed3002f2d21919d98d258a940ff69b 100644
--- a/duniterpy/documents/block.py
+++ b/duniterpy/documents/block.py
@@ -1,6 +1,7 @@
 import base64
 import hashlib
 import re
+from typing import Union, TypeVar, Type, Optional, List
 
 from .certification import Identity, Certification, Revocation
 from .document import Document, MalformedDocumentError
@@ -8,16 +9,8 @@ from .membership import Membership
 from .transaction import Transaction
 from ..constants import PUBKEY_REGEX, BLOCK_ID_REGEX, BLOCK_HASH_REGEX
 
-
-def block_uid(value):
-    if isinstance(value, BlockUID):
-        return value
-    elif isinstance(value, str):
-        return BlockUID.from_str(value)
-    elif value is None:
-        return BlockUID.empty()
-    else:
-        raise TypeError("Cannot convert {0} to BlockUID".format(type(value)))
+# required to type hint cls in classmethod
+BlockUIDType = TypeVar('BlockUIDType', bound='BlockUID')
 
 
 class BlockUID:
@@ -28,20 +21,20 @@ class BlockUID:
                                                                                block_hash_regex=BLOCK_HASH_REGEX))
     re_hash = re.compile("({block_hash_regex})".format(block_hash_regex=BLOCK_HASH_REGEX))
 
-    @classmethod
-    def empty(cls):
-        return cls(0, Block.Empty_Hash)
-
-    def __init__(self, number, sha_hash):
+    def __init__(self, number: int, sha_hash: str) -> None:
         assert (type(number) is int)
         assert (BlockUID.re_hash.match(sha_hash) is not None)
         self.number = number
         self.sha_hash = sha_hash
 
     @classmethod
-    def from_str(cls, blockid):
+    def empty(cls: Type[BlockUIDType]) -> BlockUIDType:
+        return cls(0, Block.Empty_Hash)
+
+    @classmethod
+    def from_str(cls: Type[BlockUIDType], blockid: str) -> BlockUIDType:
         """
-        :param str blockid: The block id
+        :param blockid: The block id
         """
         data = BlockUID.re_block_uid.match(blockid)
         try:
@@ -56,31 +49,52 @@ class BlockUID:
 
         return cls(number, sha_hash)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return "{0}-{1}".format(self.number, self.sha_hash)
 
-    def __eq__(self, other):
+    def __eq__(self, other: Type[BlockUIDType]) -> bool:
         return self.number == other.number and self.sha_hash == other.sha_hash
 
-    def __lt__(self, other):
+    def __lt__(self, other: Type[BlockUIDType]) -> bool:
         return self.number < other.number
 
-    def __gt__(self, other):
+    def __gt__(self, other: Type[BlockUIDType]) -> bool:
         return self.number > other.number
 
-    def __le__(self, other):
+    def __le__(self, other: Type[BlockUIDType]) -> bool:
         return self.number <= other.number
 
-    def __ge__(self, other):
+    def __ge__(self, other: Type[BlockUIDType]) -> bool:
         return self.number >= other.number
 
-    def __hash__(self):
+    def __hash__(self) -> int:
         return hash((self.number, self.sha_hash))
 
-    def __bool__(self):
+    def __bool__(self) -> bool:
         return self != BlockUID.empty()
 
 
+def block_uid(value: Union[str, BlockUID, None]) -> BlockUID:
+    """
+    Convert value to BlockUID instance
+
+    :param value: Value to convert
+    :return:
+    """
+    if isinstance(value, BlockUID):
+        return value
+    elif isinstance(value, str):
+        return BlockUID.from_str(value)
+    elif value is None:
+        return BlockUID.empty()
+    else:
+        raise TypeError("Cannot convert {0} to BlockUID".format(type(value)))
+
+
+# required to type hint cls in classmethod
+BlockType = TypeVar('BlockType', bound='Block')
+
+
 class Block(Document):
     """
 The class Block handles Block documents.
@@ -183,47 +197,70 @@ The class Block handles Block documents.
         'Transactions': re_transactions,
         'InnerHash': re_hash,
         'Noonce': re_noonce,
-        }
     }
+                      }
 
     Empty_Hash = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
 
-    def __init__(self, version, currency, number, powmin, time,
-                 mediantime, ud, unit_base, issuer, issuers_frame, issuers_frame_var,
-                 different_issuers_count, prev_hash, prev_issuer,
-                 parameters, members_count, identities, joiners,
-                 actives, leavers, revokations, excluded, certifications,
-                 transactions, inner_hash, noonce, signature):
+    def __init__(self,
+                 version: int,
+                 currency: str,
+                 number: int,
+                 powmin: int,
+                 time: int,
+                 mediantime: int,
+                 ud: Optional[int],
+                 unit_base: int,
+                 issuer: str,
+                 issuers_frame: int,
+                 issuers_frame_var: int,
+                 different_issuers_count: int,
+                 prev_hash: str,
+                 prev_issuer: str,
+                 parameters: str,
+                 members_count: int,
+                 identities: List[Identity],
+                 joiners: List[Membership],
+                 actives: List[Membership],
+                 leavers: List[Membership],
+                 revokations: List[Revocation],
+                 excluded: List[str],
+                 certifications: List[Certification],
+                 transactions: List[Transaction],
+                 inner_hash: str,
+                 noonce: int,
+                 signature: str
+                 ) -> None:
         """
         Constructor
 
-        :param int version: duniter protocol version
-        :param str currency: the block currency
-        :param int number: the number of the block
-        :param int powmin: the powmin value of this block
-        :param int time: the timestamp of this block
-        :param int ud: the dividend amount, or None if no dividend present in this block
-        :param int unit_base: the unit_base of the dividend, or None if no dividend present in this block
-        :param str issuer: the pubkey of the issuer of the block
-        :param int issuers_frame:
-        :param int issuers_frame_var:
-        :param int different_issuers_count: the count of issuers
-        :param str prev_hash: the previous block hash
-        :param str prev_issuer: the previous block issuer
-        :param Optional[Sequence[str]] parameters: the parameters of the currency. Should only be present in block 0.
-        :param int members_count: the number of members found in this block
-        :param list[duniterpy.documents.Identity] identities: the self certifications declared in this block
-        :param list[duniterpy.documents.Membership] joiners: the joiners memberships via "IN" documents
-        :param list[duniterpy.documents.Membership] actives: renewed memberships via "IN" documents
-        :param list[duniterpy.documents.Membership] leavers: the leavers memberships via "OUT" documents
-        :param list[duniterpy.documents.Revocation] revokations: revokations
-        :param list[str] excluded: members excluded because of missing certifications
-        :param list[duniterpy.documents.Membership] actives: renewed memberships via "IN" documents
-        :param list[duniterpy.documents.Certification] certifications: certifications documents
-        :param list[duniterpy.documents.Transaction] transactions: transactions documents
-        :param str inner_hash: the block hah
-        :param int noonce: the noonce value of the block
-        :param str signature: the block signature
+        :param version: duniter protocol version
+        :param currency: the block currency
+        :param number: the number of the block
+        :param powmin: the powmin value of this block
+        :param time: the timestamp of this block
+        :param mediantime: the timestamp of the median time of this block
+        :param ud: the dividend amount, or None if no dividend present in this block
+        :param unit_base: the unit_base of the dividend, or None if no dividend present in this block
+        :param issuer: the pubkey of the issuer of the block
+        :param issuers_frame:
+        :param issuers_frame_var:
+        :param different_issuers_count: the count of issuers
+        :param prev_hash: the previous block hash
+        :param prev_issuer: the previous block issuer
+        :param parameters: the parameters of the currency. Should only be present in block 0.
+        :param members_count: the number of members found in this block
+        :param identities: the self certifications declared in this block
+        :param joiners: the joiners memberships via "IN" documents
+        :param actives: renewed memberships via "IN" documents
+        :param leavers: the leavers memberships via "OUT" documents
+        :param revokations: revokations
+        :param excluded: members excluded because of missing certifications
+        :param certifications: certifications documents
+        :param transactions: transactions documents
+        :param inner_hash: the block hah
+        :param noonce: the noonce value of the block
+        :param signature: the block signature
         """
         super().__init__(version, currency, [signature])
         documents_versions = max(max([1] + [i.version for i in identities]),
@@ -260,11 +297,11 @@ The class Block handles Block documents.
         self.noonce = noonce
 
     @property
-    def blockUID(self):
+    def blockUID(self) -> BlockUIDType:
         return BlockUID(self.number, self.proof_of_work())
 
     @classmethod
-    def from_signed_raw(cls, signed_raw):
+    def from_signed_raw(cls: Type[BlockType], signed_raw: str) -> BlockType:
         lines = signed_raw.splitlines(True)
         n = 0
 
@@ -437,7 +474,7 @@ The class Block handles Block documents.
                    actives, leavers, revoked, excluded, certifications,
                    transactions, inner_hash, noonce, signature)
 
-    def raw(self):
+    def raw(self) -> str:
         doc = """Version: {version}
 Type: Block
 Currency: {currency}
@@ -512,14 +549,14 @@ PreviousIssuer: {1}\n".format(self.prev_hash, self.prev_issuer)
 
         return doc
 
-    def proof_of_work(self):
+    def proof_of_work(self) -> str:
         doc_str = """InnerHash: {inner_hash}
 Nonce: {nonce}
 {signature}
 """.format(inner_hash=self.inner_hash, nonce=self.noonce, signature=self.signatures[0])
         return hashlib.sha256(doc_str.encode('ascii')).hexdigest().upper()
 
-    def computed_inner_hash(self):
+    def computed_inner_hash(self) -> str:
         doc = self.signed_raw()
         inner_doc = '\n'.join(doc.split('\n')[:-2]) + '\n'
         return hashlib.sha256(inner_doc.encode("ascii")).hexdigest().upper()
@@ -534,17 +571,17 @@ Nonce: {nonce}
         signing = base64.b64encode(key.signature(bytes(signed, 'ascii')))
         self.signatures = [signing.decode("ascii")]
 
-    def __eq__(self, other):
+    def __eq__(self, other: Type[BlockType]) -> bool:
         return self.blockUID == other.blockUID
 
-    def __lt__(self, other):
+    def __lt__(self, other: Type[BlockType]) -> bool:
         return self.blockUID < other.blockUID
 
-    def __gt__(self, other):
+    def __gt__(self, other: Type[BlockType]) -> bool:
         return self.blockUID > other.blockUID
 
-    def __le__(self, other):
+    def __le__(self, other: Type[BlockType]) -> bool:
         return self.blockUID <= other.blockUID
 
-    def __ge__(self, other):
+    def __ge__(self, other: Type[BlockType]) -> bool:
         return self.blockUID >= other.blockUID
diff --git a/duniterpy/documents/ws2p/heads.py b/duniterpy/documents/ws2p/heads.py
index 0483a74b390a7516622f1222c2b72ba5f7d611d5..1f63d90b0e408c71cbb39606d16b3deb064a670d 100644
--- a/duniterpy/documents/ws2p/heads.py
+++ b/duniterpy/documents/ws2p/heads.py
@@ -2,8 +2,8 @@ import re
 
 import attr
 
-from ..document import MalformedDocumentError
 from ..block import BlockUID
+from ..document import MalformedDocumentError
 from ...constants import WS2P_PUBLIC_PREFIX_REGEX, WS2P_PRIVATE_PREFIX_REGEX, WS2P_HEAD_REGEX, \
     PUBKEY_REGEX, SIGNATURE_REGEX, WS2PID_REGEX, BLOCK_UID_REGEX
 
@@ -18,21 +18,15 @@ class API:
         ws2p_public=WS2P_PUBLIC_PREFIX_REGEX))
 
     @classmethod
-    def from_inline(cls, inline):
+    def from_inline(cls, inline: str):
         data = API.re_inline.match(inline)
-        if data.group(1):
-            private = data.group(1)
-        else:
-            private = ""
-
-        if data.group(2):
-            public = data.group(2)
-        else:
-            public = ""
-
+        if data is None:
+            raise MalformedDocumentError("WS2P API Document")
+        private = data.group(1)
+        public = data.group(2)
         return cls(private, public)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return "WS2P" + self.private + self.public
 
 
@@ -43,19 +37,18 @@ class Head:
     re_inline = re.compile(WS2P_HEAD_REGEX)
 
     @classmethod
-    def from_inline(cls, inline):
+    def from_inline(cls, inline: str):
         try:
             data = Head.re_inline.match(inline)
+            if data is None:
+                raise MalformedDocumentError("Head")
             head = data.group(0).split(':')
-            if len(head) == 2:
-                version = int(head[1])
-            else:
-                version = 0
+            version = int(head[1]) if len(head) == 2 else 0
             return cls(version)
         except AttributeError:
             raise MalformedDocumentError("Head")
 
-    def __str__(self):
+    def __str__(self) -> str:
         return "HEAD" if self.version == 0 else "HEAD:{}".format(str(self.version))
 
 
@@ -81,9 +74,11 @@ class HeadV0:
     re_signature = re.compile(SIGNATURE_REGEX)
 
     @classmethod
-    def from_inline(cls, inline, signature):
+    def from_inline(cls, inline: str, signature: str):
         try:
             data = HeadV0.re_inline.match(inline)
+            if data is None:
+                raise MalformedDocumentError("HeadV0")
             api = API.from_inline(data.group(1))
             head = Head.from_inline(data.group(2))
             pubkey = data.group(3)
@@ -93,7 +88,7 @@ class HeadV0:
         except AttributeError:
             raise MalformedDocumentError("HeadV0")
 
-    def inline(self):
+    def inline(self) -> str:
         values = (str(v) for v in attr.astuple(self, recurse=False,
                                                filter=attr.filters.exclude(attr.fields(HeadV0).signature)))
         return ":".join(values)
@@ -114,10 +109,12 @@ class HeadV1:
         pow_prefix="[0-9]+"))
 
     @classmethod
-    def from_inline(cls, inline, signature):
+    def from_inline(cls, inline: str, signature: str):
         try:
             v0, offload = HeadV0.from_inline(inline, signature)
             data = HeadV1.re_inline.match(offload)
+            if data is None:
+                raise MalformedDocumentError("HeadV1")
             ws2pid = data.group(1)
             software = data.group(2)
             software_version = data.group(3)
@@ -127,20 +124,20 @@ class HeadV1:
         except AttributeError:
             raise MalformedDocumentError("HeadV1")
 
-    def inline(self):
+    def inline(self) -> str:
         values = [str(v) for v in attr.astuple(self, True, filter=attr.filters.exclude(attr.fields(HeadV1).v0))]
         return self.v0.inline() + ":" + ":".join(values)
 
     @property
-    def pubkey(self):
+    def pubkey(self) -> str:
         return self.v0.pubkey
 
     @property
-    def signature(self):
+    def signature(self) -> str:
         return self.v0.signature
 
     @property
-    def blockstamp(self):
+    def blockstamp(self) -> BlockUID:
         return self.v0.blockstamp
 
 
@@ -155,28 +152,30 @@ class HeadV2:
         free_mirror_room="[0-9]+"))
 
     @classmethod
-    def from_inline(cls, inline, signature):
+    def from_inline(cls, inline: str, signature: str):
         try:
             v1, offload = HeadV1.from_inline(inline, signature)
             data = HeadV2.re_inline.match(offload)
+            if data is None:
+                raise MalformedDocumentError("HeadV2")
             free_member_room = int(data.group(1))
             free_mirror_room = int(data.group(2))
             return cls(v1, free_member_room, free_mirror_room), ""
         except AttributeError:
             raise MalformedDocumentError("HeadV2")
 
-    def inline(self):
+    def inline(self) -> str:
         values = (str(v) for v in attr.astuple(self, True, filter=attr.filters.exclude(attr.fields(HeadV2).v1)))
         return self.v1.inline() + ":" + ":".join(values)
 
     @property
-    def pubkey(self):
+    def pubkey(self) -> str:
         return self.v1.pubkey
 
     @property
-    def signature(self):
+    def signature(self) -> str:
         return self.v1.signature
 
     @property
-    def blockstamp(self):
+    def blockstamp(self) -> BlockUID:
         return self.v1.blockstamp
diff --git a/release.sh b/release.sh
old mode 100755
new mode 100644