docs: function APICall(config) {
      this.config = merge({}, defaultConfig, config);
      this._events = {};
  
      var agent = 'JS/' + version;
      var opts = this.config.options;
      if (opts.appname) {
        agent += ' ' + opts.appname.replace(agentInvalid, '_');
        if (opts.appversion) {
          agent += '/' + opts.appversion.replace(agentInvalid, '_');
        }
      }
  
      // Fields that are available at the completion of the api call.
      this.additionalData = this.config.callbackData;
      this.data = null;
      this.hasErrors = false;
      this.requestData = this.config.data;
      this.requestHeaders = {
        'X-CloudMine-ApiKey': opts.apikey,
        'X-CloudMine-Agent': agent,
        'X-CloudMine-UT': opts.user_token
      };
  
        /**
         * I am adding an else here to account for the adding back the old set webservice method. I don't know if the global
         * object is even used anymore but if for some strange reason the xuniqueid is not set in the option
         * we can possibly get it from the global obj ... if global is set
         */
      if (opts.xuniqueid) {
        this.requestHeaders['X-Unique-Id'] = opts.xuniqueid;
      } else {
          if (typeof(global) != 'undefined' && typeof global._$XUniqueID != 'undefined') {
              this.requestHeaders['X-Unique-Id'] = global._$XUniqueID;
          }
      }
  
      this.responseHeaders = {};
      this.responseText = null;
      this.status = null;
      this.type = this.config.type || 'GET';
  
      // Build the URL and headers
      var query = stringify(server_params(opts, this.config.query));
      var root = '/', session = opts.session_token, applevel = opts.applevel;
      if (applevel === false || (applevel !== true && session != null)) {
        if (config.action.split('/')[0] !== 'account'){
          root = '/user/';
        }
        if (session != null) this.requestHeaders['X-CloudMine-SessionToken'] = session;
      }
  
      // Merge in headers in case-insensitive (if necessary) manner.
      for (var key in this.config.headers) {
        mapInsensitive(this.requestHeaders, key, this.config.headers[key]);
      }
  
      this.config.headers = this.requestHeaders;
  
      if (!isEmptyObject(perfComplete)) {
        this.config.headers['X-CloudMine-UT'] += ';' + stringify(perfComplete, ':', ',', null, PERF_HEADER_LIMIT);
        perfComplete = {};
      }
  
      this.setContentType(config.contentType || 'application/json');
      var endpointVersion = this.config.options.version || 'v1';
      var versionPath = '/' + endpointVersion + '/app/';
      this.url = [this.config.options.apiroot, versionPath, this.config.options.appid, root, this.config.action].join("");
  
      var sep = this.url.indexOf('?') === -1 ? '?' : '&';
      this.url = [this.url, (query ? sep + query : "")].join("");
  
      var self = this, sConfig = this.config, timestamp = +(new Date);
      /** @private */
      this.config.complete = function(xhr, status) {
        var data;
        if (xhr) {
          data = xhr.responseText
          self.status = xhr.status;
          self.responseText = data;
          self.responseHeaders = unstringify(xhr.getAllResponseHeaders(), /:\s*/, /(?:\r|\n)?\n/);
  
          // Performance metrics, if applicable.
          var requestId = mapInsensitive(self.responseHeaders, 'X-Request-Id');
          if (requestId) perfComplete[requestId] = +(new Date) - timestamp;
  
          // If we can parse the data as JSON or store the original data.
          try {
            self.data = JSON.parse(data || "{}");
          } catch (e) {
            self.data = data;
          }
        } else {
          self.status = 'abort';
          self.data = [ sConfig.data ];
        }
  
        // Parse the response only if a safe status
        if (status == 'success' && self.status >= 200 && self.status < 300) {
          // Preprocess data coming in to hash-hash: [success/errors].[httpcode]
          data = sConfig.processResponse.call(self, self.data, xhr, self);
        } else {
          data = {errors: {}};
          if (isString(self.data)) {
            self.data = { errors: [ self.data ] } // This way, we can rely on this structure data[statuscode].errors to always be an Array of one or more errors
          }
          data.errors[self.status] = self.data;
        }
  
        setTimeout(function() {
          APICall.complete(self, data);
        }, 1);
      }
  
      // Let script continue before triggering ajax call
      if (!this.config.later && this.config.async) {
        setTimeout(function() {
          if(!self.config.cancel)
            self.xhr = ajax(self.url, sConfig);
        }, 1);
      }
    }