docs: function(p, callback) {
  
  		var _this = this;
  		var error = _this.error;
  
  		// This has to go through a POST request
  		if (!_this.isEmpty(p.data) && !('FileList' in window) && _this.hasBinary(p.data)) {
  
  			// Disable XHR and JSONP
  			p.xhr = false;
  			p.jsonp = false;
  		}
  
  		// Check if the browser and service support CORS
  		var cors = this.request_cors(function() {
  			// If it does then run this...
  			return ((p.xhr === undefined) || (p.xhr && (typeof (p.xhr) !== 'function' || p.xhr(p, p.query))));
  		});
  
  		if (cors) {
  
  			formatUrl(p, function(url) {
  
  				var x = _this.xhr(p.method, url, p.headers, p.data, callback);
  				x.onprogress = p.onprogress || null;
  
  				// Windows Phone does not support xhr.upload, see #74
  				// Feature detect
  				if (x.upload && p.onuploadprogress) {
  					x.upload.onprogress = p.onuploadprogress;
  				}
  
  			});
  
  			return;
  		}
  
  		// Clone the query object
  		// Each request modifies the query object and needs to be tared after each one.
  		var _query = p.query;
  
  		p.query = _this.clone(p.query);
  
  		// Assign a new callbackID
  		p.callbackID = _this.globalEvent();
  
  		// JSONP
  		if (p.jsonp !== false) {
  
  			// Clone the query object
  			p.query.callback = p.callbackID;
  
  			// If the JSONP is a function then run it
  			if (typeof (p.jsonp) === 'function') {
  				p.jsonp(p, p.query);
  			}
  
  			// Lets use JSONP if the method is 'get'
  			if (p.method === 'get') {
  
  				formatUrl(p, function(url) {
  					_this.jsonp(url, callback, p.callbackID, p.timeout);
  				});
  
  				return;
  			}
  			else {
  				// It's not compatible reset query
  				p.query = _query;
  			}
  
  		}
  
  		// Otherwise we're on to the old school, iframe hacks and JSONP
  		if (p.form !== false) {
  
  			// Add some additional query parameters to the URL
  			// We're pretty stuffed if the endpoint doesn't like these
  			p.query.redirect_uri = p.redirect_uri;
  			p.query.state = JSON.stringify({callback:p.callbackID});
  
  			var opts;
  
  			if (typeof (p.form) === 'function') {
  
  				// Format the request
  				opts = p.form(p, p.query);
  			}
  
  			if (p.method === 'post' && opts !== false) {
  
  				formatUrl(p, function(url) {
  					_this.post(url, p.data, opts, callback, p.callbackID, p.timeout);
  				});
  
  				return;
  			}
  		}
  
  		// None of the methods were successful throw an error
  		callback(error('invalid_request', 'There was no mechanism for handling this request'));
  
  		return;
  
  		// Format URL
  		// Constructs the request URL, optionally wraps the URL through a call to a proxy server
  		// Returns the formatted URL
  		function formatUrl(p, callback) {
  
  			// Are we signing the request?
  			var sign;
  
  			// OAuth1
  			// Remove the token from the query before signing
  			if (p.authResponse && p.authResponse.oauth && parseInt(p.authResponse.oauth.version, 10) === 1) {
  
  				// OAUTH SIGNING PROXY
  				sign = p.query.access_token;
  
  				// Remove the access_token
  				delete p.query.access_token;
  
  				// Enfore use of Proxy
  				p.proxy = true;
  			}
  
  			// POST body to querystring
  			if (p.data && (p.method === 'get' || p.method === 'delete')) {
  				// Attach the p.data to the querystring.
  				_this.extend(p.query, p.data);
  				p.data = null;
  			}
  
  			// Construct the path
  			var path = _this.qs(p.url, p.query);
  
  			// Proxy the request through a server
  			// Used for signing OAuth1
  			// And circumventing services without Access-Control Headers
  			if (p.proxy) {
  				// Use the proxy as a path
  				path = _this.qs(p.oauth_proxy, {
  					path: path,
  					access_token: sign || '',
  
  					// This will prompt the request to be signed as though it is OAuth1
  					then: p.proxy_response_type || (p.method.toLowerCase() === 'get' ? 'redirect' : 'proxy'),
  					method: p.method.toLowerCase(),
  					suppress_response_codes: true
  				});
  			}
  
  			callback(path);
  		}
  	}