diff --git a/duniterpy/api/client.py b/duniterpy/api/client.py
index 4960d961fcb218c26c49708898873088ce6a68d9..92518245c7a2d4e103367a44916a2146112e5b5a 100644
--- a/duniterpy/api/client.py
+++ b/duniterpy/api/client.py
@@ -4,7 +4,7 @@
 # vit
 import json
 import logging
-from typing import Callable, Union, Any, Optional
+from typing import Callable, Union, Any, Optional, Dict
 
 import jsonschema
 from aiohttp import ClientResponse, ClientSession
@@ -44,7 +44,7 @@ def parse_text(text: str, schema: dict) -> Any:
     return data
 
 
-def parse_error(text: str) -> Any:
+def parse_error(text: str) -> dict:
     """
     Validate and parse the BMA answer from websocket
 
@@ -120,7 +120,7 @@ class API:
 
         return url + path
 
-    async def requests_get(self, path: str, **kwargs) -> ClientResponse:
+    async def requests_get(self, path: str, **kwargs: Any) -> ClientResponse:
         """
         Requests GET wrapper in order to use API parameters.
 
@@ -150,7 +150,7 @@ class API:
 
         return response
 
-    async def requests_post(self, path: str, **kwargs) -> ClientResponse:
+    async def requests_post(self, path: str, **kwargs: Any) -> ClientResponse:
         """
         Requests POST wrapper in order to use API parameters.
 
@@ -162,12 +162,53 @@ class API:
 
         logging.debug("POST : %s", kwargs)
         response = await self.connection_handler.session.post(
-            self.reverse_url(self.connection_handler.http_scheme, path),
+            url,
             data=kwargs,
             headers=self.headers,
             proxy=self.connection_handler.proxy,
             timeout=15,
         )
+
+        if response.status != 200:
+            try:
+                error_data = parse_error(await response.text())
+                raise DuniterError(error_data)
+            except (TypeError, jsonschema.ValidationError):
+                raise ValueError('status code != 200 => %d (%s)' % (response.status, (await response.text())))
+
+        return response
+
+    async def requests(self, method: str = 'GET', path: str = '', data: Optional[dict] = None,
+                       _json: Optional[dict] = None) -> aiohttp.ClientResponse:
+        """
+        Generic requests wrapper on aiohttp
+
+        :param method: the request http method
+        :param path: the path added to endpoint
+        :param data: data for form POST request
+        :param _json: json for json POST request
+        :rtype: aiohttp.ClientResponse
+        """
+        url = self.reverse_url(self.connection_handler.http_scheme, path)
+
+        if data is not None:
+            logging.debug("{0} : {1}, data={2}".format(method, url, data))
+        elif _json is not None:
+            logging.debug("{0} : {1}, json={2}".format(method, url, _json))
+            # http header to send json body
+            self.headers['Content-Type'] = 'application/json; charset=utf-8'
+        else:
+            logging.debug("{0} : {1}".format(method, url))
+
+        response = await self.connection_handler.session.request(
+            method,
+            url,
+            data=data,
+            json=_json,
+            headers=self.headers,
+            proxy=self.connection_handler.proxy,
+            timeout=15
+        )
         return response
 
     def connect_ws(self, path: str) -> _WSRequestContextManager:
@@ -193,18 +234,14 @@ class Client:
     Main class to create an API client
     """
 
-    def __init__(
-        self,
-        _endpoint: Union[str, endpoint.Endpoint],
-        session: ClientSession = None,
-        proxy: str = None,
-    ) -> None:
+    def __init__(self, _endpoint: Union[str, endpoint.Endpoint], session: Optional[ClientSession] = None,
+                 proxy: Optional[str] = None) -> None:
         """
         Init Client instance
 
         :param _endpoint: Endpoint string in duniter format
         :param session: Aiohttp client session (optional, default None)
-        :param proxy: Proxy server as hostname:port
+        :param proxy: Proxy server as hostname:port (optional, default None)
         """
         if isinstance(_endpoint, str):
             # Endpoint Protocol detection
@@ -225,19 +262,14 @@ class Client:
             self.session = session
         self.proxy = proxy
 
-    async def get(
-        self,
-        url_path: str,
-        params: dict = None,
-        rtype: str = RESPONSE_JSON,
-        schema: dict = None,
-    ) -> Any:
+    async def get(self, url_path: str, params: Optional[dict] = None, rtype: str = RESPONSE_JSON,
+                  schema: Optional[dict] = None) -> Any:
         """
-        GET request on self.endpoint + url_path
+        GET request on endpoint host + url_path
 
         :param url_path: Url encoded path following the endpoint
-        :param params: Url query string parameters dictionary
-        :param rtype: Response type
+        :param params: Url query string parameters dictionary (optional, default None)
+        :param rtype: Response type (optional, default RESPONSE_JSON)
         :param schema: Json Schema to validate response (optional, default None)
         :return:
         """
@@ -263,19 +295,14 @@ class Client:
 
         return result
 
-    async def post(
-        self,
-        url_path: str,
-        params: dict = None,
-        rtype: str = RESPONSE_JSON,
-        schema: dict = None,
-    ) -> Any:
+    async def post(self, url_path: str, params: Optional[dict] = None, rtype: str = RESPONSE_JSON,
+                   schema: Optional[dict] = None) -> Any:
         """
-        POST request on self.endpoint + url_path
+        POST request on endpoint host + url_path
 
         :param url_path: Url encoded path following the endpoint
-        :param params: Url query string parameters dictionary
-        :param rtype: Response type
+        :param params: Url query string parameters dictionary (optional, default None)
+        :param rtype: Response type (optional, default RESPONSE_JSON)
         :param schema: Json Schema to validate response (optional, default None)
         :return:
         """
@@ -287,6 +314,42 @@ class Client:
         # get aiohttp response
         response = await client.requests_post(url_path, **params)
 
+        # if schema supplied...
+        if schema is not None:
+            # validate response
+            await parse_response(response, schema)
+
+        # return the chosen type
+        if rtype == RESPONSE_AIOHTTP:
+            return response
+        elif rtype == RESPONSE_TEXT:
+            return await response.text()
+        elif rtype == RESPONSE_JSON:
+            return await response.json()
+
+    async def query(self, query: str, variables: Optional[dict] = None, rtype: str = RESPONSE_JSON,
+                    schema: Optional[dict] = None) -> Any:
+        """
+        GraphQL query or mutation request on endpoint
+
+        :param query: GraphQL query string
+        :param variables: Variables for the query (optional, default None)
+        :param rtype: Response type (optional, default RESPONSE_JSON)
+        :param schema: Json Schema to validate response (optional, default None)
+        :return:
+        """
+        payload = {
+            'query': query
+        }  # type: Dict[str, Union[str, dict]]
+
+        if variables is not None:
+            payload['variables'] = variables
+
+        client = API(self.endpoint.conn_handler(self.session, self.proxy))
+
+        # get aiohttp response
+        response = await client.requests('POST', _json=payload)
+
         # if schema supplied...
         if schema is not None:
             # validate response
diff --git a/examples/request_swapi.py b/examples/request_swapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..4322ac6e23358514af41665a9f2cf9cefdeb19f4
--- /dev/null
+++ b/examples/request_swapi.py
@@ -0,0 +1,38 @@
+import asyncio
+
+from duniterpy.api.client import Client
+
+# CONFIG #######################################
+
+# You can either use a complete defined endpoint : [NAME_OF_THE_API] [DOMAIN] [IPv4] [IPv6] [PORT]
+# or the simple definition : [NAME_OF_THE_API] [DOMAIN] [PORT]
+# Here we use the secure BASIC_MERKLED_API (BMAS)
+SWAPI_ENDPOINT = "BMAS swapi.graph.cool 443"
+
+
+################################################
+
+
+async def main():
+    client = Client(SWAPI_ENDPOINT)
+
+    query = """query {
+       allFilms {
+        title,
+        characters {
+          name
+        }
+      }
+     }
+    """
+
+    response = await client.query(query)
+    print(response)
+
+    # Close client aiohttp session
+    await client.close()
+
+
+# Latest duniter-python-api is asynchronous and you have to use asyncio, an asyncio loop and a "as" on the data.
+# ( https://docs.python.org/3/library/asyncio.html )
+asyncio.get_event_loop().run_until_complete(main())