Stack Apps is a question and answer site for apps, scripts, and development with the Stack Exchange API. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

This tips demonstrates how to call the API in JavaScript without the need of jQuery or other frameworks.

In a lot of cases you will find that the JSONP implementations provided in various frameworks are just not quite right for the job and require a lot of massaging.

Here is a JSONP implementation I designed specifically for use with the API.

I have tested on many browsers on windows and have yet to encounter a failure.

It may be easy to miss, but a packed version is at the bottom of this post and weighs in at 696 bytes.

Usage Example

<script src="scripts/DevTips.js" type="text/javascript"></script>

<script type="text/javascript">

    var myApiKey = "";

    var url = "";

    var parameters = {
        "sort": "votes",
        "min": "5",
        "order": "asc"

    DevTips.jsonp(url, parameters, myApiKey,
    function success(data) {
    function failure(error) {


The Code:

var DevTips = DevTips || {};

(function(dt) {

    var __callbackId = 0;
    var maxQueryStringLengthErrorMessage = "Query string length exceeds maximum recommended value of ";
    var maxPathLengthErrorMessage = "Url path length exceeds maximum recommended value of ";

    // expose these for reference
    var maxQueryStringLength = dt.maxQueryStringLength = 1000;
    var maxPathLength = dt.maxPathLength = 240;

    var jsEncode = dt.jsEncode = function(value) {
        // properly encode arrays and dates
        // NOTE: use this to turn arrays of ids into
        // semicolon delimited string.

        if (value instanceof Date) {
            return new Date(value).getTime();

        if (value instanceof Array) {
            var values = [];
            for (var i = 0; i < value.length; i++) {
            // vectorize arrays
            return values.join(";");

        return encodeURIComponent(value);


    var jsonp = dt.jsonp = function(url, parameters, apiKey, success, error, timeout) {
        /// <summary>
        /// Sets up a JSONP call to the api.
        /// </summary>
        /// <param name="url" type="String"></param>
        /// <param name="parameters" type="Map">Optional</param>
        /// <param name="apiKey" type="String">Optional</param>
        /// <param name="success" type="Function(data)">Handle successful api call. data = api JSOB</param>
        /// <param name="error" type="Function(error)">Handle and error condition. error = API error JSOB. 
        /// Optional, if you are crazy or would like to be driven that way.</param>
        /// <param name="timeout" type="Number">Timeout in ms. Optional. Defaults to 10,000</param>

        if (url.length > maxPathLength) {
            throw new Error(maxPathLengthErrorMessage + maxPathLength);

        var callBackName = "_callback" + __callbackId++;

        var queryString = "?jsonp=DevTips.jsonp." + callBackName;
        if (parameters) {
            for (var name in parameters) {
                if (parameters.hasOwnProperty(name)) {
                    queryString = queryString + "&" + name + "=" + jsEncode(parameters[name]);
        if (queryString.length > maxQueryStringLength) {
            throw new Error(maxQueryStringLengthErrorMessage + maxQueryStringLength);

        // setup the callback
        jsonp[callBackName] = function(data) {
            delete jsonp[callBackName];
            if (data.error) {
                if (error) {
                    data.error.callback = callBackName;
            else {

        // send the request
        var scr = document.createElement("script");
        scr.type = "text/javascript";
        scr.src = url + queryString;
        var head = document.getElementsByTagName("head")[0];
        head.insertBefore(scr, head.firstChild);

        // default to 10 second timeout
        timeout = timeout || 10000;

        window.setTimeout(function() {
            if (typeof jsonp[callBackName] == "function") {

                // replace success with null callback in case the request is just very latent.
                jsonp[callBackName] = function(data) {
                    delete jsonp[callBackName];

                // call the error callback
                error({ code: 408, message: "Request Timeout", callback: callBackName });

                // set a longer timeout to safely clean up the unused callback.
                window.setTimeout(function() {
                    if (typeof jsonp[callBackName] == "function") {
                        delete jsonp[callBackName];
                }, 60000);
        }, timeout);

The Code Packed with Dojo ShrinkSafe (696 bytes)

var DevTips=DevTips||{}; (function(dt){ var _2=0; var _3="Query string length exceeds maximum recommended value of "; var _4="Url path length exceeds maximum recommended value of "; var _5=dt.maxQueryStringLength=1000; var _6=dt.maxPathLength=240; var _7=dt.jsEncode=function(_8){ if(_8 instanceof Date){ return new Date(_8).getTime(); } if(_8 instanceof Array){ var _9=[]; for(var i=0;i<_8.length;i++){ _9.push(encodeURIComponent(_8[i])); } return _9.join(";"); } return encodeURIComponent(_8); }; var _b=dt.jsonp=function(_c,_d,_e,_f,_10,_11){ if(_c.length>_6){ throw new Error(_4+_6); } var _12="_callback"+_2++; var _13="?jsonp=DevTips.jsonp."+_12; if(_d){ for(var _14 in _d){ if(_d.hasOwnProperty(_14)){ _13=_13+"&"+_14+"="+_7(_d[_14]); } } } if(_13.length>_5){ throw new Error(_3+_5); } _b[_12]=function(_15){ delete _b[_12]; if(_15.error){ if(_10){ _15.error.callback=_12; _10(_15.error); } }else{ _f(_15); } }; var scr=document.createElement("script"); scr.type="text/javascript"; scr.src=_c+_13; var _17=document.getElementsByTagName("head")[0]; _17.insertBefore(scr,_17.firstChild); _11=_11||10000; window.setTimeout(function(){ if(typeof _b[_12]=="function"){ _b[_12]=function(_18){ delete _b[_12]; }; _10({code:408,message:"Request Timeout",callback:_12}); window.setTimeout(function(){ if(typeof _b[_12]=="function"){ delete _b[_12]; } },60000); } },_11); }; })(DevTips);
share|improve this question
Thanks for the tip... but my app uses jQuery anyway. – Nathan Osman Jun 25 '10 at 17:54
ya, i did this in jquery in about 10% of the code here. – Jason Jun 25 '10 at 22:28
@jason - Really? Congratulations. And your point is what? As long as we are throwing non-sequiturs around, you might be interested in the fact that the only reason you can reliably use jquery to consume the api is here…. But again, that is not the point. If you are still not picking up what I am putting down, perhaps from now on, you should count to 10 and take that time to ask yourself if what you are about to post is noise or signal. – Sky Sanders Jun 26 '10 at 0:35
@jason - and I ain't mad at ya. It is just obvious that you made no effort to understand the point of this post and there was zero thought put into the comment you made. capisce? and as soon as it disappears, so will these. – Sky Sanders Jun 26 '10 at 1:47
@code poet - easy there, killer... that comment had nothing to do with you or your efforts or anything. it was merely a statement of fact in response to george's comment. i can (and do) perform the same action that you do here in approximately 10% of the code using jQuery. And no, it's not because of that link to whatever wrapper you wrote that I have never seen before now. jeez.. you sound like my girlfriend always making everything about you :\ – Jason Jun 26 '10 at 2:10
@jason - let's try this again. read. then count. then post. really. and noise in response to noise is still noise. – Sky Sanders Jun 26 '10 at 2:16
@jason - 'And no, it's not because of that link to whatever wrapper you wrote that I have never seen before now. jeez.. you sound like my girlfriend always making everything about you :\' - you sound like someone who cannot be bothered to inform themselves before blurting emotional responses. That link is to a post addressing a flaw that prevented reliable consumption of the api via JSONP. It has nothing to do with me other than the fact that I wrote it. Do you really want to keep this going? – Sky Sanders Jun 26 '10 at 2:32
@moderator - can you please delete all of this noise, including mine. It adds absolutely no value. – Sky Sanders Jun 26 '10 at 2:34
@geo - i blame this on you and your non-comment comment. see what you started? butterflies and hurricanes and all that. i am smiling but cannot bring myself to type one more frakking emoticon. i just don't have it in me toady. – Sky Sanders Jun 26 '10 at 2:44
@code: Oh wow! I did start something :O – Nathan Osman Jun 26 '10 at 2:59
prozac......... – Igor Zevaka Jun 26 '10 at 12:23

You must log in to answer this question.

Browse other questions tagged .