diff --git a/conf/requires/config.js b/conf/requires/config.js
index ff02a5060239f3c9bb4d0259004ef3ec350e3792..b0a1e942349940d7a023e69735488e3bd7bab04d 100644
--- a/conf/requires/config.js
+++ b/conf/requires/config.js
@@ -57,6 +57,7 @@ var oApplicationFiles = {
             'modules/vmap/javascript/externs/html2canvas/html2canvas.js',
             'modules/vmap/javascript/externs/jspdf/jspdf.debug.js',
             // Studio
+            'javascript/externs/jmespath/jmespath.min.js',
             'javascript/externs/bootbox/bootbox.min.js',
             'javascript/externs/codemirror/codemirror.min.js',
             'javascript/externs/codemirror/htmlmixed.js',
@@ -87,8 +88,8 @@ var oApplicationFiles = {
             'javascript/externs/viewer/viewer.min.js',
             'javascript/externs/alasql/alasql.min.js',
             'javascript/externs/alasql/xlsx.core.min.js'
-            
+
         ]
     },
     'vitisModuleDependencies': ['vmap', 'ui.codemirror', 'ui.tinymce', 'vfb', 'ui.grid.draggable-rows' ,'ui.grid.exporter']
-};
\ No newline at end of file
+};
diff --git a/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js b/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js
index 581ea7fd7ba8f4fdfc9cb6a28069485242482abb..a2701448c3261af9e41f6e331b0212e10612d3a5 100644
--- a/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js
+++ b/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js
@@ -514,10 +514,14 @@ formReader.formReaderController.prototype.extractFormDefinitionInfos = function
                             if (goog.isDef(elem['datasource']['datasource_id'])) {
                                 if (goog.isDef(oDataSources)) {
                                     if (goog.isDef(oDataSources[elem['datasource']['datasource_id']])) {
-                                        // Datasource web_service ?
+                                        // Datasource web_service
                                         if (oDataSources[elem['datasource']['datasource_id']]['type'] === 'web_service') {
                                             oWebService = this_.extractElemWebService(elem, oDataSources);
                                         }
+                                        // Datasource external_API
+                                        if (oDataSources[elem['datasource']['datasource_id']]['type'] === 'external_API') {
+                                            oWebService = angular.copy(oDataSources[elem['datasource']['datasource_id']]);
+                                        }
                                         // Datasource object
                                         else if (oDataSources[elem['datasource']['datasource_id']]['type'] === 'object') {
                                             aOptions = this_.extractElemOptions(elem, oDataSources);
diff --git a/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js b/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js
index 613652f9c35ccf385e3b72c1c26432d8e0e8a09f..f174cfef86ee19958d0c70f808509c9881e49bae 100644
--- a/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js
+++ b/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js
@@ -158,8 +158,8 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time
                 var j, oNotSelectedOptions, oSelectedOptions, sFromSelectName = "", sToSelectName;
 
                 // Clés label / valeur du web service pour les <options>.
-                var sLabelKey = oWebService["label_key"];
-                var sValueKey = oWebService["id_key"];
+                var sLabelKey = goog.isDefAndNotNull(oWebService["label_key"]) ? oWebService["label_key"] : 'label';
+                var sValueKey = goog.isDefAndNotNull(oWebService["id_key"]) ? oWebService["id_key"] : 'value';
 
                 // Id des <options> à garder.
                 var aSelectedOptions = [];
@@ -429,8 +429,9 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time
 
                 }
             }
-            // Charge la liste du menu déroulant.
-            ajaxRequest({
+
+            // Options requête
+            var oAjaxOptions = {
                 "method": "POST",
                 "url": oProperties["web_server_name"] + '/' + oProperties["services_alias"] + '/' + oWebService["ressource_id"],
                 "data": oParams,
@@ -438,7 +439,23 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time
                 "headers": {
                     'X-HTTP-Method-Override': 'GET',
                     'Accept': 'application/x-vm-json'
-                },
+                }
+            }
+            if (oWebService['type'] === 'external_API') {
+                oAjaxOptions = {
+                    "method": "GET",
+                    "url": oWebService['api_url'],
+                    "scope": $rootScope,
+                }
+            }
+
+            // Charge la liste du menu déroulant.
+            ajaxRequest({
+                "method": oAjaxOptions['method'],
+                "url": oAjaxOptions['url'],
+                "data": oAjaxOptions['data'],
+                "scope": oAjaxOptions['scope'],
+                "headers": oAjaxOptions['headers'],
                 "success": function (response) {
                     // Remet le filtre origiel
                     if (goog.isDefAndNotNull(oWebService['parameters']))
@@ -446,21 +463,47 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time
 
                     // Récupération de la donnée
                     var data = response['data'];
-                    data['data'] = goog.isDef(data['data']) ? data['data'] : [];
 
                     // Met à jour la liste des enregistrements.
-                    if (data["status"] === 1) {
+                    if (oWebService['type'] === 'external_API' &&
+                        goog.isDefAndNotNull(oWebService['api_key']) &&
+                        goog.isDefAndNotNull(oWebService['api_value'])) {
+
+                        if (!goog.isDefAndNotNull(jmespath)) {
+                            console.error('jmespath not defined');
+                        }
 
-                        // Extraction des données.
                         var aData;
-                        if (goog.isDefAndNotNull(oWebService['subObject'])) {
-                            aData = [];
-                            for (var i = 0; i < data['data'].length; i++) {
-                                if (goog.isDefAndNotNull(data['data'][i][oWebService['subObject']]))
-                                    aData.push(data['data'][i][oWebService['subObject']]);
+                        var aKeys = jmespath.search(data, oWebService['api_key']);
+                        var aValues = jmespath.search(data, oWebService['api_value']);
+                        if (goog.isArray(aKeys) && goog.isArray(aValues)) {
+                            if (aKeys.length === aValues.length) {
+                                aData = [];
+                                for (var i = 0; i < aKeys.length; i++) {
+                                    aData.push({
+                                        'value': aKeys[i],
+                                        'label': aValues[i]
+                                    });
+                                }
+                            } else {
+                                conole.error('Values number different than key\'s');
+                            }
+                        }
+                    } else {
+                        data['data'] = goog.isDef(data['data']) ? data['data'] : [];
+                        if (data["status"] === 1) {
+
+                            // Extraction des données.
+                            var aData;
+                            if (goog.isDefAndNotNull(oWebService['subObject'])) {
+                                aData = [];
+                                for (var i = 0; i < data['data'].length; i++) {
+                                    if (goog.isDefAndNotNull(data['data'][i][oWebService['subObject']]))
+                                        aData.push(data['data'][i][oWebService['subObject']]);
+                                }
+                            } else {
+                                aData = goog.isDef(data['data']) ? data['data'] : [];
                             }
-                        } else {
-                            aData = goog.isDef(data['data']) ? data['data'] : [];
                         }
                     }
 
diff --git a/src/vitis/client/javascript/externs/jmespath/jmespath.js b/src/vitis/client/javascript/externs/jmespath/jmespath.js
new file mode 100644
index 0000000000000000000000000000000000000000..f59e8e4b997510f4a7b420d7c2689a2e7c2de84f
--- /dev/null
+++ b/src/vitis/client/javascript/externs/jmespath/jmespath.js
@@ -0,0 +1,1667 @@
+(function(exports) {
+  "use strict";
+
+  function isArray(obj) {
+    if (obj !== null) {
+      return Object.prototype.toString.call(obj) === "[object Array]";
+    } else {
+      return false;
+    }
+  }
+
+  function isObject(obj) {
+    if (obj !== null) {
+      return Object.prototype.toString.call(obj) === "[object Object]";
+    } else {
+      return false;
+    }
+  }
+
+  function strictDeepEqual(first, second) {
+    // Check the scalar case first.
+    if (first === second) {
+      return true;
+    }
+
+    // Check if they are the same type.
+    var firstType = Object.prototype.toString.call(first);
+    if (firstType !== Object.prototype.toString.call(second)) {
+      return false;
+    }
+    // We know that first and second have the same type so we can just check the
+    // first type from now on.
+    if (isArray(first) === true) {
+      // Short circuit if they're not the same length;
+      if (first.length !== second.length) {
+        return false;
+      }
+      for (var i = 0; i < first.length; i++) {
+        if (strictDeepEqual(first[i], second[i]) === false) {
+          return false;
+        }
+      }
+      return true;
+    }
+    if (isObject(first) === true) {
+      // An object is equal if it has the same key/value pairs.
+      var keysSeen = {};
+      for (var key in first) {
+        if (hasOwnProperty.call(first, key)) {
+          if (strictDeepEqual(first[key], second[key]) === false) {
+            return false;
+          }
+          keysSeen[key] = true;
+        }
+      }
+      // Now check that there aren't any keys in second that weren't
+      // in first.
+      for (var key2 in second) {
+        if (hasOwnProperty.call(second, key2)) {
+          if (keysSeen[key2] !== true) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+    return false;
+  }
+
+  function isFalse(obj) {
+    // From the spec:
+    // A false value corresponds to the following values:
+    // Empty list
+    // Empty object
+    // Empty string
+    // False boolean
+    // null value
+
+    // First check the scalar values.
+    if (obj === "" || obj === false || obj === null) {
+        return true;
+    } else if (isArray(obj) && obj.length === 0) {
+        // Check for an empty array.
+        return true;
+    } else if (isObject(obj)) {
+        // Check for an empty object.
+        for (var key in obj) {
+            // If there are any keys, then
+            // the object is not empty so the object
+            // is not false.
+            if (obj.hasOwnProperty(key)) {
+              return false;
+            }
+        }
+        return true;
+    } else {
+        return false;
+    }
+  }
+
+  function objValues(obj) {
+    var keys = Object.keys(obj);
+    var values = [];
+    for (var i = 0; i < keys.length; i++) {
+      values.push(obj[keys[i]]);
+    }
+    return values;
+  }
+
+  function merge(a, b) {
+      var merged = {};
+      for (var key in a) {
+          merged[key] = a[key];
+      }
+      for (var key2 in b) {
+          merged[key2] = b[key2];
+      }
+      return merged;
+  }
+
+  var trimLeft;
+  if (typeof String.prototype.trimLeft === "function") {
+    trimLeft = function(str) {
+      return str.trimLeft();
+    };
+  } else {
+    trimLeft = function(str) {
+      return str.match(/^\s*(.*)/)[1];
+    };
+  }
+
+  // Type constants used to define functions.
+  var TYPE_NUMBER = 0;
+  var TYPE_ANY = 1;
+  var TYPE_STRING = 2;
+  var TYPE_ARRAY = 3;
+  var TYPE_OBJECT = 4;
+  var TYPE_BOOLEAN = 5;
+  var TYPE_EXPREF = 6;
+  var TYPE_NULL = 7;
+  var TYPE_ARRAY_NUMBER = 8;
+  var TYPE_ARRAY_STRING = 9;
+
+  var TOK_EOF = "EOF";
+  var TOK_UNQUOTEDIDENTIFIER = "UnquotedIdentifier";
+  var TOK_QUOTEDIDENTIFIER = "QuotedIdentifier";
+  var TOK_RBRACKET = "Rbracket";
+  var TOK_RPAREN = "Rparen";
+  var TOK_COMMA = "Comma";
+  var TOK_COLON = "Colon";
+  var TOK_RBRACE = "Rbrace";
+  var TOK_NUMBER = "Number";
+  var TOK_CURRENT = "Current";
+  var TOK_EXPREF = "Expref";
+  var TOK_PIPE = "Pipe";
+  var TOK_OR = "Or";
+  var TOK_AND = "And";
+  var TOK_EQ = "EQ";
+  var TOK_GT = "GT";
+  var TOK_LT = "LT";
+  var TOK_GTE = "GTE";
+  var TOK_LTE = "LTE";
+  var TOK_NE = "NE";
+  var TOK_FLATTEN = "Flatten";
+  var TOK_STAR = "Star";
+  var TOK_FILTER = "Filter";
+  var TOK_DOT = "Dot";
+  var TOK_NOT = "Not";
+  var TOK_LBRACE = "Lbrace";
+  var TOK_LBRACKET = "Lbracket";
+  var TOK_LPAREN= "Lparen";
+  var TOK_LITERAL= "Literal";
+
+  // The "&", "[", "<", ">" tokens
+  // are not in basicToken because
+  // there are two token variants
+  // ("&&", "[?", "<=", ">=").  This is specially handled
+  // below.
+
+  var basicTokens = {
+    ".": TOK_DOT,
+    "*": TOK_STAR,
+    ",": TOK_COMMA,
+    ":": TOK_COLON,
+    "{": TOK_LBRACE,
+    "}": TOK_RBRACE,
+    "]": TOK_RBRACKET,
+    "(": TOK_LPAREN,
+    ")": TOK_RPAREN,
+    "@": TOK_CURRENT
+  };
+
+  var operatorStartToken = {
+      "<": true,
+      ">": true,
+      "=": true,
+      "!": true
+  };
+
+  var skipChars = {
+      " ": true,
+      "\t": true,
+      "\n": true
+  };
+
+
+  function isAlpha(ch) {
+      return (ch >= "a" && ch <= "z") ||
+             (ch >= "A" && ch <= "Z") ||
+             ch === "_";
+  }
+
+  function isNum(ch) {
+      return (ch >= "0" && ch <= "9") ||
+             ch === "-";
+  }
+  function isAlphaNum(ch) {
+      return (ch >= "a" && ch <= "z") ||
+             (ch >= "A" && ch <= "Z") ||
+             (ch >= "0" && ch <= "9") ||
+             ch === "_";
+  }
+
+  function Lexer() {
+  }
+  Lexer.prototype = {
+      tokenize: function(stream) {
+          var tokens = [];
+          this._current = 0;
+          var start;
+          var identifier;
+          var token;
+          while (this._current < stream.length) {
+              if (isAlpha(stream[this._current])) {
+                  start = this._current;
+                  identifier = this._consumeUnquotedIdentifier(stream);
+                  tokens.push({type: TOK_UNQUOTEDIDENTIFIER,
+                               value: identifier,
+                               start: start});
+              } else if (basicTokens[stream[this._current]] !== undefined) {
+                  tokens.push({type: basicTokens[stream[this._current]],
+                              value: stream[this._current],
+                              start: this._current});
+                  this._current++;
+              } else if (isNum(stream[this._current])) {
+                  token = this._consumeNumber(stream);
+                  tokens.push(token);
+              } else if (stream[this._current] === "[") {
+                  // No need to increment this._current.  This happens
+                  // in _consumeLBracket
+                  token = this._consumeLBracket(stream);
+                  tokens.push(token);
+              } else if (stream[this._current] === "\"") {
+                  start = this._current;
+                  identifier = this._consumeQuotedIdentifier(stream);
+                  tokens.push({type: TOK_QUOTEDIDENTIFIER,
+                               value: identifier,
+                               start: start});
+              } else if (stream[this._current] === "'") {
+                  start = this._current;
+                  identifier = this._consumeRawStringLiteral(stream);
+                  tokens.push({type: TOK_LITERAL,
+                               value: identifier,
+                               start: start});
+              } else if (stream[this._current] === "`") {
+                  start = this._current;
+                  var literal = this._consumeLiteral(stream);
+                  tokens.push({type: TOK_LITERAL,
+                               value: literal,
+                               start: start});
+              } else if (operatorStartToken[stream[this._current]] !== undefined) {
+                  tokens.push(this._consumeOperator(stream));
+              } else if (skipChars[stream[this._current]] !== undefined) {
+                  // Ignore whitespace.
+                  this._current++;
+              } else if (stream[this._current] === "&") {
+                  start = this._current;
+                  this._current++;
+                  if (stream[this._current] === "&") {
+                      this._current++;
+                      tokens.push({type: TOK_AND, value: "&&", start: start});
+                  } else {
+                      tokens.push({type: TOK_EXPREF, value: "&", start: start});
+                  }
+              } else if (stream[this._current] === "|") {
+                  start = this._current;
+                  this._current++;
+                  if (stream[this._current] === "|") {
+                      this._current++;
+                      tokens.push({type: TOK_OR, value: "||", start: start});
+                  } else {
+                      tokens.push({type: TOK_PIPE, value: "|", start: start});
+                  }
+              } else {
+                  var error = new Error("Unknown character:" + stream[this._current]);
+                  error.name = "LexerError";
+                  throw error;
+              }
+          }
+          return tokens;
+      },
+
+      _consumeUnquotedIdentifier: function(stream) {
+          var start = this._current;
+          this._current++;
+          while (this._current < stream.length && isAlphaNum(stream[this._current])) {
+              this._current++;
+          }
+          return stream.slice(start, this._current);
+      },
+
+      _consumeQuotedIdentifier: function(stream) {
+          var start = this._current;
+          this._current++;
+          var maxLength = stream.length;
+          while (stream[this._current] !== "\"" && this._current < maxLength) {
+              // You can escape a double quote and you can escape an escape.
+              var current = this._current;
+              if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
+                                               stream[current + 1] === "\"")) {
+                  current += 2;
+              } else {
+                  current++;
+              }
+              this._current = current;
+          }
+          this._current++;
+          return JSON.parse(stream.slice(start, this._current));
+      },
+
+      _consumeRawStringLiteral: function(stream) {
+          var start = this._current;
+          this._current++;
+          var maxLength = stream.length;
+          while (stream[this._current] !== "'" && this._current < maxLength) {
+              // You can escape a single quote and you can escape an escape.
+              var current = this._current;
+              if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
+                                               stream[current + 1] === "'")) {
+                  current += 2;
+              } else {
+                  current++;
+              }
+              this._current = current;
+          }
+          this._current++;
+          var literal = stream.slice(start + 1, this._current - 1);
+          return literal.replace("\\'", "'");
+      },
+
+      _consumeNumber: function(stream) {
+          var start = this._current;
+          this._current++;
+          var maxLength = stream.length;
+          while (isNum(stream[this._current]) && this._current < maxLength) {
+              this._current++;
+          }
+          var value = parseInt(stream.slice(start, this._current));
+          return {type: TOK_NUMBER, value: value, start: start};
+      },
+
+      _consumeLBracket: function(stream) {
+          var start = this._current;
+          this._current++;
+          if (stream[this._current] === "?") {
+              this._current++;
+              return {type: TOK_FILTER, value: "[?", start: start};
+          } else if (stream[this._current] === "]") {
+              this._current++;
+              return {type: TOK_FLATTEN, value: "[]", start: start};
+          } else {
+              return {type: TOK_LBRACKET, value: "[", start: start};
+          }
+      },
+
+      _consumeOperator: function(stream) {
+          var start = this._current;
+          var startingChar = stream[start];
+          this._current++;
+          if (startingChar === "!") {
+              if (stream[this._current] === "=") {
+                  this._current++;
+                  return {type: TOK_NE, value: "!=", start: start};
+              } else {
+                return {type: TOK_NOT, value: "!", start: start};
+              }
+          } else if (startingChar === "<") {
+              if (stream[this._current] === "=") {
+                  this._current++;
+                  return {type: TOK_LTE, value: "<=", start: start};
+              } else {
+                  return {type: TOK_LT, value: "<", start: start};
+              }
+          } else if (startingChar === ">") {
+              if (stream[this._current] === "=") {
+                  this._current++;
+                  return {type: TOK_GTE, value: ">=", start: start};
+              } else {
+                  return {type: TOK_GT, value: ">", start: start};
+              }
+          } else if (startingChar === "=") {
+              if (stream[this._current] === "=") {
+                  this._current++;
+                  return {type: TOK_EQ, value: "==", start: start};
+              }
+          }
+      },
+
+      _consumeLiteral: function(stream) {
+          this._current++;
+          var start = this._current;
+          var maxLength = stream.length;
+          var literal;
+          while(stream[this._current] !== "`" && this._current < maxLength) {
+              // You can escape a literal char or you can escape the escape.
+              var current = this._current;
+              if (stream[current] === "\\" && (stream[current + 1] === "\\" ||
+                                               stream[current + 1] === "`")) {
+                  current += 2;
+              } else {
+                  current++;
+              }
+              this._current = current;
+          }
+          var literalString = trimLeft(stream.slice(start, this._current));
+          literalString = literalString.replace("\\`", "`");
+          if (this._looksLikeJSON(literalString)) {
+              literal = JSON.parse(literalString);
+          } else {
+              // Try to JSON parse it as "<literal>"
+              literal = JSON.parse("\"" + literalString + "\"");
+          }
+          // +1 gets us to the ending "`", +1 to move on to the next char.
+          this._current++;
+          return literal;
+      },
+
+      _looksLikeJSON: function(literalString) {
+          var startingChars = "[{\"";
+          var jsonLiterals = ["true", "false", "null"];
+          var numberLooking = "-0123456789";
+
+          if (literalString === "") {
+              return false;
+          } else if (startingChars.indexOf(literalString[0]) >= 0) {
+              return true;
+          } else if (jsonLiterals.indexOf(literalString) >= 0) {
+              return true;
+          } else if (numberLooking.indexOf(literalString[0]) >= 0) {
+              try {
+                  JSON.parse(literalString);
+                  return true;
+              } catch (ex) {
+                  return false;
+              }
+          } else {
+              return false;
+          }
+      }
+  };
+
+      var bindingPower = {};
+      bindingPower[TOK_EOF] = 0;
+      bindingPower[TOK_UNQUOTEDIDENTIFIER] = 0;
+      bindingPower[TOK_QUOTEDIDENTIFIER] = 0;
+      bindingPower[TOK_RBRACKET] = 0;
+      bindingPower[TOK_RPAREN] = 0;
+      bindingPower[TOK_COMMA] = 0;
+      bindingPower[TOK_RBRACE] = 0;
+      bindingPower[TOK_NUMBER] = 0;
+      bindingPower[TOK_CURRENT] = 0;
+      bindingPower[TOK_EXPREF] = 0;
+      bindingPower[TOK_PIPE] = 1;
+      bindingPower[TOK_OR] = 2;
+      bindingPower[TOK_AND] = 3;
+      bindingPower[TOK_EQ] = 5;
+      bindingPower[TOK_GT] = 5;
+      bindingPower[TOK_LT] = 5;
+      bindingPower[TOK_GTE] = 5;
+      bindingPower[TOK_LTE] = 5;
+      bindingPower[TOK_NE] = 5;
+      bindingPower[TOK_FLATTEN] = 9;
+      bindingPower[TOK_STAR] = 20;
+      bindingPower[TOK_FILTER] = 21;
+      bindingPower[TOK_DOT] = 40;
+      bindingPower[TOK_NOT] = 45;
+      bindingPower[TOK_LBRACE] = 50;
+      bindingPower[TOK_LBRACKET] = 55;
+      bindingPower[TOK_LPAREN] = 60;
+
+  function Parser() {
+  }
+
+  Parser.prototype = {
+      parse: function(expression) {
+          this._loadTokens(expression);
+          this.index = 0;
+          var ast = this.expression(0);
+          if (this._lookahead(0) !== TOK_EOF) {
+              var t = this._lookaheadToken(0);
+              var error = new Error(
+                  "Unexpected token type: " + t.type + ", value: " + t.value);
+              error.name = "ParserError";
+              throw error;
+          }
+          return ast;
+      },
+
+      _loadTokens: function(expression) {
+          var lexer = new Lexer();
+          var tokens = lexer.tokenize(expression);
+          tokens.push({type: TOK_EOF, value: "", start: expression.length});
+          this.tokens = tokens;
+      },
+
+      expression: function(rbp) {
+          var leftToken = this._lookaheadToken(0);
+          this._advance();
+          var left = this.nud(leftToken);
+          var currentToken = this._lookahead(0);
+          while (rbp < bindingPower[currentToken]) {
+              this._advance();
+              left = this.led(currentToken, left);
+              currentToken = this._lookahead(0);
+          }
+          return left;
+      },
+
+      _lookahead: function(number) {
+          return this.tokens[this.index + number].type;
+      },
+
+      _lookaheadToken: function(number) {
+          return this.tokens[this.index + number];
+      },
+
+      _advance: function() {
+          this.index++;
+      },
+
+      nud: function(token) {
+        var left;
+        var right;
+        var expression;
+        switch (token.type) {
+          case TOK_LITERAL:
+            return {type: "Literal", value: token.value};
+          case TOK_UNQUOTEDIDENTIFIER:
+            return {type: "Field", name: token.value};
+          case TOK_QUOTEDIDENTIFIER:
+            var node = {type: "Field", name: token.value};
+            if (this._lookahead(0) === TOK_LPAREN) {
+                throw new Error("Quoted identifier not allowed for function names.");
+            } else {
+                return node;
+            }
+            break;
+          case TOK_NOT:
+            right = this.expression(bindingPower.Not);
+            return {type: "NotExpression", children: [right]};
+          case TOK_STAR:
+            left = {type: "Identity"};
+            right = null;
+            if (this._lookahead(0) === TOK_RBRACKET) {
+                // This can happen in a multiselect,
+                // [a, b, *]
+                right = {type: "Identity"};
+            } else {
+                right = this._parseProjectionRHS(bindingPower.Star);
+            }
+            return {type: "ValueProjection", children: [left, right]};
+          case TOK_FILTER:
+            return this.led(token.type, {type: "Identity"});
+          case TOK_LBRACE:
+            return this._parseMultiselectHash();
+          case TOK_FLATTEN:
+            left = {type: TOK_FLATTEN, children: [{type: "Identity"}]};
+            right = this._parseProjectionRHS(bindingPower.Flatten);
+            return {type: "Projection", children: [left, right]};
+          case TOK_LBRACKET:
+            if (this._lookahead(0) === TOK_NUMBER || this._lookahead(0) === TOK_COLON) {
+                right = this._parseIndexExpression();
+                return this._projectIfSlice({type: "Identity"}, right);
+            } else if (this._lookahead(0) === TOK_STAR &&
+                       this._lookahead(1) === TOK_RBRACKET) {
+                this._advance();
+                this._advance();
+                right = this._parseProjectionRHS(bindingPower.Star);
+                return {type: "Projection",
+                        children: [{type: "Identity"}, right]};
+            } else {
+                return this._parseMultiselectList();
+            }
+            break;
+          case TOK_CURRENT:
+            return {type: TOK_CURRENT};
+          case TOK_EXPREF:
+            expression = this.expression(bindingPower.Expref);
+            return {type: "ExpressionReference", children: [expression]};
+          case TOK_LPAREN:
+            var args = [];
+            while (this._lookahead(0) !== TOK_RPAREN) {
+              if (this._lookahead(0) === TOK_CURRENT) {
+                expression = {type: TOK_CURRENT};
+                this._advance();
+              } else {
+                expression = this.expression(0);
+              }
+              args.push(expression);
+            }
+            this._match(TOK_RPAREN);
+            return args[0];
+          default:
+            this._errorToken(token);
+        }
+      },
+
+      led: function(tokenName, left) {
+        var right;
+        switch(tokenName) {
+          case TOK_DOT:
+            var rbp = bindingPower.Dot;
+            if (this._lookahead(0) !== TOK_STAR) {
+                right = this._parseDotRHS(rbp);
+                return {type: "Subexpression", children: [left, right]};
+            } else {
+                // Creating a projection.
+                this._advance();
+                right = this._parseProjectionRHS(rbp);
+                return {type: "ValueProjection", children: [left, right]};
+            }
+            break;
+          case TOK_PIPE:
+            right = this.expression(bindingPower.Pipe);
+            return {type: TOK_PIPE, children: [left, right]};
+          case TOK_OR:
+            right = this.expression(bindingPower.Or);
+            return {type: "OrExpression", children: [left, right]};
+          case TOK_AND:
+            right = this.expression(bindingPower.And);
+            return {type: "AndExpression", children: [left, right]};
+          case TOK_LPAREN:
+            var name = left.name;
+            var args = [];
+            var expression, node;
+            while (this._lookahead(0) !== TOK_RPAREN) {
+              if (this._lookahead(0) === TOK_CURRENT) {
+                expression = {type: TOK_CURRENT};
+                this._advance();
+              } else {
+                expression = this.expression(0);
+              }
+              if (this._lookahead(0) === TOK_COMMA) {
+                this._match(TOK_COMMA);
+              }
+              args.push(expression);
+            }
+            this._match(TOK_RPAREN);
+            node = {type: "Function", name: name, children: args};
+            return node;
+          case TOK_FILTER:
+            var condition = this.expression(0);
+            this._match(TOK_RBRACKET);
+            if (this._lookahead(0) === TOK_FLATTEN) {
+              right = {type: "Identity"};
+            } else {
+              right = this._parseProjectionRHS(bindingPower.Filter);
+            }
+            return {type: "FilterProjection", children: [left, right, condition]};
+          case TOK_FLATTEN:
+            var leftNode = {type: TOK_FLATTEN, children: [left]};
+            var rightNode = this._parseProjectionRHS(bindingPower.Flatten);
+            return {type: "Projection", children: [leftNode, rightNode]};
+          case TOK_EQ:
+          case TOK_NE:
+          case TOK_GT:
+          case TOK_GTE:
+          case TOK_LT:
+          case TOK_LTE:
+            return this._parseComparator(left, tokenName);
+          case TOK_LBRACKET:
+            var token = this._lookaheadToken(0);
+            if (token.type === TOK_NUMBER || token.type === TOK_COLON) {
+                right = this._parseIndexExpression();
+                return this._projectIfSlice(left, right);
+            } else {
+                this._match(TOK_STAR);
+                this._match(TOK_RBRACKET);
+                right = this._parseProjectionRHS(bindingPower.Star);
+                return {type: "Projection", children: [left, right]};
+            }
+            break;
+          default:
+            this._errorToken(this._lookaheadToken(0));
+        }
+      },
+
+      _match: function(tokenType) {
+          if (this._lookahead(0) === tokenType) {
+              this._advance();
+          } else {
+              var t = this._lookaheadToken(0);
+              var error = new Error("Expected " + tokenType + ", got: " + t.type);
+              error.name = "ParserError";
+              throw error;
+          }
+      },
+
+      _errorToken: function(token) {
+          var error = new Error("Invalid token (" +
+                                token.type + "): \"" +
+                                token.value + "\"");
+          error.name = "ParserError";
+          throw error;
+      },
+
+
+      _parseIndexExpression: function() {
+          if (this._lookahead(0) === TOK_COLON || this._lookahead(1) === TOK_COLON) {
+              return this._parseSliceExpression();
+          } else {
+              var node = {
+                  type: "Index",
+                  value: this._lookaheadToken(0).value};
+              this._advance();
+              this._match(TOK_RBRACKET);
+              return node;
+          }
+      },
+
+      _projectIfSlice: function(left, right) {
+          var indexExpr = {type: "IndexExpression", children: [left, right]};
+          if (right.type === "Slice") {
+              return {
+                  type: "Projection",
+                  children: [indexExpr, this._parseProjectionRHS(bindingPower.Star)]
+              };
+          } else {
+              return indexExpr;
+          }
+      },
+
+      _parseSliceExpression: function() {
+          // [start:end:step] where each part is optional, as well as the last
+          // colon.
+          var parts = [null, null, null];
+          var index = 0;
+          var currentToken = this._lookahead(0);
+          while (currentToken !== TOK_RBRACKET && index < 3) {
+              if (currentToken === TOK_COLON) {
+                  index++;
+                  this._advance();
+              } else if (currentToken === TOK_NUMBER) {
+                  parts[index] = this._lookaheadToken(0).value;
+                  this._advance();
+              } else {
+                  var t = this._lookahead(0);
+                  var error = new Error("Syntax error, unexpected token: " +
+                                        t.value + "(" + t.type + ")");
+                  error.name = "Parsererror";
+                  throw error;
+              }
+              currentToken = this._lookahead(0);
+          }
+          this._match(TOK_RBRACKET);
+          return {
+              type: "Slice",
+              children: parts
+          };
+      },
+
+      _parseComparator: function(left, comparator) {
+        var right = this.expression(bindingPower[comparator]);
+        return {type: "Comparator", name: comparator, children: [left, right]};
+      },
+
+      _parseDotRHS: function(rbp) {
+          var lookahead = this._lookahead(0);
+          var exprTokens = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER, TOK_STAR];
+          if (exprTokens.indexOf(lookahead) >= 0) {
+              return this.expression(rbp);
+          } else if (lookahead === TOK_LBRACKET) {
+              this._match(TOK_LBRACKET);
+              return this._parseMultiselectList();
+          } else if (lookahead === TOK_LBRACE) {
+              this._match(TOK_LBRACE);
+              return this._parseMultiselectHash();
+          }
+      },
+
+      _parseProjectionRHS: function(rbp) {
+          var right;
+          if (bindingPower[this._lookahead(0)] < 10) {
+              right = {type: "Identity"};
+          } else if (this._lookahead(0) === TOK_LBRACKET) {
+              right = this.expression(rbp);
+          } else if (this._lookahead(0) === TOK_FILTER) {
+              right = this.expression(rbp);
+          } else if (this._lookahead(0) === TOK_DOT) {
+              this._match(TOK_DOT);
+              right = this._parseDotRHS(rbp);
+          } else {
+              var t = this._lookaheadToken(0);
+              var error = new Error("Sytanx error, unexpected token: " +
+                                    t.value + "(" + t.type + ")");
+              error.name = "ParserError";
+              throw error;
+          }
+          return right;
+      },
+
+      _parseMultiselectList: function() {
+          var expressions = [];
+          while (this._lookahead(0) !== TOK_RBRACKET) {
+              var expression = this.expression(0);
+              expressions.push(expression);
+              if (this._lookahead(0) === TOK_COMMA) {
+                  this._match(TOK_COMMA);
+                  if (this._lookahead(0) === TOK_RBRACKET) {
+                    throw new Error("Unexpected token Rbracket");
+                  }
+              }
+          }
+          this._match(TOK_RBRACKET);
+          return {type: "MultiSelectList", children: expressions};
+      },
+
+      _parseMultiselectHash: function() {
+        var pairs = [];
+        var identifierTypes = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER];
+        var keyToken, keyName, value, node;
+        for (;;) {
+          keyToken = this._lookaheadToken(0);
+          if (identifierTypes.indexOf(keyToken.type) < 0) {
+            throw new Error("Expecting an identifier token, got: " +
+                            keyToken.type);
+          }
+          keyName = keyToken.value;
+          this._advance();
+          this._match(TOK_COLON);
+          value = this.expression(0);
+          node = {type: "KeyValuePair", name: keyName, value: value};
+          pairs.push(node);
+          if (this._lookahead(0) === TOK_COMMA) {
+            this._match(TOK_COMMA);
+          } else if (this._lookahead(0) === TOK_RBRACE) {
+            this._match(TOK_RBRACE);
+            break;
+          }
+        }
+        return {type: "MultiSelectHash", children: pairs};
+      }
+  };
+
+
+  function TreeInterpreter(runtime) {
+    this.runtime = runtime;
+  }
+
+  TreeInterpreter.prototype = {
+      search: function(node, value) {
+          return this.visit(node, value);
+      },
+
+      visit: function(node, value) {
+          var matched, current, result, first, second, field, left, right, collected, i;
+          switch (node.type) {
+            case "Field":
+              if (value === null ) {
+                  return null;
+              } else if (isObject(value)) {
+                  field = value[node.name];
+                  if (field === undefined) {
+                      return null;
+                  } else {
+                      return field;
+                  }
+              } else {
+                return null;
+              }
+              break;
+            case "Subexpression":
+              result = this.visit(node.children[0], value);
+              for (i = 1; i < node.children.length; i++) {
+                  result = this.visit(node.children[1], result);
+                  if (result === null) {
+                      return null;
+                  }
+              }
+              return result;
+            case "IndexExpression":
+              left = this.visit(node.children[0], value);
+              right = this.visit(node.children[1], left);
+              return right;
+            case "Index":
+              if (!isArray(value)) {
+                return null;
+              }
+              var index = node.value;
+              if (index < 0) {
+                index = value.length + index;
+              }
+              result = value[index];
+              if (result === undefined) {
+                result = null;
+              }
+              return result;
+            case "Slice":
+              if (!isArray(value)) {
+                return null;
+              }
+              var sliceParams = node.children.slice(0);
+              var computed = this.computeSliceParams(value.length, sliceParams);
+              var start = computed[0];
+              var stop = computed[1];
+              var step = computed[2];
+              result = [];
+              if (step > 0) {
+                  for (i = start; i < stop; i += step) {
+                      result.push(value[i]);
+                  }
+              } else {
+                  for (i = start; i > stop; i += step) {
+                      result.push(value[i]);
+                  }
+              }
+              return result;
+            case "Projection":
+              // Evaluate left child.
+              var base = this.visit(node.children[0], value);
+              if (!isArray(base)) {
+                return null;
+              }
+              collected = [];
+              for (i = 0; i < base.length; i++) {
+                current = this.visit(node.children[1], base[i]);
+                if (current !== null) {
+                  collected.push(current);
+                }
+              }
+              return collected;
+            case "ValueProjection":
+              // Evaluate left child.
+              base = this.visit(node.children[0], value);
+              if (!isObject(base)) {
+                return null;
+              }
+              collected = [];
+              var values = objValues(base);
+              for (i = 0; i < values.length; i++) {
+                current = this.visit(node.children[1], values[i]);
+                if (current !== null) {
+                  collected.push(current);
+                }
+              }
+              return collected;
+            case "FilterProjection":
+              base = this.visit(node.children[0], value);
+              if (!isArray(base)) {
+                return null;
+              }
+              var filtered = [];
+              var finalResults = [];
+              for (i = 0; i < base.length; i++) {
+                matched = this.visit(node.children[2], base[i]);
+                if (!isFalse(matched)) {
+                  filtered.push(base[i]);
+                }
+              }
+              for (var j = 0; j < filtered.length; j++) {
+                current = this.visit(node.children[1], filtered[j]);
+                if (current !== null) {
+                  finalResults.push(current);
+                }
+              }
+              return finalResults;
+            case "Comparator":
+              first = this.visit(node.children[0], value);
+              second = this.visit(node.children[1], value);
+              switch(node.name) {
+                case TOK_EQ:
+                  result = strictDeepEqual(first, second);
+                  break;
+                case TOK_NE:
+                  result = !strictDeepEqual(first, second);
+                  break;
+                case TOK_GT:
+                  result = first > second;
+                  break;
+                case TOK_GTE:
+                  result = first >= second;
+                  break;
+                case TOK_LT:
+                  result = first < second;
+                  break;
+                case TOK_LTE:
+                  result = first <= second;
+                  break;
+                default:
+                  throw new Error("Unknown comparator: " + node.name);
+              }
+              return result;
+            case TOK_FLATTEN:
+              var original = this.visit(node.children[0], value);
+              if (!isArray(original)) {
+                return null;
+              }
+              var merged = [];
+              for (i = 0; i < original.length; i++) {
+                current = original[i];
+                if (isArray(current)) {
+                  merged.push.apply(merged, current);
+                } else {
+                  merged.push(current);
+                }
+              }
+              return merged;
+            case "Identity":
+              return value;
+            case "MultiSelectList":
+              if (value === null) {
+                return null;
+              }
+              collected = [];
+              for (i = 0; i < node.children.length; i++) {
+                  collected.push(this.visit(node.children[i], value));
+              }
+              return collected;
+            case "MultiSelectHash":
+              if (value === null) {
+                return null;
+              }
+              collected = {};
+              var child;
+              for (i = 0; i < node.children.length; i++) {
+                child = node.children[i];
+                collected[child.name] = this.visit(child.value, value);
+              }
+              return collected;
+            case "OrExpression":
+              matched = this.visit(node.children[0], value);
+              if (isFalse(matched)) {
+                  matched = this.visit(node.children[1], value);
+              }
+              return matched;
+            case "AndExpression":
+              first = this.visit(node.children[0], value);
+
+              if (isFalse(first) === true) {
+                return first;
+              }
+              return this.visit(node.children[1], value);
+            case "NotExpression":
+              first = this.visit(node.children[0], value);
+              return isFalse(first);
+            case "Literal":
+              return node.value;
+            case TOK_PIPE:
+              left = this.visit(node.children[0], value);
+              return this.visit(node.children[1], left);
+            case TOK_CURRENT:
+              return value;
+            case "Function":
+              var resolvedArgs = [];
+              for (i = 0; i < node.children.length; i++) {
+                  resolvedArgs.push(this.visit(node.children[i], value));
+              }
+              return this.runtime.callFunction(node.name, resolvedArgs);
+            case "ExpressionReference":
+              var refNode = node.children[0];
+              // Tag the node with a specific attribute so the type
+              // checker verify the type.
+              refNode.jmespathType = TOK_EXPREF;
+              return refNode;
+            default:
+              throw new Error("Unknown node type: " + node.type);
+          }
+      },
+
+      computeSliceParams: function(arrayLength, sliceParams) {
+        var start = sliceParams[0];
+        var stop = sliceParams[1];
+        var step = sliceParams[2];
+        var computed = [null, null, null];
+        if (step === null) {
+          step = 1;
+        } else if (step === 0) {
+          var error = new Error("Invalid slice, step cannot be 0");
+          error.name = "RuntimeError";
+          throw error;
+        }
+        var stepValueNegative = step < 0 ? true : false;
+
+        if (start === null) {
+            start = stepValueNegative ? arrayLength - 1 : 0;
+        } else {
+            start = this.capSliceRange(arrayLength, start, step);
+        }
+
+        if (stop === null) {
+            stop = stepValueNegative ? -1 : arrayLength;
+        } else {
+            stop = this.capSliceRange(arrayLength, stop, step);
+        }
+        computed[0] = start;
+        computed[1] = stop;
+        computed[2] = step;
+        return computed;
+      },
+
+      capSliceRange: function(arrayLength, actualValue, step) {
+          if (actualValue < 0) {
+              actualValue += arrayLength;
+              if (actualValue < 0) {
+                  actualValue = step < 0 ? -1 : 0;
+              }
+          } else if (actualValue >= arrayLength) {
+              actualValue = step < 0 ? arrayLength - 1 : arrayLength;
+          }
+          return actualValue;
+      }
+
+  };
+
+  function Runtime(interpreter) {
+    this._interpreter = interpreter;
+    this.functionTable = {
+        // name: [function, <signature>]
+        // The <signature> can be:
+        //
+        // {
+        //   args: [[type1, type2], [type1, type2]],
+        //   variadic: true|false
+        // }
+        //
+        // Each arg in the arg list is a list of valid types
+        // (if the function is overloaded and supports multiple
+        // types.  If the type is "any" then no type checking
+        // occurs on the argument.  Variadic is optional
+        // and if not provided is assumed to be false.
+        abs: {_func: this._functionAbs, _signature: [{types: [TYPE_NUMBER]}]},
+        avg: {_func: this._functionAvg, _signature: [{types: [TYPE_ARRAY_NUMBER]}]},
+        ceil: {_func: this._functionCeil, _signature: [{types: [TYPE_NUMBER]}]},
+        contains: {
+            _func: this._functionContains,
+            _signature: [{types: [TYPE_STRING, TYPE_ARRAY]},
+                        {types: [TYPE_ANY]}]},
+        "ends_with": {
+            _func: this._functionEndsWith,
+            _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]},
+        floor: {_func: this._functionFloor, _signature: [{types: [TYPE_NUMBER]}]},
+        length: {
+            _func: this._functionLength,
+            _signature: [{types: [TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT]}]},
+        map: {
+            _func: this._functionMap,
+            _signature: [{types: [TYPE_EXPREF]}, {types: [TYPE_ARRAY]}]},
+        max: {
+            _func: this._functionMax,
+            _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]},
+        "merge": {
+            _func: this._functionMerge,
+            _signature: [{types: [TYPE_OBJECT], variadic: true}]
+        },
+        "max_by": {
+          _func: this._functionMaxBy,
+          _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
+        },
+        sum: {_func: this._functionSum, _signature: [{types: [TYPE_ARRAY_NUMBER]}]},
+        "starts_with": {
+            _func: this._functionStartsWith,
+            _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]},
+        min: {
+            _func: this._functionMin,
+            _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]},
+        "min_by": {
+          _func: this._functionMinBy,
+          _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
+        },
+        type: {_func: this._functionType, _signature: [{types: [TYPE_ANY]}]},
+        keys: {_func: this._functionKeys, _signature: [{types: [TYPE_OBJECT]}]},
+        values: {_func: this._functionValues, _signature: [{types: [TYPE_OBJECT]}]},
+        sort: {_func: this._functionSort, _signature: [{types: [TYPE_ARRAY_STRING, TYPE_ARRAY_NUMBER]}]},
+        "sort_by": {
+          _func: this._functionSortBy,
+          _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}]
+        },
+        join: {
+            _func: this._functionJoin,
+            _signature: [
+                {types: [TYPE_STRING]},
+                {types: [TYPE_ARRAY_STRING]}
+            ]
+        },
+        reverse: {
+            _func: this._functionReverse,
+            _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}]},
+        "to_array": {_func: this._functionToArray, _signature: [{types: [TYPE_ANY]}]},
+        "to_string": {_func: this._functionToString, _signature: [{types: [TYPE_ANY]}]},
+        "to_number": {_func: this._functionToNumber, _signature: [{types: [TYPE_ANY]}]},
+        "not_null": {
+            _func: this._functionNotNull,
+            _signature: [{types: [TYPE_ANY], variadic: true}]
+        }
+    };
+  }
+
+  Runtime.prototype = {
+    callFunction: function(name, resolvedArgs) {
+      var functionEntry = this.functionTable[name];
+      if (functionEntry === undefined) {
+          throw new Error("Unknown function: " + name + "()");
+      }
+      this._validateArgs(name, resolvedArgs, functionEntry._signature);
+      return functionEntry._func.call(this, resolvedArgs);
+    },
+
+    _validateArgs: function(name, args, signature) {
+        // Validating the args requires validating
+        // the correct arity and the correct type of each arg.
+        // If the last argument is declared as variadic, then we need
+        // a minimum number of args to be required.  Otherwise it has to
+        // be an exact amount.
+        var pluralized;
+        if (signature[signature.length - 1].variadic) {
+            if (args.length < signature.length) {
+                pluralized = signature.length === 1 ? " argument" : " arguments";
+                throw new Error("ArgumentError: " + name + "() " +
+                                "takes at least" + signature.length + pluralized +
+                                " but received " + args.length);
+            }
+        } else if (args.length !== signature.length) {
+            pluralized = signature.length === 1 ? " argument" : " arguments";
+            throw new Error("ArgumentError: " + name + "() " +
+                            "takes " + signature.length + pluralized +
+                            " but received " + args.length);
+        }
+        var currentSpec;
+        var actualType;
+        var typeMatched;
+        for (var i = 0; i < signature.length; i++) {
+            typeMatched = false;
+            currentSpec = signature[i].types;
+            actualType = this._getTypeName(args[i]);
+            for (var j = 0; j < currentSpec.length; j++) {
+                if (this._typeMatches(actualType, currentSpec[j], args[i])) {
+                    typeMatched = true;
+                    break;
+                }
+            }
+            if (!typeMatched) {
+                throw new Error("TypeError: " + name + "() " +
+                                "expected argument " + (i + 1) +
+                                " to be type " + currentSpec +
+                                " but received type " + actualType +
+                                " instead.");
+            }
+        }
+    },
+
+    _typeMatches: function(actual, expected, argValue) {
+        if (expected === TYPE_ANY) {
+            return true;
+        }
+        if (expected === TYPE_ARRAY_STRING ||
+            expected === TYPE_ARRAY_NUMBER ||
+            expected === TYPE_ARRAY) {
+            // The expected type can either just be array,
+            // or it can require a specific subtype (array of numbers).
+            //
+            // The simplest case is if "array" with no subtype is specified.
+            if (expected === TYPE_ARRAY) {
+                return actual === TYPE_ARRAY;
+            } else if (actual === TYPE_ARRAY) {
+                // Otherwise we need to check subtypes.
+                // I think this has potential to be improved.
+                var subtype;
+                if (expected === TYPE_ARRAY_NUMBER) {
+                  subtype = TYPE_NUMBER;
+                } else if (expected === TYPE_ARRAY_STRING) {
+                  subtype = TYPE_STRING;
+                }
+                for (var i = 0; i < argValue.length; i++) {
+                    if (!this._typeMatches(
+                            this._getTypeName(argValue[i]), subtype,
+                                             argValue[i])) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        } else {
+            return actual === expected;
+        }
+    },
+    _getTypeName: function(obj) {
+        switch (Object.prototype.toString.call(obj)) {
+            case "[object String]":
+              return TYPE_STRING;
+            case "[object Number]":
+              return TYPE_NUMBER;
+            case "[object Array]":
+              return TYPE_ARRAY;
+            case "[object Boolean]":
+              return TYPE_BOOLEAN;
+            case "[object Null]":
+              return TYPE_NULL;
+            case "[object Object]":
+              // Check if it's an expref.  If it has, it's been
+              // tagged with a jmespathType attr of 'Expref';
+              if (obj.jmespathType === TOK_EXPREF) {
+                return TYPE_EXPREF;
+              } else {
+                return TYPE_OBJECT;
+              }
+        }
+    },
+
+    _functionStartsWith: function(resolvedArgs) {
+        return resolvedArgs[0].lastIndexOf(resolvedArgs[1]) === 0;
+    },
+
+    _functionEndsWith: function(resolvedArgs) {
+        var searchStr = resolvedArgs[0];
+        var suffix = resolvedArgs[1];
+        return searchStr.indexOf(suffix, searchStr.length - suffix.length) !== -1;
+    },
+
+    _functionReverse: function(resolvedArgs) {
+        var typeName = this._getTypeName(resolvedArgs[0]);
+        if (typeName === TYPE_STRING) {
+          var originalStr = resolvedArgs[0];
+          var reversedStr = "";
+          for (var i = originalStr.length - 1; i >= 0; i--) {
+              reversedStr += originalStr[i];
+          }
+          return reversedStr;
+        } else {
+          var reversedArray = resolvedArgs[0].slice(0);
+          reversedArray.reverse();
+          return reversedArray;
+        }
+    },
+
+    _functionAbs: function(resolvedArgs) {
+      return Math.abs(resolvedArgs[0]);
+    },
+
+    _functionCeil: function(resolvedArgs) {
+        return Math.ceil(resolvedArgs[0]);
+    },
+
+    _functionAvg: function(resolvedArgs) {
+        var sum = 0;
+        var inputArray = resolvedArgs[0];
+        for (var i = 0; i < inputArray.length; i++) {
+            sum += inputArray[i];
+        }
+        return sum / inputArray.length;
+    },
+
+    _functionContains: function(resolvedArgs) {
+        return resolvedArgs[0].indexOf(resolvedArgs[1]) >= 0;
+    },
+
+    _functionFloor: function(resolvedArgs) {
+        return Math.floor(resolvedArgs[0]);
+    },
+
+    _functionLength: function(resolvedArgs) {
+       if (!isObject(resolvedArgs[0])) {
+         return resolvedArgs[0].length;
+       } else {
+         // As far as I can tell, there's no way to get the length
+         // of an object without O(n) iteration through the object.
+         return Object.keys(resolvedArgs[0]).length;
+       }
+    },
+
+    _functionMap: function(resolvedArgs) {
+      var mapped = [];
+      var interpreter = this._interpreter;
+      var exprefNode = resolvedArgs[0];
+      var elements = resolvedArgs[1];
+      for (var i = 0; i < elements.length; i++) {
+          mapped.push(interpreter.visit(exprefNode, elements[i]));
+      }
+      return mapped;
+    },
+
+    _functionMerge: function(resolvedArgs) {
+      var merged = {};
+      for (var i = 0; i < resolvedArgs.length; i++) {
+        var current = resolvedArgs[i];
+        for (var key in current) {
+          merged[key] = current[key];
+        }
+      }
+      return merged;
+    },
+
+    _functionMax: function(resolvedArgs) {
+      if (resolvedArgs[0].length > 0) {
+        var typeName = this._getTypeName(resolvedArgs[0][0]);
+        if (typeName === TYPE_NUMBER) {
+          return Math.max.apply(Math, resolvedArgs[0]);
+        } else {
+          var elements = resolvedArgs[0];
+          var maxElement = elements[0];
+          for (var i = 1; i < elements.length; i++) {
+              if (maxElement.localeCompare(elements[i]) < 0) {
+                  maxElement = elements[i];
+              }
+          }
+          return maxElement;
+        }
+      } else {
+          return null;
+      }
+    },
+
+    _functionMin: function(resolvedArgs) {
+      if (resolvedArgs[0].length > 0) {
+        var typeName = this._getTypeName(resolvedArgs[0][0]);
+        if (typeName === TYPE_NUMBER) {
+          return Math.min.apply(Math, resolvedArgs[0]);
+        } else {
+          var elements = resolvedArgs[0];
+          var minElement = elements[0];
+          for (var i = 1; i < elements.length; i++) {
+              if (elements[i].localeCompare(minElement) < 0) {
+                  minElement = elements[i];
+              }
+          }
+          return minElement;
+        }
+      } else {
+        return null;
+      }
+    },
+
+    _functionSum: function(resolvedArgs) {
+      var sum = 0;
+      var listToSum = resolvedArgs[0];
+      for (var i = 0; i < listToSum.length; i++) {
+        sum += listToSum[i];
+      }
+      return sum;
+    },
+
+    _functionType: function(resolvedArgs) {
+        switch (this._getTypeName(resolvedArgs[0])) {
+          case TYPE_NUMBER:
+            return "number";
+          case TYPE_STRING:
+            return "string";
+          case TYPE_ARRAY:
+            return "array";
+          case TYPE_OBJECT:
+            return "object";
+          case TYPE_BOOLEAN:
+            return "boolean";
+          case TYPE_EXPREF:
+            return "expref";
+          case TYPE_NULL:
+            return "null";
+        }
+    },
+
+    _functionKeys: function(resolvedArgs) {
+        return Object.keys(resolvedArgs[0]);
+    },
+
+    _functionValues: function(resolvedArgs) {
+        var obj = resolvedArgs[0];
+        var keys = Object.keys(obj);
+        var values = [];
+        for (var i = 0; i < keys.length; i++) {
+            values.push(obj[keys[i]]);
+        }
+        return values;
+    },
+
+    _functionJoin: function(resolvedArgs) {
+        var joinChar = resolvedArgs[0];
+        var listJoin = resolvedArgs[1];
+        return listJoin.join(joinChar);
+    },
+
+    _functionToArray: function(resolvedArgs) {
+        if (this._getTypeName(resolvedArgs[0]) === TYPE_ARRAY) {
+            return resolvedArgs[0];
+        } else {
+            return [resolvedArgs[0]];
+        }
+    },
+
+    _functionToString: function(resolvedArgs) {
+        if (this._getTypeName(resolvedArgs[0]) === TYPE_STRING) {
+            return resolvedArgs[0];
+        } else {
+            return JSON.stringify(resolvedArgs[0]);
+        }
+    },
+
+    _functionToNumber: function(resolvedArgs) {
+        var typeName = this._getTypeName(resolvedArgs[0]);
+        var convertedValue;
+        if (typeName === TYPE_NUMBER) {
+            return resolvedArgs[0];
+        } else if (typeName === TYPE_STRING) {
+            convertedValue = +resolvedArgs[0];
+            if (!isNaN(convertedValue)) {
+                return convertedValue;
+            }
+        }
+        return null;
+    },
+
+    _functionNotNull: function(resolvedArgs) {
+        for (var i = 0; i < resolvedArgs.length; i++) {
+            if (this._getTypeName(resolvedArgs[i]) !== TYPE_NULL) {
+                return resolvedArgs[i];
+            }
+        }
+        return null;
+    },
+
+    _functionSort: function(resolvedArgs) {
+        var sortedArray = resolvedArgs[0].slice(0);
+        sortedArray.sort();
+        return sortedArray;
+    },
+
+    _functionSortBy: function(resolvedArgs) {
+        var sortedArray = resolvedArgs[0].slice(0);
+        if (sortedArray.length === 0) {
+            return sortedArray;
+        }
+        var interpreter = this._interpreter;
+        var exprefNode = resolvedArgs[1];
+        var requiredType = this._getTypeName(
+            interpreter.visit(exprefNode, sortedArray[0]));
+        if ([TYPE_NUMBER, TYPE_STRING].indexOf(requiredType) < 0) {
+            throw new Error("TypeError");
+        }
+        var that = this;
+        // In order to get a stable sort out of an unstable
+        // sort algorithm, we decorate/sort/undecorate (DSU)
+        // by creating a new list of [index, element] pairs.
+        // In the cmp function, if the evaluated elements are
+        // equal, then the index will be used as the tiebreaker.
+        // After the decorated list has been sorted, it will be
+        // undecorated to extract the original elements.
+        var decorated = [];
+        for (var i = 0; i < sortedArray.length; i++) {
+          decorated.push([i, sortedArray[i]]);
+        }
+        decorated.sort(function(a, b) {
+          var exprA = interpreter.visit(exprefNode, a[1]);
+          var exprB = interpreter.visit(exprefNode, b[1]);
+          if (that._getTypeName(exprA) !== requiredType) {
+              throw new Error(
+                  "TypeError: expected " + requiredType + ", received " +
+                  that._getTypeName(exprA));
+          } else if (that._getTypeName(exprB) !== requiredType) {
+              throw new Error(
+                  "TypeError: expected " + requiredType + ", received " +
+                  that._getTypeName(exprB));
+          }
+          if (exprA > exprB) {
+            return 1;
+          } else if (exprA < exprB) {
+            return -1;
+          } else {
+            // If they're equal compare the items by their
+            // order to maintain relative order of equal keys
+            // (i.e. to get a stable sort).
+            return a[0] - b[0];
+          }
+        });
+        // Undecorate: extract out the original list elements.
+        for (var j = 0; j < decorated.length; j++) {
+          sortedArray[j] = decorated[j][1];
+        }
+        return sortedArray;
+    },
+
+    _functionMaxBy: function(resolvedArgs) {
+      var exprefNode = resolvedArgs[1];
+      var resolvedArray = resolvedArgs[0];
+      var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]);
+      var maxNumber = -Infinity;
+      var maxRecord;
+      var current;
+      for (var i = 0; i < resolvedArray.length; i++) {
+        current = keyFunction(resolvedArray[i]);
+        if (current > maxNumber) {
+          maxNumber = current;
+          maxRecord = resolvedArray[i];
+        }
+      }
+      return maxRecord;
+    },
+
+    _functionMinBy: function(resolvedArgs) {
+      var exprefNode = resolvedArgs[1];
+      var resolvedArray = resolvedArgs[0];
+      var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]);
+      var minNumber = Infinity;
+      var minRecord;
+      var current;
+      for (var i = 0; i < resolvedArray.length; i++) {
+        current = keyFunction(resolvedArray[i]);
+        if (current < minNumber) {
+          minNumber = current;
+          minRecord = resolvedArray[i];
+        }
+      }
+      return minRecord;
+    },
+
+    createKeyFunction: function(exprefNode, allowedTypes) {
+      var that = this;
+      var interpreter = this._interpreter;
+      var keyFunc = function(x) {
+        var current = interpreter.visit(exprefNode, x);
+        if (allowedTypes.indexOf(that._getTypeName(current)) < 0) {
+          var msg = "TypeError: expected one of " + allowedTypes +
+                    ", received " + that._getTypeName(current);
+          throw new Error(msg);
+        }
+        return current;
+      };
+      return keyFunc;
+    }
+
+  };
+
+  function compile(stream) {
+    var parser = new Parser();
+    var ast = parser.parse(stream);
+    return ast;
+  }
+
+  function tokenize(stream) {
+      var lexer = new Lexer();
+      return lexer.tokenize(stream);
+  }
+
+  function search(data, expression) {
+      var parser = new Parser();
+      // This needs to be improved.  Both the interpreter and runtime depend on
+      // each other.  The runtime needs the interpreter to support exprefs.
+      // There's likely a clean way to avoid the cyclic dependency.
+      var runtime = new Runtime();
+      var interpreter = new TreeInterpreter(runtime);
+      runtime._interpreter = interpreter;
+      var node = parser.parse(expression);
+      return interpreter.search(node, data);
+  }
+
+  exports.tokenize = tokenize;
+  exports.compile = compile;
+  exports.search = search;
+  exports.strictDeepEqual = strictDeepEqual;
+})(typeof exports === "undefined" ? this.jmespath = {} : exports);
diff --git a/src/vitis/client/javascript/externs/jmespath/jmespath.min.js b/src/vitis/client/javascript/externs/jmespath/jmespath.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae76a6df3bdcc74574c5fee3db69207dd81be749
--- /dev/null
+++ b/src/vitis/client/javascript/externs/jmespath/jmespath.min.js
@@ -0,0 +1,2 @@
+/*! jmespath 2016-03-22 */
+!function(a){"use strict";function b(a){return null!==a?"[object Array]"===Object.prototype.toString.call(a):!1}function c(a){return null!==a?"[object Object]"===Object.prototype.toString.call(a):!1}function d(a,e){if(a===e)return!0;var f=Object.prototype.toString.call(a);if(f!==Object.prototype.toString.call(e))return!1;if(b(a)===!0){if(a.length!==e.length)return!1;for(var g=0;g<a.length;g++)if(d(a[g],e[g])===!1)return!1;return!0}if(c(a)===!0){var h={};for(var i in a)if(hasOwnProperty.call(a,i)){if(d(a[i],e[i])===!1)return!1;h[i]=!0}for(var j in e)if(hasOwnProperty.call(e,j)&&h[j]!==!0)return!1;return!0}return!1}function e(a){if(""===a||a===!1||null===a)return!0;if(b(a)&&0===a.length)return!0;if(c(a)){for(var d in a)if(a.hasOwnProperty(d))return!1;return!0}return!1}function f(a){for(var b=Object.keys(a),c=[],d=0;d<b.length;d++)c.push(a[b[d]]);return c}function g(a){return a>="a"&&"z">=a||a>="A"&&"Z">=a||"_"===a}function h(a){return a>="0"&&"9">=a||"-"===a}function i(a){return a>="a"&&"z">=a||a>="A"&&"Z">=a||a>="0"&&"9">=a||"_"===a}function j(){}function k(){}function l(a){this.runtime=a}function m(a){this.a=a,this.functionTable={abs:{b:this.c,d:[{types:[r]}]},avg:{b:this.e,d:[{types:[z]}]},ceil:{b:this.f,d:[{types:[r]}]},contains:{b:this.g,d:[{types:[t,u]},{types:[s]}]},ends_with:{b:this.h,d:[{types:[t]},{types:[t]}]},floor:{b:this.i,d:[{types:[r]}]},length:{b:this.j,d:[{types:[t,u,v]}]},map:{b:this.k,d:[{types:[x]},{types:[u]}]},max:{b:this.l,d:[{types:[z,A]}]},merge:{b:this.m,d:[{types:[v],variadic:!0}]},max_by:{b:this.n,d:[{types:[u]},{types:[x]}]},sum:{b:this.o,d:[{types:[z]}]},starts_with:{b:this.p,d:[{types:[t]},{types:[t]}]},min:{b:this.q,d:[{types:[z,A]}]},min_by:{b:this.r,d:[{types:[u]},{types:[x]}]},type:{b:this.s,d:[{types:[s]}]},keys:{b:this.t,d:[{types:[v]}]},values:{b:this.u,d:[{types:[v]}]},sort:{b:this.v,d:[{types:[A,z]}]},sort_by:{b:this.w,d:[{types:[u]},{types:[x]}]},join:{b:this.x,d:[{types:[t]},{types:[A]}]},reverse:{b:this.y,d:[{types:[t,u]}]},to_array:{b:this.z,d:[{types:[s]}]},to_string:{b:this.A,d:[{types:[s]}]},to_number:{b:this.B,d:[{types:[s]}]},not_null:{b:this.C,d:[{types:[s],variadic:!0}]}}}function n(a){var b=new k,c=b.parse(a);return c}function o(a){var b=new j;return b.tokenize(a)}function p(a,b){var c=new k,d=new m,e=new l(d);d.a=e;var f=c.parse(b);return e.search(f,a)}var q;q="function"==typeof String.prototype.trimLeft?function(a){return a.trimLeft()}:function(a){return a.match(/^\s*(.*)/)[1]};var r=0,s=1,t=2,u=3,v=4,w=5,x=6,y=7,z=8,A=9,B="EOF",C="UnquotedIdentifier",D="QuotedIdentifier",E="Rbracket",F="Rparen",G="Comma",H="Colon",I="Rbrace",J="Number",K="Current",L="Expref",M="Pipe",N="Or",O="And",P="EQ",Q="GT",R="LT",S="GTE",T="LTE",U="NE",V="Flatten",W="Star",X="Filter",Y="Dot",Z="Not",$="Lbrace",_="Lbracket",aa="Lparen",ba="Literal",ca={".":Y,"*":W,",":G,":":H,"{":$,"}":I,"]":E,"(":aa,")":F,"@":K},da={"<":!0,">":!0,"=":!0,"!":!0},ea={" ":!0,"	":!0,"\n":!0};j.prototype={tokenize:function(a){var b=[];this.D=0;for(var c,d,e;this.D<a.length;)if(g(a[this.D]))c=this.D,d=this.E(a),b.push({type:C,value:d,start:c});else if(void 0!==ca[a[this.D]])b.push({type:ca[a[this.D]],value:a[this.D],start:this.D}),this.D++;else if(h(a[this.D]))e=this.F(a),b.push(e);else if("["===a[this.D])e=this.G(a),b.push(e);else if('"'===a[this.D])c=this.D,d=this.H(a),b.push({type:D,value:d,start:c});else if("'"===a[this.D])c=this.D,d=this.I(a),b.push({type:ba,value:d,start:c});else if("`"===a[this.D]){c=this.D;var f=this.J(a);b.push({type:ba,value:f,start:c})}else if(void 0!==da[a[this.D]])b.push(this.K(a));else if(void 0!==ea[a[this.D]])this.D++;else if("&"===a[this.D])c=this.D,this.D++,"&"===a[this.D]?(this.D++,b.push({type:O,value:"&&",start:c})):b.push({type:L,value:"&",start:c});else{if("|"!==a[this.D]){var i=new Error("Unknown character:"+a[this.D]);throw i.name="LexerError",i}c=this.D,this.D++,"|"===a[this.D]?(this.D++,b.push({type:N,value:"||",start:c})):b.push({type:M,value:"|",start:c})}return b},E:function(a){var b=this.D;for(this.D++;this.D<a.length&&i(a[this.D]);)this.D++;return a.slice(b,this.D)},H:function(a){var b=this.D;this.D++;for(var c=a.length;'"'!==a[this.D]&&this.D<c;){var d=this.D;"\\"!==a[d]||"\\"!==a[d+1]&&'"'!==a[d+1]?d++:d+=2,this.D=d}return this.D++,JSON.parse(a.slice(b,this.D))},I:function(a){var b=this.D;this.D++;for(var c=a.length;"'"!==a[this.D]&&this.D<c;){var d=this.D;"\\"!==a[d]||"\\"!==a[d+1]&&"'"!==a[d+1]?d++:d+=2,this.D=d}this.D++;var e=a.slice(b+1,this.D-1);return e.replace("\\'","'")},F:function(a){var b=this.D;this.D++;for(var c=a.length;h(a[this.D])&&this.D<c;)this.D++;var d=parseInt(a.slice(b,this.D));return{type:J,value:d,start:b}},G:function(a){var b=this.D;return this.D++,"?"===a[this.D]?(this.D++,{type:X,value:"[?",start:b}):"]"===a[this.D]?(this.D++,{type:V,value:"[]",start:b}):{type:_,value:"[",start:b}},K:function(a){var b=this.D,c=a[b];return this.D++,"!"===c?"="===a[this.D]?(this.D++,{type:U,value:"!=",start:b}):{type:Z,value:"!",start:b}:"<"===c?"="===a[this.D]?(this.D++,{type:T,value:"<=",start:b}):{type:R,value:"<",start:b}:">"===c?"="===a[this.D]?(this.D++,{type:S,value:">=",start:b}):{type:Q,value:">",start:b}:"="===c&&"="===a[this.D]?(this.D++,{type:P,value:"==",start:b}):void 0},J:function(a){this.D++;for(var b,c=this.D,d=a.length;"`"!==a[this.D]&&this.D<d;){var e=this.D;"\\"!==a[e]||"\\"!==a[e+1]&&"`"!==a[e+1]?e++:e+=2,this.D=e}var f=q(a.slice(c,this.D));return f=f.replace("\\`","`"),b=this.L(f)?JSON.parse(f):JSON.parse('"'+f+'"'),this.D++,b},L:function(a){var b='[{"',c=["true","false","null"],d="-0123456789";if(""===a)return!1;if(b.indexOf(a[0])>=0)return!0;if(c.indexOf(a)>=0)return!0;if(!(d.indexOf(a[0])>=0))return!1;try{return JSON.parse(a),!0}catch(e){return!1}}};var fa={};fa[B]=0,fa[C]=0,fa[D]=0,fa[E]=0,fa[F]=0,fa[G]=0,fa[I]=0,fa[J]=0,fa[K]=0,fa[L]=0,fa[M]=1,fa[N]=2,fa[O]=3,fa[P]=5,fa[Q]=5,fa[R]=5,fa[S]=5,fa[T]=5,fa[U]=5,fa[V]=9,fa[W]=20,fa[X]=21,fa[Y]=40,fa[Z]=45,fa[$]=50,fa[_]=55,fa[aa]=60,k.prototype={parse:function(a){this.M(a),this.index=0;var b=this.expression(0);if(this.N(0)!==B){var c=this.O(0),d=new Error("Unexpected token type: "+c.type+", value: "+c.value);throw d.name="ParserError",d}return b},M:function(a){var b=new j,c=b.tokenize(a);c.push({type:B,value:"",start:a.length}),this.tokens=c},expression:function(a){var b=this.O(0);this.P();for(var c=this.nud(b),d=this.N(0);a<fa[d];)this.P(),c=this.led(d,c),d=this.N(0);return c},N:function(a){return this.tokens[this.index+a].type},O:function(a){return this.tokens[this.index+a]},P:function(){this.index++},nud:function(a){var b,c,d;switch(a.type){case ba:return{type:"Literal",value:a.value};case C:return{type:"Field",name:a.value};case D:var e={type:"Field",name:a.value};if(this.N(0)===aa)throw new Error("Quoted identifier not allowed for function names.");return e;case Z:return c=this.expression(fa.Not),{type:"NotExpression",children:[c]};case W:return b={type:"Identity"},c=null,c=this.N(0)===E?{type:"Identity"}:this.Q(fa.Star),{type:"ValueProjection",children:[b,c]};case X:return this.led(a.type,{type:"Identity"});case $:return this.R();case V:return b={type:V,children:[{type:"Identity"}]},c=this.Q(fa.Flatten),{type:"Projection",children:[b,c]};case _:return this.N(0)===J||this.N(0)===H?(c=this.S(),this.T({type:"Identity"},c)):this.N(0)===W&&this.N(1)===E?(this.P(),this.P(),c=this.Q(fa.Star),{type:"Projection",children:[{type:"Identity"},c]}):this.U();case K:return{type:K};case L:return d=this.expression(fa.Expref),{type:"ExpressionReference",children:[d]};case aa:for(var f=[];this.N(0)!==F;)this.N(0)===K?(d={type:K},this.P()):d=this.expression(0),f.push(d);return this.V(F),f[0];default:this.W(a)}},led:function(a,b){var c;switch(a){case Y:var d=fa.Dot;return this.N(0)!==W?(c=this.X(d),{type:"Subexpression",children:[b,c]}):(this.P(),c=this.Q(d),{type:"ValueProjection",children:[b,c]});case M:return c=this.expression(fa.Pipe),{type:M,children:[b,c]};case N:return c=this.expression(fa.Or),{type:"OrExpression",children:[b,c]};case O:return c=this.expression(fa.And),{type:"AndExpression",children:[b,c]};case aa:for(var e,f,g=b.name,h=[];this.N(0)!==F;)this.N(0)===K?(e={type:K},this.P()):e=this.expression(0),this.N(0)===G&&this.V(G),h.push(e);return this.V(F),f={type:"Function",name:g,children:h};case X:var i=this.expression(0);return this.V(E),c=this.N(0)===V?{type:"Identity"}:this.Q(fa.Filter),{type:"FilterProjection",children:[b,c,i]};case V:var j={type:V,children:[b]},k=this.Q(fa.Flatten);return{type:"Projection",children:[j,k]};case P:case U:case Q:case S:case R:case T:return this.Y(b,a);case _:var l=this.O(0);return l.type===J||l.type===H?(c=this.S(),this.T(b,c)):(this.V(W),this.V(E),c=this.Q(fa.Star),{type:"Projection",children:[b,c]});default:this.W(this.O(0))}},V:function(a){if(this.N(0)!==a){var b=this.O(0),c=new Error("Expected "+a+", got: "+b.type);throw c.name="ParserError",c}this.P()},W:function(a){var b=new Error("Invalid token ("+a.type+'): "'+a.value+'"');throw b.name="ParserError",b},S:function(){if(this.N(0)===H||this.N(1)===H)return this.Z();var a={type:"Index",value:this.O(0).value};return this.P(),this.V(E),a},T:function(a,b){var c={type:"IndexExpression",children:[a,b]};return"Slice"===b.type?{type:"Projection",children:[c,this.Q(fa.Star)]}:c},Z:function(){for(var a=[null,null,null],b=0,c=this.N(0);c!==E&&3>b;){if(c===H)b++,this.P();else{if(c!==J){var d=this.N(0),e=new Error("Syntax error, unexpected token: "+d.value+"("+d.type+")");throw e.name="Parsererror",e}a[b]=this.O(0).value,this.P()}c=this.N(0)}return this.V(E),{type:"Slice",children:a}},Y:function(a,b){var c=this.expression(fa[b]);return{type:"Comparator",name:b,children:[a,c]}},X:function(a){var b=this.N(0),c=[C,D,W];return c.indexOf(b)>=0?this.expression(a):b===_?(this.V(_),this.U()):b===$?(this.V($),this.R()):void 0},Q:function(a){var b;if(fa[this.N(0)]<10)b={type:"Identity"};else if(this.N(0)===_)b=this.expression(a);else if(this.N(0)===X)b=this.expression(a);else{if(this.N(0)!==Y){var c=this.O(0),d=new Error("Sytanx error, unexpected token: "+c.value+"("+c.type+")");throw d.name="ParserError",d}this.V(Y),b=this.X(a)}return b},U:function(){for(var a=[];this.N(0)!==E;){var b=this.expression(0);if(a.push(b),this.N(0)===G&&(this.V(G),this.N(0)===E))throw new Error("Unexpected token Rbracket")}return this.V(E),{type:"MultiSelectList",children:a}},R:function(){for(var a,b,c,d,e=[],f=[C,D];;){if(a=this.O(0),f.indexOf(a.type)<0)throw new Error("Expecting an identifier token, got: "+a.type);if(b=a.value,this.P(),this.V(H),c=this.expression(0),d={type:"KeyValuePair",name:b,value:c},e.push(d),this.N(0)===G)this.V(G);else if(this.N(0)===I){this.V(I);break}}return{type:"MultiSelectHash",children:e}}},l.prototype={search:function(a,b){return this.visit(a,b)},visit:function(a,g){var h,i,j,k,l,m,n,o,p,q;switch(a.type){case"Field":return null===g?null:c(g)?(m=g[a.name],void 0===m?null:m):null;case"Subexpression":for(j=this.visit(a.children[0],g),q=1;q<a.children.length;q++)if(j=this.visit(a.children[1],j),null===j)return null;return j;case"IndexExpression":return n=this.visit(a.children[0],g),o=this.visit(a.children[1],n);case"Index":if(!b(g))return null;var r=a.value;return 0>r&&(r=g.length+r),j=g[r],void 0===j&&(j=null),j;case"Slice":if(!b(g))return null;var s=a.children.slice(0),t=this.computeSliceParams(g.length,s),u=t[0],v=t[1],w=t[2];if(j=[],w>0)for(q=u;v>q;q+=w)j.push(g[q]);else for(q=u;q>v;q+=w)j.push(g[q]);return j;case"Projection":var x=this.visit(a.children[0],g);if(!b(x))return null;for(p=[],q=0;q<x.length;q++)i=this.visit(a.children[1],x[q]),null!==i&&p.push(i);return p;case"ValueProjection":if(x=this.visit(a.children[0],g),!c(x))return null;p=[];var y=f(x);for(q=0;q<y.length;q++)i=this.visit(a.children[1],y[q]),null!==i&&p.push(i);return p;case"FilterProjection":if(x=this.visit(a.children[0],g),!b(x))return null;var z=[],A=[];for(q=0;q<x.length;q++)h=this.visit(a.children[2],x[q]),e(h)||z.push(x[q]);for(var B=0;B<z.length;B++)i=this.visit(a.children[1],z[B]),null!==i&&A.push(i);return A;case"Comparator":switch(k=this.visit(a.children[0],g),l=this.visit(a.children[1],g),a.name){case P:j=d(k,l);break;case U:j=!d(k,l);break;case Q:j=k>l;break;case S:j=k>=l;break;case R:j=l>k;break;case T:j=l>=k;break;default:throw new Error("Unknown comparator: "+a.name)}return j;case V:var C=this.visit(a.children[0],g);if(!b(C))return null;var D=[];for(q=0;q<C.length;q++)i=C[q],b(i)?D.push.apply(D,i):D.push(i);return D;case"Identity":return g;case"MultiSelectList":if(null===g)return null;for(p=[],q=0;q<a.children.length;q++)p.push(this.visit(a.children[q],g));return p;case"MultiSelectHash":if(null===g)return null;p={};var E;for(q=0;q<a.children.length;q++)E=a.children[q],p[E.name]=this.visit(E.value,g);return p;case"OrExpression":return h=this.visit(a.children[0],g),e(h)&&(h=this.visit(a.children[1],g)),h;case"AndExpression":return k=this.visit(a.children[0],g),e(k)===!0?k:this.visit(a.children[1],g);case"NotExpression":return k=this.visit(a.children[0],g),e(k);case"Literal":return a.value;case M:return n=this.visit(a.children[0],g),this.visit(a.children[1],n);case K:return g;case"Function":var F=[];for(q=0;q<a.children.length;q++)F.push(this.visit(a.children[q],g));return this.runtime.callFunction(a.name,F);case"ExpressionReference":var G=a.children[0];return G.jmespathType=L,G;default:throw new Error("Unknown node type: "+a.type)}},computeSliceParams:function(a,b){var c=b[0],d=b[1],e=b[2],f=[null,null,null];if(null===e)e=1;else if(0===e){var g=new Error("Invalid slice, step cannot be 0");throw g.name="RuntimeError",g}var h=0>e?!0:!1;return c=null===c?h?a-1:0:this.capSliceRange(a,c,e),d=null===d?h?-1:a:this.capSliceRange(a,d,e),f[0]=c,f[1]=d,f[2]=e,f},capSliceRange:function(a,b,c){return 0>b?(b+=a,0>b&&(b=0>c?-1:0)):b>=a&&(b=0>c?a-1:a),b}},m.prototype={callFunction:function(a,b){var c=this.functionTable[a];if(void 0===c)throw new Error("Unknown function: "+a+"()");return this.$(a,b,c.d),c.b.call(this,b)},$:function(a,b,c){var d;if(c[c.length-1].variadic){if(b.length<c.length)throw d=1===c.length?" argument":" arguments",new Error("ArgumentError: "+a+"() takes at least"+c.length+d+" but received "+b.length)}else if(b.length!==c.length)throw d=1===c.length?" argument":" arguments",new Error("ArgumentError: "+a+"() takes "+c.length+d+" but received "+b.length);for(var e,f,g,h=0;h<c.length;h++){g=!1,e=c[h].types,f=this._(b[h]);for(var i=0;i<e.length;i++)if(this.aa(f,e[i],b[h])){g=!0;break}if(!g)throw new Error("TypeError: "+a+"() expected argument "+(h+1)+" to be type "+e+" but received type "+f+" instead.")}},aa:function(a,b,c){if(b===s)return!0;if(b!==A&&b!==z&&b!==u)return a===b;if(b===u)return a===u;if(a===u){var d;b===z?d=r:b===A&&(d=t);for(var e=0;e<c.length;e++)if(!this.aa(this._(c[e]),d,c[e]))return!1;return!0}},_:function(a){switch(Object.prototype.toString.call(a)){case"[object String]":return t;case"[object Number]":return r;case"[object Array]":return u;case"[object Boolean]":return w;case"[object Null]":return y;case"[object Object]":return a.jmespathType===L?x:v}},p:function(a){return 0===a[0].lastIndexOf(a[1])},h:function(a){var b=a[0],c=a[1];return-1!==b.indexOf(c,b.length-c.length)},y:function(a){var b=this._(a[0]);if(b===t){for(var c=a[0],d="",e=c.length-1;e>=0;e--)d+=c[e];return d}var f=a[0].slice(0);return f.reverse(),f},c:function(a){return Math.abs(a[0])},f:function(a){return Math.ceil(a[0])},e:function(a){for(var b=0,c=a[0],d=0;d<c.length;d++)b+=c[d];return b/c.length},g:function(a){return a[0].indexOf(a[1])>=0},i:function(a){return Math.floor(a[0])},j:function(a){return c(a[0])?Object.keys(a[0]).length:a[0].length},k:function(a){for(var b=[],c=this.a,d=a[0],e=a[1],f=0;f<e.length;f++)b.push(c.visit(d,e[f]));return b},m:function(a){for(var b={},c=0;c<a.length;c++){var d=a[c];for(var e in d)b[e]=d[e]}return b},l:function(a){if(a[0].length>0){var b=this._(a[0][0]);if(b===r)return Math.max.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;e<c.length;e++)d.localeCompare(c[e])<0&&(d=c[e]);return d}return null},q:function(a){if(a[0].length>0){var b=this._(a[0][0]);if(b===r)return Math.min.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;e<c.length;e++)c[e].localeCompare(d)<0&&(d=c[e]);return d}return null},o:function(a){for(var b=0,c=a[0],d=0;d<c.length;d++)b+=c[d];return b},s:function(a){switch(this._(a[0])){case r:return"number";case t:return"string";case u:return"array";case v:return"object";case w:return"boolean";case x:return"expref";case y:return"null"}},t:function(a){return Object.keys(a[0])},u:function(a){for(var b=a[0],c=Object.keys(b),d=[],e=0;e<c.length;e++)d.push(b[c[e]]);return d},x:function(a){var b=a[0],c=a[1];return c.join(b)},z:function(a){return this._(a[0])===u?a[0]:[a[0]]},A:function(a){return this._(a[0])===t?a[0]:JSON.stringify(a[0])},B:function(a){var b,c=this._(a[0]);return c===r?a[0]:c!==t||(b=+a[0],isNaN(b))?null:b},C:function(a){for(var b=0;b<a.length;b++)if(this._(a[b])!==y)return a[b];return null},v:function(a){var b=a[0].slice(0);return b.sort(),b},w:function(a){var b=a[0].slice(0);if(0===b.length)return b;var c=this.a,d=a[1],e=this._(c.visit(d,b[0]));if([r,t].indexOf(e)<0)throw new Error("TypeError");for(var f=this,g=[],h=0;h<b.length;h++)g.push([h,b[h]]);g.sort(function(a,b){var g=c.visit(d,a[1]),h=c.visit(d,b[1]);if(f._(g)!==e)throw new Error("TypeError: expected "+e+", received "+f._(g));if(f._(h)!==e)throw new Error("TypeError: expected "+e+", received "+f._(h));return g>h?1:h>g?-1:a[0]-b[0]});for(var i=0;i<g.length;i++)b[i]=g[i][1];return b},n:function(a){for(var b,c,d=a[1],e=a[0],f=this.createKeyFunction(d,[r,t]),g=-(1/0),h=0;h<e.length;h++)c=f(e[h]),c>g&&(g=c,b=e[h]);return b},r:function(a){for(var b,c,d=a[1],e=a[0],f=this.createKeyFunction(d,[r,t]),g=1/0,h=0;h<e.length;h++)c=f(e[h]),g>c&&(g=c,b=e[h]);return b},createKeyFunction:function(a,b){var c=this,d=this.a,e=function(e){var f=d.visit(a,e);if(b.indexOf(c._(f))<0){var g="TypeError: expected one of "+b+", received "+c._(f);throw new Error(g)}return f};return e}},a.tokenize=o,a.compile=n,a.search=p,a.strictDeepEqual=d}("undefined"==typeof exports?this.jmespath={}:exports);
\ No newline at end of file
diff --git a/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js b/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js
index c1aa72c7576c4b3d8b1b6f493aedfd37b4149aef..900ff40320ab0bc297f3d0f593fbb7267d4405f1 100755
--- a/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js
+++ b/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js
@@ -240,7 +240,7 @@ oVFB.cleanFormToSave = function(oJson){
     if(oVFB.getApplication() !== "wab"){
         return oJson;
     }
-    
+
     var oForm;
 
     for (var sFormName in oJson) {
@@ -485,7 +485,7 @@ oVFB.setTreeDnD = function (str) {
 
 /**
  * getter
- * @return {string} Vitis's token 
+ * @return {string} Vitis's token
  * @export
  */
 oVFB.getToken = function () {
@@ -502,7 +502,7 @@ oVFB['setToken'] = function (token) {
 
 /**
  * getter
- * @return {string} Vitis's token 
+ * @return {string} Vitis's token
  * @export
  */
 oVFB.getId = function () {
@@ -719,7 +719,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) {
         'onLoad': function (cm_) {
             /*$scope.$watch("editedDatasource.dataType", function (value) {
              if (value === 'text') {
-             
+
              $timeout(function () {
              $scope.refresh = true;
              $timeout(function () {
@@ -852,7 +852,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) {
     $scope['sToken'] = sessionStorage['session_token'];
 
     /**
-     * 
+     *
      */
     $scope['wab'] = {};
 
@@ -1014,7 +1014,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) {
             }
 
             /**
-             * !Important! utilisation d'un pointeur permet que les changements 
+             * !Important! utilisation d'un pointeur permet que les changements
              * de oTestTabFormDefinition affecteront getJsonOutput
              */
             $scope['oTestTabFormDefinition'] = data;
@@ -1061,7 +1061,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) {
             }
 
             /**
-             * !Important! utilisation d'un pointeur permet que les changements 
+             * !Important! utilisation d'un pointeur permet que les changements
              * de oTestWabFormDefinition affecteront getJsonOutput
              */
             $scope['oTestWabFormDefinition'] = data;
@@ -1139,7 +1139,7 @@ oVFB.module.filter('sortStates', function(){
 oVFB.studioMainController.prototype.showTabsManagerModal = function () {
     oVFB.log('studioMainController.showTabsManagerModal');
 
-    // Modification du studio 
+    // Modification du studio
     oVFB.Update = true;
 
     var $scope = this.$scope_;
@@ -1214,7 +1214,7 @@ oVFB.studioMainController.prototype.generateWabMethod = function(){
                   });
 
                 // add label in lang.json string
-                oLangAddons[sLabelKeyMethod] = this.$scope_["aBoMethods"][i]["label"]; 
+                oLangAddons[sLabelKeyMethod] = this.$scope_["aBoMethods"][i]["label"];
             }
         }
     }
@@ -1632,7 +1632,7 @@ oVFB.studioMainController.prototype.updateTestWabForm = function () {
  * @export
  */
 oVFB.studioMainController.prototype.showDatasourcesModal = function () {
-    // Modification du studio 
+    // Modification du studio
     oVFB.Update = true;
     oVFB.log('studioMainController.showDatasourcesModal');
 
@@ -2207,6 +2207,51 @@ oVFB.studioMainController.prototype.addSource = function (newSource) {
         this.saveDatasource(source);
         this.setStudioGridDatasources();
 
+    } else if (newSource['dataType'] === 'externalAPI') {
+
+        if (!goog.isDef(newSource['id'])) {
+            console.error('newSource.id not defined');
+            return 0;
+        }
+        if (!goog.isDef(newSource['name'])) {
+            console.error('newSource.name not defined');
+            return 0;
+        }
+        if (goog.isDef($scope['datasources'][newSource['id']])) {
+            bootbox.alert(this['text']['Element']['Component']['Select']['Datasource_Exists']);
+            return 0;
+        }
+        if (!goog.isDef(newSource['api_url'])) {
+            bootbox.alert(this['text']['Element']['Component']['Select']['API_URL_undefined']);
+            console.error('newSource.api_url not defined');
+            return 0;
+        }
+        if (!goog.isDef(newSource['api_key'])) {
+            bootbox.alert(this['text']['Element']['Component']['Select']['API_key_undefined']);
+            console.error('newSource.api_key not defined');
+            return 0;
+        }
+        if (!goog.isDef(newSource['api_value'])) {
+            bootbox.alert(this['text']['Element']['Component']['Select']['API_value_undefined']);
+            console.error('newSource.api_value not defined');
+            return 0;
+        }
+
+        var source = {
+            'id': newSource['id'],
+            'type': 'external_API',
+            'dataType': newSource['dataType'],
+            'name': newSource['name'],
+            'description': newSource['description'],
+            'api_url': newSource['api_url'],
+            'api_key': newSource['api_key'],
+            'api_value': newSource['api_value']
+        };
+
+        $('#studio-datasource-add-modal').modal('hide');
+        this.saveDatasource(source);
+        this.setStudioGridDatasources();
+
     }
 };
 
@@ -2321,6 +2366,18 @@ oVFB.studioMainController.prototype.saveDatasource = function (datasource) {
             $scope['datasources'][datasource['id']]['subObject'] = 'bo_' + datasource['businessObjectRequest'];
             $scope['datasources'][datasource['id']]['ressource_id'] = 'vmap/querys/' + datasource['businessObject']['business_object_id'] + '/' + datasource['businessObjectRequest'];
         }
+    } else if (datasource['type'] === 'external_API') {
+
+        $scope['datasources'][datasource['id']] = {
+            'type': datasource['type'],
+            'dataType': datasource['dataType'],
+            'name': datasource['name'],
+            'description': datasource['description'],
+            'api_url': datasource['api_url'],
+            'api_key': datasource['api_key'],
+            'api_value': datasource['api_value']
+        };
+
     }
 
     if (datasource['dataType'] === 'externalDatabase') {
@@ -2557,6 +2614,8 @@ oVFB.studioMainController.prototype.testDatasource = function (datasource, modal
         this.testWebService(datasource, modalName);
     } else if (datasource['dataType'] === 'businessObject') {
         this.testBusinessObject(datasource, modalName);
+    } else if (datasource['dataType'] === 'externalAPI') {
+        this.testExternalAPI(datasource, modalName);
     }
 
 };
@@ -2694,6 +2753,87 @@ oVFB.studioMainController.prototype.testRequestAPI = function (url, params, grid
     });
 };
 
+/**
+ * Run a test for external API datasource
+ *
+ * @param  {object} datasource
+ * @param  {string} modalName
+ * @export
+ */
+oVFB.studioMainController.prototype.testExternalAPI = function (datasource, modalName) {
+    oVFB.log('studioMainController.testExternalAPI');
+
+    var this_ = this;
+
+    datasource['api_result_options'] = [];
+    datasource['api_result_data'] = '';
+    datasource['api_result_key'] = '';
+    datasource['api_result_value'] = '';
+
+    if (!goog.isDefAndNotNull(jmespath)) {
+        console.error('jmespath not defined');
+        return 0;
+    }
+    if (!goog.isObject(datasource)) {
+        console.error('datasource not defined');
+        return 0;
+    }
+    if (!goog.isDefAndNotNull(datasource['parameters'])) {
+        console.error('datasource.parameters not defined');
+        return 0;
+    }
+    if (!goog.isDefAndNotNull(datasource['api_url'])) {
+        console.error('datasource.api_url not defined');
+        return 0;
+    }
+
+    ajaxRequest({
+        'method': 'GET',
+        'url': datasource['api_url'],
+        'headers': {
+            'Accept': 'application/x-vm-json'
+        },
+        'scope': this.$scope_,
+        'success': function (response) {
+
+            if (!goog.isDef(response['data'])) {
+                bootbox.alert(this_['text']['Element']['Component']['Select']['NoDataReturned']);
+                return 0;
+            }
+
+            var aResult = response['data'];
+
+            // Résultat requête
+            datasource['api_result_data'] = JSON.stringify(aResult, null, 4);
+
+            // Résultat clé
+            if (goog.isDefAndNotNull(datasource['api_key'])) {
+                var aKeys = jmespath.search(aResult, datasource['api_key']);
+                datasource['api_result_key'] = JSON.stringify(aKeys, null, 4);
+            }
+
+            // Résultat valeur
+            if (goog.isDefAndNotNull(datasource['api_value'])) {
+                var aValues = jmespath.search(aResult, datasource['api_value']);
+                datasource['api_result_value'] = JSON.stringify(aValues, null, 4);
+            }
+
+            if (goog.isArray(aKeys) && goog.isArray(aValues)) {
+                if (aKeys.length === aValues.length) {
+                    for (var i = 0; i < aKeys.length; i++) {
+                        datasource['api_result_options'].push({
+                            'value': aKeys[i],
+                            'label': aValues[i]
+                        });
+                    }
+                } else {
+                    $.notify(this_['text']['Element']['Component']['Select']['Key_Values_not_matching'], 'error');
+                }
+            }
+        }
+    });
+}
+
 /**
  * Clear the datasources grid
  * @export
@@ -2901,7 +3041,7 @@ oVFB.studioMainController.prototype.moveBootboxModalToStudioContainer = function
         var aElement = oModal[0].parentNode.querySelectorAll(".modal-backdrop");
         for (var i = 0; i < aElement.length; i++) {
             document.getElementById("studio_container").appendChild(aElement[i]);
-        }    
+        }
         document.getElementById("studio_container").appendChild(oModal[0]);
     }
 };
@@ -2982,4 +3122,4 @@ oVFB.oVisualizer_ = new nsVFB.Visualizer();
 
 /***********************************************************************************
  *                                   End                                           *
- **********************************************************************************/
\ No newline at end of file
+ **********************************************************************************/
diff --git a/src/vitis/client/javascript/externs/studio/lang/lang-en.json b/src/vitis/client/javascript/externs/studio/lang/lang-en.json
index b6d34b3729e50d6508ea9665f3c1446ae7cec112..2a8607765144ea740a1a942c6b65707e030622e0 100755
--- a/src/vitis/client/javascript/externs/studio/lang/lang-en.json
+++ b/src/vitis/client/javascript/externs/studio/lang/lang-en.json
@@ -242,6 +242,16 @@
                 "Table": "Table",
                 "Column": "Column",
                 "Server": "Hostname of server",
+                "API_URL": "URL",
+                "API_Key": "Key",
+                "API_Value": "Value",
+                "API_Request_Data": "Request result",
+                "API_Request_Key": "Key result",
+                "API_Request_Value": "Value result",
+                "API_Key_Values_not_matching": "The number of keys is different than the number of values",
+                "API_URL_undefined": "API URL not defined",
+                "API_key_undefined": "API key not defined",
+                "API_value_undefined": "API value not defined",
                 "Port": "Port",
                 "SGBD": "SGBD",
                 "Login": "Login",
@@ -268,6 +278,7 @@
                 "Select_Webservice": "Web service",
                 "Select_Table_Values": " Local table values",
                 "Select_External_Database": "External database",
+                "Select_External_API": "External API",
                 "Webservice_Class": "API",
                 "Webservice": "Web Service",
                 "Ressource": "Ressource",
diff --git a/src/vitis/client/javascript/externs/studio/lang/lang-fr.json b/src/vitis/client/javascript/externs/studio/lang/lang-fr.json
index ddaad2891c0fd4177698f0341e3e075d915290b3..bb90dd51670f0baf1ea619c0d44a06a62d86e815 100755
--- a/src/vitis/client/javascript/externs/studio/lang/lang-fr.json
+++ b/src/vitis/client/javascript/externs/studio/lang/lang-fr.json
@@ -250,6 +250,16 @@
                 "Table": "Table",
                 "Column": "Column",
                 "Server": "Adresse du serveur",
+                "API_URL": "URL",
+                "API_Key": "Clé",
+                "API_Value": "Valeur",
+                "API_Request_Data": "Résulstat requête",
+                "API_Request_Key": "Clé",
+                "API_Request_Value": "Valeur",
+                "Key_Values_not_matching": "Le nombre de clés est différent du combre de valeurs",
+                "API_URL_undefined": "URL non valide",
+                "API_key_undefined": "Clé non valide",
+                "API_value_undefined": "Valeur non valide",
                 "Port": "Port",
                 "SGBD": "SGBD",
                 "Login": "Login",
@@ -276,6 +286,7 @@
                 "Select_Webservice": "Service web",
                 "Select_Table_Values": "Valeurs d'une table locale",
                 "Select_External_Database": "Base de données externe",
+                "Select_External_API": "API externe",
                 "Webservice_Class": "API",
                 "Ressource": "Ressource",
                 "Webservice": "Service web",
diff --git a/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html b/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html
index 2bc6e1b92a74a3879de8b3af5bee0c2cd7957289..6572d447c1734109166f928a077b031cab0dba78 100755
--- a/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html
+++ b/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html
@@ -41,16 +41,11 @@
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label>
                                             <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled>
                                         </div>
-                                        <!--Description-->
-                                        <!--<div class="input-group element-margin input-group-xs width-100">
-                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                            <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled>
-                                        </div>-->
                                         <!--Contenu-->
                                         <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Content}}"></label>
                                         <div>
                                             <i>{{::ctrl.text.Element.Component.Select.Content_description}}</i>
-                                        </div>                                    
+                                        </div>
                                         <textarea style="width: 398px; height: 132px; cursor: not-allowed" ng-model="selectedDatasource.options_model" disabled></textarea>
                                     </form>
                                 </div>
@@ -68,11 +63,6 @@
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label>
                                             <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled>
                                         </div>
-                                        <!--Description-->
-                                        <!--<div class="input-group element-margin input-group-xs width-100">
-                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                            <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled>
-                                        </div>-->
                                         <!--Database-->
                                         <div class="input-group element-margin input-group-xs width-100">
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Database}}</label>
@@ -104,11 +94,6 @@
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label>
                                             <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled>
                                         </div>
-                                        <!--description-->
-                                        <!--<div class="input-group element-margin input-group-xs width-100">
-                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                            <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled>
-                                        </div>-->
                                         <!--web service-->
                                         <div class="input-group element-margin input-group-xs width-100">
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Webservice}}</label>
@@ -135,11 +120,6 @@
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label>
                                             <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled>
                                         </div>
-                                        <!--description-->
-                                        <!--<div class="input-group element-margin input-group-xs width-100">
-                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                            <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled>
-                                        </div>-->
                                         <!--Objet métier-->
                                         <div class="input-group element-margin input-group-xs width-100">
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.BusinessObject}}</label>
@@ -166,11 +146,6 @@
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label>
                                             <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled>
                                         </div>
-                                        <!--Description-->
-                                        <!--<div class="input-group element-margin input-group-xs width-100">
-                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                            <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled>
-                                        </div>-->
                                         <!--Server-->
                                         <div class="input-group element-margin input-group-xs width-100">
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Server}}</label>
@@ -195,7 +170,7 @@
                                         <div class="input-group element-margin input-group-xs width-100">
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Password}}</label>
                                             <input type="text" class="form-control" ng-model="selectedDatasource.parameters.password" disabled>
-                                        </div>                                    
+                                        </div>
                                         <!--Database-->
                                         <div class="input-group element-margin input-group-xs width-100">
                                             <label class="control-label">{{::ctrl.text.Element.Component.Select.Database}}</label>
@@ -218,6 +193,37 @@
                                         </div>
                                     </form>
                                 </div>
+                                <!-- externalAPI -->
+                                <div ng-show="selectedDatasource.dataType === 'externalAPI'">
+                                    <h4>{{::ctrl.text.Element.Component.Select.Select_External_API}}</h4>
+                                    <form class="element-margin form-group-xs">
+                                        <!--ID-->
+                                        <div ng-show="false" class="input-group element-margin input-group-xs width-100">
+                                            <label class="control-label">ID</label>
+                                            <input type="text" class="form-control" ng-model="selectedDatasource.id" disabled>
+                                        </div>
+                                        <!--Nom-->
+                                        <div class="input-group element-margin input-group-xs width-100">
+                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label>
+                                            <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled>
+                                        </div>
+                                        <!--URL-->
+                                        <div class="input-group element-margin input-group-xs width-100">
+                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.API_URL}}</label>
+                                            <input type="text" class="form-control" ng-model="selectedDatasource.api_url" disabled>
+                                        </div>
+                                        <!--Key-->
+                                        <div class="input-group element-margin input-group-xs width-100">
+                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Key}}</label>
+                                            <input type="text" class="form-control" ng-model="selectedDatasource.api_key" disabled>
+                                        </div>
+                                        <!--Value-->
+                                        <div class="input-group element-margin input-group-xs width-100">
+                                            <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Value}}</label>
+                                            <input type="text" class="form-control" ng-model="selectedDatasource.api_value" disabled>
+                                        </div>
+                                    </form>
+                                </div>
                             </div>
                             <div class="col-sm-6">
                                 <div style="height: 40px">
@@ -235,7 +241,7 @@
                         </div>
                     </div>
                 </div>
-            </div>  
+            </div>
         </div>
         <!--Modale édition source de donnees-->
         <div id="studio-datasource-edit-modal" class="modal fade" role="dialog">
@@ -262,35 +268,29 @@
                                            ng-model="editedDatasource.name"
                                            required>
                                 </div>
-                                <!--Description-->
-                                <!--<div class="input-group element-margin input-group-xs width-100">
-                                    <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                    <input type="text" class="form-control"
-                                           ng-model="editedDatasource.description">
-                                       </div>-->
                                 <!--text-->
-                                <div id="textDatasourceAddform" class="col-xs-12" ng-if="editedDatasource.dataType === 'text'">
+                                <div id="textDatasourceEditForm" class="col-xs-12" ng-if="editedDatasource.dataType === 'text'">
                                     <!--Contenu-->
                                     <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Content}}"></label>
                                     <div>
                                         <i>{{::ctrl.text.Element.Component.Select.Content_description}}</i>
-                                    </div>                                
-                                    <ui-codemirror 
+                                    </div>
+                                    <ui-codemirror
                                         id="textOptionsCodeMirrorEdit"
                                         ui-refresh="refresh"
-                                        ui-codemirror-opts="editorOptions" 
+                                        ui-codemirror-opts="editorOptions"
                                         ng-model="editedDatasource.options_model"
                                         ng-change="ctrl.updateDatasourceTextContent(editedDatasource)"></ui-codemirror>
                                 </div>
                                 <!--tableValue-->
-                                <div id="tableValueDatasourceAddform" ng-if="editedDatasource.dataType === 'tableValue'">
+                                <div id="tableValueDatasourceEditForm" ng-if="editedDatasource.dataType === 'tableValue'">
                                     <!--Base-->
                                     <div class="element-margin form-group-xxs col-xs-5">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.Database}}</label>
                                         <div class="row">
                                             <div class="col-sm-8">
                                                 <select type="text" class="form-control"
-                                                        ng-options="database.database for database in databases| orderBy: 'database' track by database.database"  
+                                                        ng-options="database.database for database in databases| orderBy: 'database' track by database.database"
                                                         ng-model="$parent.databaseSelected"
                                                         ng-change="editedDatasource.parameters.database = databaseSelected.database" required></select>
                                             </div>
@@ -311,7 +311,7 @@
                                                        ng-model="editedDatasource.parameters.schema">
                                                 <select type="text" class="form-control"
                                                         ng-show="schemas.length > 0"
-                                                        ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name" 
+                                                        ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name"
                                                         ng-model="$parent.schemaSelected"
                                                         ng-change="editedDatasource.parameters.schema = schemaSelected.schema_name" required></select>
                                             </div>
@@ -335,7 +335,7 @@
                                                         ng-options="table.table_name for table in tables| orderBy: 'table_name' track by table.table_name"
                                                         ng-model="$parent.tableSelected"
                                                         ng-change="editedDatasource.parameters.table = tableSelected.table_name"
-                                                        required>      
+                                                        required>
                                                 </select>
                                             </div>
                                         </div>
@@ -395,13 +395,13 @@
                                     </div>
                                 </div>
                                 <!--webService-->
-                                <div id="webServiceDatasourceAddform" ng-if="editedDatasource.dataType === 'webService'">
+                                <div id="webServiceDatasourceEditForm" ng-if="editedDatasource.dataType === 'webService'">
                                     <!--Webservice-->
                                     <div class="element-margin form-group-xxs col-xs-6">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.Webservice}}</label>
                                         <select class="form-control element-margin"
                                                 ng-model="editedDatasource.webservice"
-                                                ng-change="ctrl.updateRessources(editedDatasource.webservice)"  
+                                                ng-change="ctrl.updateRessources(editedDatasource.webservice)"
                                                 ng-options="webservice.name for webservice in webservices track by webservice.name"
                                                 required></select>
                                     </div>
@@ -409,18 +409,18 @@
                                     <div class="element-margin form-group-xxs col-xs-6">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.Ressource}}</label>
                                         <select class="form-control element-margin"
-                                                ng-model="editedDatasource.ressource" 
+                                                ng-model="editedDatasource.ressource"
                                                 ng-options="ressource.name for ressource in ressources track by ressource.name"
                                                 required></select>
                                     </div>
                                 </div>
                                 <!--businessObject-->
-                                <div id="businessObjectDatasourceAddform" ng-if="editedDatasource.dataType === 'businessObject'">
+                                <div id="businessObjectDatasourceEditForm" ng-if="editedDatasource.dataType === 'businessObject'">
                                     <!--Business Object-->
                                     <div class="element-margin form-group-xxs col-xs-6">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.BusinessObject}}</label>
                                         <select class="form-control element-margin"
-                                                ng-model="editedDatasource.businessObject" 
+                                                ng-model="editedDatasource.businessObject"
                                                 ng-options="businessObjects.business_object_id for businessObjects in businessObjects track by businessObjects.business_object_id"
                                                 required></select>
                                     </div>
@@ -437,7 +437,7 @@
                                     <textarea style="width:calc(100% - 30px);height: 63px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.businessObject['sql_' + editedDatasource.businessObjectRequest]" disabled></textarea>
                                 </div>
                                 <!--externalDatabase-->
-                                <div id="externalDatabaseDatasourceAddform" ng-if="editedDatasource.dataType === 'externalDatabase'">
+                                <div id="externalDatabaseDatasourceEditForm" ng-if="editedDatasource.dataType === 'externalDatabase'">
                                     <!--Serveur-->
                                     <div class="element-margin form-group-xxs col-xs-6">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.Server}}</label>
@@ -456,8 +456,8 @@
                                     <div class="element-margin form-group-xxs col-xs-3">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.SGBD}}</label>
                                         <select class="form-control" ng-model="editedDatasource.parameters.sgbd" required>
-                                            <option value="pgsql" label="Postgres">Postgres</option>     
-                                            <option value="oci" label="Oracle">Oracle</option>  
+                                            <option value="pgsql" label="Postgres">Postgres</option>
+                                            <option value="oci" label="Oracle">Oracle</option>
                                         </select>
                                     </div>
                                     <!--Login-->
@@ -567,6 +567,33 @@
                                         </div>
                                     </div>
                                 </div>
+                                <!-- externalAPI -->
+                                <div id="externalAPIDatasourceEditForm" ng-if="editedDatasource.dataType === 'externalAPI'">
+                                    <!-- URL -->
+                                    <div class="element-margin form-group-xxs col-xs-12">
+                                        <label class="control-label">{{::ctrl.text.Element.Component.Select.API_URL}}</label>
+                                        <input type="text" class="form-control"
+                                               ng-model="editedDatasource.api_url"
+                                               placeholder="https://api-adresse.data.gouv.fr/search/?q=Paris&limit=100"
+                                               required>
+                                    </div>
+                                    <!-- clé -->
+                                    <div class="element-margin form-group-xxs col-xs-6">
+                                        <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Key}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label>
+                                        <input type="text" class="form-control"
+                                               ng-model="editedDatasource.api_key"
+                                               placeholder="features[*].properties.name"
+                                               required>
+                                    </div>
+                                    <!-- valeur -->
+                                    <div class="element-margin form-group-xxs col-xs-6">
+                                        <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Value}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label>
+                                        <input type="text" class="form-control"
+                                               ng-model="editedDatasource.api_value"
+                                               placeholder="features[*].properties.name"
+                                               required>
+                                    </div>
+                                </div>
                                 <!--Fenêtre de test-->
                                 <div class="col-xs-12" id="Layout_Preview">
                                     <!--Text-->
@@ -583,8 +610,8 @@
                                                 {{editedDatasource.test_result.value}}
                                             </div>
                                             <div class="col-xs-3">
-                                                <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'" 
-                                                        type="submit" 
+                                                <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'"
+                                                        type="submit"
                                                         class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button>
                                             </div>
                                         </div>
@@ -594,8 +621,8 @@
                                         <!--Test-->
                                         <div>
                                             <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(editedDatasource, 'studio-datasource-edit-modal')">Test</button>
-                                            <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'" 
-                                                    type="submit" 
+                                            <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'"
+                                                    type="submit"
                                                     class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button>
                                         </div>
                                         <div><label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label></div>
@@ -603,12 +630,53 @@
                                             <div id="grid-studio-webService-test" ui-grid="studioEditModalTestGridOptions" style="width:856px;height: 350px;" class="grid element-margin"></div>
                                         </div>
                                     </div>
+                                    <!-- API externe -->
+                                    <div ng-show="editedDatasource.dataType === 'externalAPI'">
+                                        <div>
+                                            <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(editedDatasource, 'studio-datasource-add-modal')">Test</button>
+                                            <button ng-if="editedDatasource.dataType === 'externalAPI'"
+                                                    type="submit"
+                                                    class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button>
+                                        </div>
+
+                                        <div class="row" style="margin-top: -25px;">
+                                            <div class="col-xs-offset-2 col-xs-8">
+                                                <div class="col-xs-3 form-group-xxs text-right">
+                                                    <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label>
+                                                </div>
+                                                <div class="form-group-xxs col-xs-6">
+                                                    <select class="element-margin form-control"
+                                                            ng-model="editedDatasource.api_test_result"
+                                                            ng-options="option.label for option in editedDatasource.api_result_options track by option.value">
+                                                    </select>
+                                                </div>
+                                                <div class="col-xs-3">
+                                                    {{editedDatasource.api_test_result.value}}
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                        <div class="">
+                                            <div class="col-xs-6 padding-sides-0">
+                                                <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Data}}"></label>
+                                                <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.api_result_data" disabled></textarea>
+                                            </div>
+                                            <div class="col-xs-3 padding-sides-0">
+                                                <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Key}}"></label>
+                                                <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.api_result_key" disabled></textarea>
+                                            </div>
+                                            <div class="col-xs-3 padding-sides-0">
+                                                <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Value}}"></label>
+                                                <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.api_result_value" disabled></textarea>
+                                            </div>
+                                        </div>
+                                    </div>
                                 </div>
                             </form>
                         </div>
                     </div>
                 </div>
-            </div>  
+            </div>
         </div>
         <!--Modale ajout source de données-->
         <div id="studio-datasource-add-modal" class="modal fade" role="dialog">
@@ -631,6 +699,7 @@
                                     <option value="webService">{{::ctrl.text.Element.Component.Select.Select_Webservice}}</option>
                                     <option ng-if="applicationName !== 'gtf'" value="businessObject">{{::ctrl.text.Element.Component.Select.Select_Business_Object}}</option>
                                     <option value="externalDatabase">{{::ctrl.text.Element.Component.Select.Select_External_Database}}</option>
+                                    <option value="externalAPI">{{::ctrl.text.Element.Component.Select.Select_External_API}}</option>
                                 </select>
                                 <hr>
                             </div>
@@ -651,23 +720,17 @@
                                            ng-model="newSource.name"
                                            required>
                                 </div>
-                                <!--Description-->
-                                <!--<div class="input-group element-margin input-group-xs width-100">
-                                    <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label>
-                                    <input type="text" class="form-control"
-                                           ng-model="newSource.description">
-                                       </div>-->
                                 <!--text-->
                                 <div id="textDatasourceAddform" class="col-xs-12" ng-if="newSource.dataType === 'text'">
                                     <!--Contenu-->
                                     <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Content}}"></label>
                                     <div>
                                         <i>{{::ctrl.text.Element.Component.Select.Content_description}}</i>
-                                    </div>                                
-                                    <ui-codemirror 
+                                    </div>
+                                    <ui-codemirror
                                         id="textOptionsCodeMirrorAdd"
                                         ui-refresh="newSource.dataType === 'text'"
-                                        ui-codemirror-opts="editorOptions" 
+                                        ui-codemirror-opts="editorOptions"
                                         ng-model="newSource.options_model"
                                         ng-change="ctrl.updateDatasourceTextContent(newSource)"></ui-codemirror>
                                 </div>
@@ -680,7 +743,7 @@
                                             <div class="col-sm-8">
                                                 <select type="text" class="form-control"
                                                         ng-init="ctrl.setLocalDatabases(newSource.parameters.database)"
-                                                        ng-options="database.database for database in databases| orderBy: 'database' track by database.database"  
+                                                        ng-options="database.database for database in databases| orderBy: 'database' track by database.database"
                                                         ng-model="$parent.databaseSelected"
                                                         ng-change="newSource.parameters.database = databaseSelected.database" required></select>
                                             </div>
@@ -701,7 +764,7 @@
                                                        ng-model="newSource.parameters.schema">
                                                 <select type="text" class="form-control"
                                                         ng-show="schemas.length > 0"
-                                                        ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name" 
+                                                        ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name"
                                                         ng-model="$parent.schemaSelected"
                                                         ng-change="newSource.parameters.schema = schemaSelected.schema_name" required></select>
                                             </div>
@@ -725,7 +788,7 @@
                                                         ng-options="table.table_name for table in tables| orderBy: 'table_name' track by table.table_name"
                                                         ng-model="$parent.tableSelected"
                                                         ng-change="newSource.parameters.table = tableSelected.table_name"
-                                                        required>      
+                                                        required>
                                                 </select>
                                             </div>
                                         </div>
@@ -791,7 +854,7 @@
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.Webservice}}</label>
                                         <select class="form-control element-margin"
                                                 ng-model="newSource.webservice"
-                                                ng-change="ctrl.updateRessources(newSource.webservice)"  
+                                                ng-change="ctrl.updateRessources(newSource.webservice)"
                                                 ng-options="webservice.name for webservice in webservices track by webservice.name"
                                                 required></select>
                                     </div>
@@ -799,7 +862,7 @@
                                     <div class="element-margin form-group-xxs col-xs-6">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.Ressource}}</label>
                                         <select class="form-control element-margin"
-                                                ng-model="newSource.ressource" 
+                                                ng-model="newSource.ressource"
                                                 ng-options="ressource.name for ressource in ressources track by ressource.name"
                                                 required></select>
                                     </div>
@@ -810,7 +873,7 @@
                                     <div class="element-margin form-group-xxs col-xs-6">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.BusinessObject}}</label>
                                         <select class="form-control element-margin"
-                                                ng-model="newSource.businessObject" 
+                                                ng-model="newSource.businessObject"
                                                 ng-options="businessObjects.business_object_id for businessObjects in businessObjects track by businessObjects.business_object_id"
                                                 required></select>
                                     </div>
@@ -846,8 +909,8 @@
                                     <div class="element-margin form-group-xxs col-xs-3">
                                         <label class="control-label">{{::ctrl.text.Element.Component.Select.SGBD}}</label>
                                         <select class="form-control" ng-model="newSource.parameters.sgbd" required>
-                                            <option value="pgsql" label="Postgres">Postgres</option>     
-                                            <option value="oci" label="Oracle">Oracle</option>  
+                                            <option value="pgsql" label="Postgres">Postgres</option>
+                                            <option value="oci" label="Oracle">Oracle</option>
                                         </select>
                                     </div>
                                     <!--Login-->
@@ -957,6 +1020,33 @@
                                         </div>
                                     </div>
                                 </div>
+                                <!-- externalAPI -->
+                                <div id="externalAPIDatasourceAddform" ng-if="newSource.dataType === 'externalAPI'">
+                                    <!-- URL -->
+                                    <div class="element-margin form-group-xxs col-xs-12">
+                                        <label class="control-label">{{::ctrl.text.Element.Component.Select.API_URL}}</label>
+                                        <input type="text" class="form-control"
+                                               ng-model="newSource.api_url"
+                                               placeholder="https://api-adresse.data.gouv.fr/search/?q=Paris&limit=100"
+                                               required>
+                                    </div>
+                                    <!-- clé -->
+                                    <div class="element-margin form-group-xxs col-xs-6">
+                                        <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Key}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label>
+                                        <input type="text" class="form-control"
+                                               ng-model="newSource.api_key"
+                                               placeholder="features[*].properties.name"
+                                               required>
+                                    </div>
+                                    <!-- valeur -->
+                                    <div class="element-margin form-group-xxs col-xs-6">
+                                        <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Value}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label>
+                                        <input type="text" class="form-control"
+                                               ng-model="newSource.api_value"
+                                               placeholder="features[*].properties.name"
+                                               required>
+                                    </div>
+                                </div>
                                 <!--Fenêtre de test-->
                                 <div class="col-xs-12" id="Layout_Preview">
                                     <!--Text-->
@@ -973,10 +1063,10 @@
                                                 {{newSource.test_result.value}}
                                             </div>
                                             <div class="col-xs-3">
-                                                <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'" 
-                                                        type="submit" 
+                                                <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'"
+                                                        type="submit"
                                                         class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button>
-                                            </div> 
+                                            </div>
                                         </div>
                                     </div>
                                     <!--Valeurs d'une table-->
@@ -984,8 +1074,8 @@
                                         <!--Test-->
                                         <div>
                                             <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(newSource, 'studio-datasource-add-modal')">Test</button>
-                                            <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'" 
-                                                    type="submit" 
+                                            <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'"
+                                                    type="submit"
                                                     class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button>
                                         </div>
                                         <div><label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label></div>
@@ -993,6 +1083,47 @@
                                             <div id="grid-studio-webService-test" ui-grid="studioAddModalTestGridOptions" style="width:856px;height: 350px;" class="grid element-margin"></div>
                                         </div>
                                     </div>
+                                    <!-- API externe -->
+                                    <div ng-show="newSource.dataType === 'externalAPI'">
+                                        <div>
+                                            <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(newSource, 'studio-datasource-add-modal')">Test</button>
+                                            <button ng-if="newSource.dataType === 'externalAPI'"
+                                                    type="submit"
+                                                    class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button>
+                                        </div>
+
+                                        <div class="row" style="margin-top: -25px;">
+                                            <div class="col-xs-offset-2 col-xs-8">
+                                                <div class="col-xs-3 form-group-xxs text-right">
+                                                    <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label>
+                                                </div>
+                                                <div class="form-group-xxs col-xs-6">
+                                                    <select class="element-margin form-control"
+                                                            ng-model="newSource.api_test_result"
+                                                            ng-options="option.label for option in newSource.api_result_options track by option.value">
+                                                    </select>
+                                                </div>
+                                                <div class="col-xs-3">
+                                                    {{newSource.api_test_result.value}}
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                        <div class="">
+                                            <div class="col-xs-6 padding-sides-0">
+                                                <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Data}}"></label>
+                                                <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="newSource.api_result_data" disabled></textarea>
+                                            </div>
+                                            <div class="col-xs-3 padding-sides-0">
+                                                <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Key}}"></label>
+                                                <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="newSource.api_result_key" disabled></textarea>
+                                            </div>
+                                            <div class="col-xs-3 padding-sides-0">
+                                                <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Value}}"></label>
+                                                <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="newSource.api_result_value" disabled></textarea>
+                                            </div>
+                                        </div>
+                                    </div>
                                 </div>
                             </form>
                         </div>
@@ -1019,7 +1150,7 @@
 
                                             <div class="input-group input-group-xs margin-right-20" ng-show="tab.studio_mode === 'edit'">
                                                 <input type="text" class="form-control" ng-model="tab.label">
-                                                <span class="input-group-addon btn btn-primary btn-xs glyphicon glyphicon-ok" 
+                                                <span class="input-group-addon btn btn-primary btn-xs glyphicon glyphicon-ok"
                                                       style="min-width: inherit;"
                                                       ng-click="tab.studio_mode = 'display'"></span>
                                             </div>
@@ -1077,7 +1208,7 @@
                                         <td style="width:"
                                             ng-repeat="(tabIndex, tab) in tabs.list">
                                             <div class="checkbox margin-0">
-                                                <input id="studio-tabsmanager-modal-show-elem-checkbox-{{elemIndex}}-{{tabIndex}}" 
+                                                <input id="studio-tabsmanager-modal-show-elem-checkbox-{{elemIndex}}-{{tabIndex}}"
                                                        type="checkbox"
                                                        ng-checked="tab.elements.indexOf(elem) !== -1"
                                                        ng-click="ctrl.toggleElemOnTab(elem, tab)">
@@ -1091,9 +1222,9 @@
                                 <h4 class="modal-title ng-binding" style="margin-bottom: 15px;">Prévisualisation</h4>
                                 <!--Prévisualisation-->
                                 <div id="studio_test_tabs_form_reader"
-                                     app-form-reader 
-                                     app-form-definition-name="sTestTabFormDefinitionName" 
-                                     app-form-values="oTestTabFormValues" 
+                                     app-form-reader
+                                     app-form-definition-name="sTestTabFormDefinitionName"
+                                     app-form-values="oTestTabFormValues"
                                      app-form-definition="oTestTabFormDefinition"
                                      app-form-events-container="oFormScope"
                                      app-properties="oProperties"
@@ -1107,7 +1238,7 @@
                         <button type="button" class="btn btn-default right margin-left-20" onclick="$('#studio-tabsmanager-modal').modal('hide');">{{::ctrl.text.Loader.Cancel}}</button>
                     </div>
                 </div>
-            </div>  
+            </div>
         </div>
         <!--Modale gestion état wab-->
         <div id="studio-wabmanager-modal" class="modal fade" role="dialog">
@@ -1126,8 +1257,8 @@
                                 </select>
                             </div>
                             <div class="transition-form-wab-collapser" ng-class="{'col-xs-2' : !wabVisualizer, 'col-xs-7' : wabVisualizer}"></div>
-                            <div class="col-xs-2" style="text-align: right;">  
-                                <span class=" collapser-form-wab-modal" ng-click="wabVisualizer = !wabVisualizer" ng-class="{'icon-keyboard_arrow_right' : !wabVisualizer, 'icon-keyboard_arrow_left' : wabVisualizer}"></span>                            
+                            <div class="col-xs-2" style="text-align: right;">
+                                <span class=" collapser-form-wab-modal" ng-click="wabVisualizer = !wabVisualizer" ng-class="{'icon-keyboard_arrow_right' : !wabVisualizer, 'icon-keyboard_arrow_left' : wabVisualizer}"></span>
                             </div>
                             <div ng-show="!wabVisualizer" class="transition-form-wab-collapser margin-left-15" ng-class="{'col-xs-3' : !wabVisualizer}">
                                 <label>{{::ctrl.text.Loader.WabState}}</label>
@@ -1137,7 +1268,7 @@
                             </div>
                         </div>
                         <br>
-                        <div class="row" 
+                        <div class="row"
                              style="height: calc(100% - 70px);"
                              ng-if="wabSelectedGroup !== ''">
                             <div class="height-100 table-responsive studio-table-fixed transition-form-wab-collapser" ng-class="{'col-xs-7' : !wabVisualizer, 'col-xs-12' : wabVisualizer}">
@@ -1158,7 +1289,7 @@
                                                 ng-repeat="(stateName, oState) in wab|sortStates:wabStates track by $index">
                                                 <span class="checkbox margin-0 left checkbox-info"
                                                       title="{{::ctrl.text.Loader.AllowDisplayElement}}">
-                                                    <input id="studio-wabmanager-modal-display-elem-checkbox-{{elemIndex}}-{{oState.name}}" 
+                                                    <input id="studio-wabmanager-modal-display-elem-checkbox-{{elemIndex}}-{{oState.name}}"
                                                            type="checkbox"
                                                            ng-checked="oState[wabSelectedGroup][elem] === 'r' || oState[wabSelectedGroup][elem] === 'rw'"
                                                            ng-click="ctrl.toggleElemOnWab(elem, oState, wabSelectedGroup, 'r')">
@@ -1166,7 +1297,7 @@
                                                 </span>
                                                 <span class="checkbox margin-0 left checkbox-primary"
                                                       title="{{::ctrl.text.Loader.AllowEditElement}}">
-                                                    <input id="studio-wabmanager-modal-edit-elem-checkbox-{{elemIndex}}-{{oState.name}}" 
+                                                    <input id="studio-wabmanager-modal-edit-elem-checkbox-{{elemIndex}}-{{oState.name}}"
                                                            type="checkbox"
                                                            ng-checked="oState[wabSelectedGroup][elem] === 'rw'"
                                                            ng-click="ctrl.toggleElemOnWab(elem, oState, wabSelectedGroup, 'rw')">
@@ -1180,9 +1311,9 @@
                             <div ng-show="!wabVisualizer" class="height-100 transition-form-wab-collapser" style="overflow-x: auto" ng-class="{'col-xs-5' : !wabVisualizer}">
                                 <!--Prévisualisation-->
                                 <div id="studio_test_wab_form_reader"
-                                     app-form-reader 
-                                     app-form-definition-name="sTestWabFormDefinitionName" 
-                                     app-form-values="oTestWabFormValues" 
+                                     app-form-reader
+                                     app-form-definition-name="sTestWabFormDefinitionName"
+                                     app-form-values="oTestWabFormValues"
                                      app-form-definition="oTestWabFormDefinition"
                                      app-form-events-container="oTestWabFormScope"
                                      app-properties="oProperties"
@@ -1197,7 +1328,7 @@
                         <button type="button" class="btn btn-default right margin-left-20" onclick="$('#studio-wabmanager-modal').modal('hide');">{{::ctrl.text.Loader.Cancel}}</button>
                     </div>
                 </div>
-            </div>  
+            </div>
         </div>
         <!--Modale gestion méthode wab-->
         <div id="studio-wabmanager-method-modal" class="modal fade" role="dialog">
@@ -1211,7 +1342,7 @@
                         <div ng-repeat="oMethod in aBoMethods" class="row" style="margin-left: 5px;">
                            <span class="checkbox margin-0 left checkbox-primary col-xs-5"
                                     title="{{::ctrl.text.Loader.AllowEditElement}}">
-                                <input id="studio-wabmanager-modal-edit-method-checkbox-{{$index}}-{{oMethod.name}}" 
+                                <input id="studio-wabmanager-modal-edit-method-checkbox-{{$index}}-{{oMethod.name}}"
                                     type="checkbox"
                                     ng-model="oMethod['selected']">
                                 <label for="studio-wabmanager-modal-edit-method-checkbox-{{$index}}-{{oMethod.name}}" class="control-label">{{oMethod.name}} ({{oMethod.tostate_name}})</label>
@@ -1232,7 +1363,7 @@
                         <button type="button" class="btn btn-default right margin-left-20" onclick="$('#studio-wabmanager-method-modal').modal('hide');">{{::ctrl.text.Loader.Cancel}}</button>
                     </div>
                 </div>
-            </div>  
+            </div>
         </div>
     </div>
 </div>