Degree Days

Degree Days

Weather Data for Energy Professionals

Degree Days.net JSON API

The JSON API is carefully designed and well tested. Its HMAC-signature-based security scheme is a little tricky to negotiate, but, once you've figured that out, using the JSON API should be straightforward so long as you are using a programming language that makes it easy to create the JSON request and parse the JSON response.

However, if you are using Java, .NET, or Python, we do still recommend you use the client libraries we've created for those languages. They are fast, small, really easy to use, and with them you can be up and running fetching data in a couple of minutes.

If you're using another programming language and are choosing between the JSON API and the XML API, we suggest you choose based on your experience and your language's handling of JSON and XML. If you're using JavaScript for example, then JSON will probably be much easier for you to work with. But there is no other significant difference between the JSON and XML APIs, they both let you do exactly the same things, they both use the same security scheme, they are both served by the same backend system, and you can use either or both through the same API account.

We also have some sample code that uses the JSON API from JavaScript, Node.js, PHP, and Ruby.

And the JSON API test tool is useful for testing different JSON requests and seeing the JSON responses that come back.

Using the JSON API

Each of these steps is explained below:

1. Create the JSON request

There are currently two main types of request:

Both types of request are highly configurable, and the only difference in the request format is in the name of the request (i.e. LocationDataRequest or LocationInfoRequest).

Below is an example JSON request (a LocationDataRequest) that shows a few of the main options. You can copy/paste it into the JSON API test tool to try it out. Below the example request are more details of the various options you can use within the JSON.

If you have a specific data-fetching pattern in mind, and you're struggling to figure out what JSON you'll need, please feel free to email us for help at info@degreedays.net.

{
    "securityInfo": {
        // The "endpoint" is the URL you send your request to.  You can send it
        // to http://apiv1.degreedays.net/json (to call the API over HTTP) or
        // https://apiv1.degreedays.net/json (to call it over HTTPS); just make
        // sure the endpoint you specify here is exactly the same as the URL you
        // actually send your request to.
        "endpoint": "http://apiv1.degreedays.net/json",
        // The "accountKey" comes with your API account.
        "accountKey": "test-test-test",
        // The "timestamp" should be the time of the request in ISO format.
        "timestamp": "2019-09-17T10:14:28Z",
        // The "random" should be a random string, generated afresh for each
        // request.  It can be a random number or a UUID or similar.
        "random": "5d80b20441135"
    },
    // Your Request can be a LocationDataRequest (shown here) or a
    // LocationInfoRequest (explained further down this page).
    "request": {
        "type": "LocationDataRequest",
        // Specify the Location you want data from.  You can use a
        // StationIdLocation, a LongLatLocation, or a PostalCodeLocation; all
        // are explained further down this page.
        "location": {
            "type": "PostalCodeLocation",
            "postalCode": "02532",
            "countryCode": "US"
        },
        // Now specify what data you want.  You can specify up to 100 DataSpec
        // items in a single request (e.g. HDD and CDD in 50 base temperatures
        // each).  Each DataSpec can be either a DatedDataSpec or an
        // AverageDataSpec; both types are shown in this example request.  Give
        // each DataSpec a unique name in the "dataSpecs" object so you can
        // identify the corresponding DataSet in the response.
        "dataSpecs": {
            // The following specifies HDD with a base temperature of 65 F,
            // broken down daily (a value for each day), and covering the last 7
            // days.  "dailyHDD" is the name we have chosen to give this
            // DataSpec; we'll get a DataSet with the same name in the response.
            "dailyHDD": {
                "type": "DatedDataSpec",
                // The Calculation defines the type of degree days and the base
                // temperature.  You can specify a HeatingDegreeDaysCalculation
                // or a CoolingDegreeDaysCalculation; both are explained further
                // down this page.
                "calculation": {
                    "type": "HeatingDegreeDaysCalculation",
                    "baseTemperature": {
                        "unit": "F",
                        "value": 65
                    }
                },
                // A DatedDataSpec needs a Breakdown, it can be a DailyBreakdown,
                // a WeeklyBreakdown, a MonthlyBreakdown, or a YearlyBreakdown.
                // All are explained further down this page.
                "breakdown": {
                    "type": "DailyBreakdown",
                    // A Breakdown has a Period to specify the dates that the
                    // data should cover.  It can be a LatestValuesPeriod or a
                    // DayRangePeriod; both are explained further down this page.
                    "period": {
                        "type": "LatestValuesPeriod",
                        "numberOfValues": 7
                    }
                }
            },
            // The following specifies CDD with a base temperature of 21.5 C,
            // broken down monthly, and covering June, July, and August of 2018.
            "monthlyCDD": {
                "type": "DatedDataSpec",
                "calculation": {
                    "type": "CoolingDegreeDaysCalculation",
                    "baseTemperature": {
                        "unit": "C",
                        "value": 21.5
                    }
                },
                "breakdown": {
                    "type": "MonthlyBreakdown",
                    "period": {
                        "type": "DayRangePeriod",
                        "dayRange": {
                            "first": "2018-06-01",
                            "last": "2018-08-31"
                        }
                    }
                }
            },
            // The following specifies 5-year-average HDD with a base temperature
            // of 15.5 C.  If you made this request today you could expect to get
            // an average of data from 2014, 2015, 2016, 2017, and 2018.
            // NB as this covers 5 years it will use more request units than the
            // much-shorter items defined above.  For repeated testing you might
            // want to remove it or temporarily reduce its "numberOfValues" to 1
            // if your API account does not have a high rate limit.
            "averageHDD": {
                "type": "AverageDataSpec",
                "calculation": {
                    "type": "HeatingDegreeDaysCalculation",
                    "baseTemperature": {
                        "unit": "C",
                        "value": 15.5
                    }
                },
                // The Breakdown for an AverageDataSpec must be a
                // FullYearsAverageBreakdown.
                "breakdown": {
                    "type": "FullYearsAverageBreakdown",
                    "period": {
                        "type": "LatestValuesPeriod",
                        "numberOfValues": 5
                    }
                }
            }
        }
    }
}

Configuring your own LocationDataRequest

The above JSON is just an example, you can easily configure your own LocationDataRequest to specify exactly the data you want. The JSON API test tool can be useful here: you can copy/paste in the example JSON request above and then modify it to try out the options described below:

The Location can be a station ID, or a "geographic location" for which the API will select the best weather station to use automatically:

// StationIdLocation
"location": {
    "type": "StationIdLocation",
    // the "stationId" must match [-_0-9a-zA-Z]{1,60}
    "stationId": "EGLL"
}

// LongLatLocation
"location": {
    "type": "LongLatLocation",
    "longLat": {
        "longitude": -135.23127,
        "latitude": 43.92135
    }
}

// PostalCodeLocation
"location": {
    "type": "PostalCodeLocation",
    // the "postalCode" is the postal/zip code of the location and must match
    // [- 0-9a-zA-Z]{1,16}
    "postalCode": "WC2N 5DN",
    // the "countryCode" should be the 2-letter ISO country code of the
    // location, in upper case e.g. US for United States, GB for Great Britain
    "countryCode": "GB"
}

A DataSpec can be either a DatedDataSpec (for daily/weekly/monthly/yearly data), or an AverageDataSpec. Both are shown in the example request JSON above.

A Calculation can specify heating or cooling degree days:

// HeatingDegreeDaysCalculation
"calculation": {
    "type": "HeatingDegreeDaysCalculation",
    "baseTemperature": {
        "unit": "C",
        "value": 15.5
    }
}

// CoolingDegreeDaysCalculation
"calculation": {
    "type": "CoolingDegreeDaysCalculation",
    "baseTemperature": {
        "unit": "F",
        "value": 65
    }
}

All temperatures must be specified as whole numbers or with one digit after the decimal point.

A DatedDataSpec can have the following kinds of breakdown:

// DailyBreakdown
"breakdown": {
    "type": "DailyBreakdown",
    "period": {
        // Period goes here
    }
}

// WeeklyBreakdown
"breakdown": {
    "type": "WeeklyBreakdown",
    "firstDayOfWeek": "Monday",
    "period": {
        // Period goes here
    }
}

// MonthlyBreakdown
"breakdown": {
    "type": "MonthlyBreakdown",
    // "startOfMonth" is optional, the default being ---01 for regular calendar
    // months starting on the first day of each month.  The ---DD format is XML
    // Schema's gDay format for days that recur each month, based on ISO 8601.
    "startOfMonth": "---01",
    "period": {
        // Period goes here
    }
}

// YearlyBreakdown
"breakdown": {
    "type": "YearlyBreakdown",
    // "startOfYear" is optional, the default being --01-01 for regular calendar
    // years starting on Jan 1st each year.  The --MM-DD format is XML Schema's
    // gMonthDay format for days that recur each year, based on ISO 8601.
    // The example below specifies years starting on May 21st:
    "startOfYear": "--05-21",
    "period": {
        // Period goes here
    }
}

An AverageDataSpec can currently only have one type of breakdown:

// FullYearsAverageBreakdown specifies that data should be averaged from the 
// full calendar years specified by the period:
"breakdown": {
    "type": "FullYearsAverageBreakdown",
    "period": {
        // Period goes here.  Typically you'd use a LatestValuesPeriod to
        // specify that the average should come from the most recent x full
        // calendar years. 
    }
}

A Period can be specified in two ways:

// LatestValuesPeriod to get the most recent available data:
"period": {
    "type": "LatestValuesPeriod",
    "numberOfValues": 12,
    // "minimumNumberOfValues" is optional - you can specify it if you would
    // rather have a failure than a partial set of data with less than your
    // specified minimum number of values.  (Otherwise you may get back less
    // data than you asked for if there aren't enough temperature-data records
    // to generate a full set for your specified location.)
    "minimumNumberOfValues": 12
}

// DayRangePeriod to get data covering your specified dates:
"period": {
    "type": "DayRangePeriod",
    "dayRange": {
        "first": "2014-01-01",
        "last": "2018-12-31"
    }
    // "minimumDayRange" is optional - you can specify it if you would rather
    // have a failure than a partial set of data covering less than your
    // specified minimum range (like for "minimumNumberOfValues" above).
    "minimumDayRange": {
        "first": "2016-01-01",
        "last": "2018-12-31"
    }
}

LocationInfoRequest and two-stage data fetching

Except in name, LocationInfoRequest looks exactly the same as LocationDataRequest. Take the example LocationDataRequest JSON above, change "type": "LocationDataRequest" to "type": "LocationInfoRequest", and you will have a valid LocationInfoRequest.

Request Units

Each API request you make uses request units that count against your hourly rate limit. A big LocationDataRequest can use a lot of request units, but a LocationInfoRequest will only ever use one. See the sign-up page for more on request units and rate limits.

If you try this out in the JSON API test tool, you will see that LocationInfoResponse does not contain any data (it has no "dataSets" object)... It is typically used for two-stage data fetching, which can be useful if you are dealing with geographic locations (postal/zip codes, or longitude/latitude positions), but storing data by station ID (returned in every successful response). For this use-case, two-stage data fetching can help you save request units (see right) and improve the efficiency of your system by avoiding re-fetching data that you already have stored.

When you want to add a new location into your system (e.g. if a new user signs up with a new address), you can do the following:

Two-stage fetching will only improve efficiency and save request units if/when you have enough geographic locations in your system that some of them end up sharing weather stations. But, if that is the case, two-stage fetching can really help your system to scale well as more and more geographic locations are added in.

Back to overview

2. Generate the HTTP parameters and send them to our servers

You need to send five parameters to the endpoint URL (http://apiv1.degreedays.net/json or https://apiv1.degreedays.net/json):

HTTP POST is ideal, but, if that's difficult for you, a GET should be OK too.

The signature and base64url encoding are both explained below:

The signature

The signature has two purposes:

The signature_method will ideally be HmacSHA256. If you can't do that for whatever reason (it might not be supported by your programming language), HmacSHA1 should work instead. Of course the method you use to make the signature has to correspond with the signature_method parameter that you send with your request.

Your JSON string should be fed into the HMAC function, with the key being your security key (one of the access keys that comes with your API account). If the HMAC function requires a byte array, convert the JSON string into bytes first using UTF-8 encoding. The HMAC function should return the signature as a byte array. Encode that using base64url (as described below), and use the resulting string as the encoded_signature parameter.

base64url encoding

base64url encoding is required to:

base64url encoding is like regular base 64, but with a few differences:

Many libraries can do base64url encoding automatically, but if not, then hopefully you can generate a regular base-64 string and convert it using the simple rules above. Note that you might find it works OK with the = characters and line breaks left in, but to ensure future compatibility it's best to remove them.

If your base-64 function takes a byte array instead of a string, to encode the request JSON you should encode the JSON string into bytes first (using UTF-8 encoding) and then feed the bytes into your base-64 function.

If, in your programming language, you're struggling to generate anything base 64, email us at info@degreedays.net as we should be able to set up an alternative like hex.

Sending the parameters to the server

As mentioned above, an HTTP POST is the best way, but, if that's difficult in your programming language, a GET should work too.

If you can handle a compressed response, you can set the Accept-encoding header to gzip or deflate. Then the JSON response will come back in compressed format. This won't make much of a difference if you're only fetching a small amount of data with each request, but it's well worth using compression to reduce bandwidth if you're fetching daily data covering long periods or in lots of base temperatures.

Debugging your HTTP requests

The JSON API test tool can be useful for debugging your HTTP requests, as it generates and shows all the HTTP request parameters for any JSON request you give it. Usually the test tool's "Auto-prepare request" option is helpful, but you should switch it off if you are checking your encoded_request or encoded_signature against the ones generated by the test tool, as you don't want it modifying your JSON request before generating the HTTP parameters.

Back to overview

3. Parse the JSON response

Below is an annotated example JSON response to the example JSON LocationDataRequest above. It shows all the types of JSON data that you should see, apart from Failure objects (covered later), but, when writing your parsing code, please do bear in mind that we may add in extra JSON data in the future.

{
    // The following "metadata" is included with every response
    "metadata": {
        "rateLimit": {
            // "requestUnitsAvailable" shows how many request units your account
            // has left in the current period.
            "requestUnitsAvailable": 2116,
            // "minutesToReset" is the number of minutes until your available
            // request units are reset to their full allowance.
            "minutesToReset": 33
        }
    },
    // If you made a LocationDataRequest in your JSON request, the "response"
    // will be a LocationDataResponse.  (Unless it's a Failure instead - see
    // further below for more on this.)
    "response": {
        "type": "LocationDataResponse",
        // There will always be a "stationId", assuming your request could be
        // satisfied.
        "stationId": "KFMH",
        // The "targetLongLat" will always have the same format, however you
        // specified the Location in your JSON request.  If you specified a
        // StationIdLocation it will give the location of that station; if you
        // specified a LongLatLocation it will repeat those coordinates back to
        // you; if you specified a PostalCodeLocation (as in the example JSON
        // request) it will give you the coordinates that the API used to
        // represent that postal code.
        "targetLongLat": {
            "longitude": -70.59047,
            "latitude": 42.7455
        },
        // The "sources" array represents the stations that were used to
        // generate your data.  There will always be at least one, and at
        // present there will only be one (we've allowed for the future
        // possibility of combining data from multiple stations without breaking
        // compatibility).
        "sources": [
            {
                "station": {
                    "id": "KFMH",
                    "longLat": {
                        "longitude": -70.5215,
                        "latitude": 41.6585
                    },
                    "elevationMetres": 40,
                    "displayName": "Otis Air National Guard Base, MA, US"
                },
                // How far is this station from the "targetLongLat" (see above)?
                // (It will be 0 if you specified a StationIdLocation in your
                // request.)
                "metresFromTarget": 11242
            }
        ],
        "dataSets": {
            // Every DataSet has a unique name in the "dataSets" object that
            // matches the name you gave the corresponding DataSpec in the
            // "dataSpecs" object of your JSON request.
            // The following examples show the formats you can expect for the
            // different types of DataSet:
            "dailyHDD": {
                "type": "DatedDataSet",
                "percentageEstimated": 1,
                "values": [
                    {
                        // "d" is the first day of the period that the value "v"
                        // covers.  The "ld" (last day) property isn't included
                        // for daily data as it's always the same as "d".
                        // We don't like cryptic abbreviations or properties
                        // that are only sometimes present, but big responses
                        // can easily have hundreds of thousands of values, so
                        // we keep them small to minimize bandwidth and memory
                        // footprint.
                        "d": "2019-09-10",
                        "v": 3.8
                    },
                    {
                        "d": "2019-09-11",
                        "v": 2.6,
                        // "pe" is short for "percentage estimated".  If it's
                        // missing it's the default value of 0.  
                        "pe": 0.6
                    },
                    {
                        "d": "2019-09-12",
                        "v": 1.4
                    },
                    {
                        "d": "2019-09-13",
                        "v": 0.3
                    },
                    {
                        "d": "2019-09-14",
                        "v": 2.9
                    },
                    {
                        "d": "2019-09-15",
                        "v": 3.9,
                        "pe": 1
                    },
                    {
                        "d": "2019-09-16",
                        "v": 1.4
                    }
                ]
            },
            "monthlyCDD": {
                "type": "DatedDataSet",
                "percentageEstimated": 0.6,
                "values": [
                    {
                        "d": "2018-06-01",
                        // "ld" is short for "last day".  It's only included if
                        // it's different to "d" (the first day) i.e. if the
                        // data is weekly or monthly or yearly instead of daily.
                        "ld": "2018-06-30",
                        "v": 17.4,
                        "pe": 2
                    },
                    {
                        "d": "2018-07-01",
                        "ld": "2018-07-31",
                        "v": 73.6
                    },
                    {
                        "d": "2018-08-01",
                        "ld": "2018-08-31",
                        "v": 24.1
                    }
                ]
            },
            "averageHDD": {
                "type": "AverageDataSet",
                "percentageEstimated": 0.2,
                "firstYear": 2014,
                "lastYear": 2018,
                // "annual" has the average-annual total.
                "annual": {
                    "v": 2478.3,
                    "pe": 0.2
                },
                "monthly": {
                    // "1" has the average value for January; "2" has the
                    // average value for February; etc.
                    "1": {
                        "v": 523.9,
                        "pe": 0.01
                    },
                    "2": {
                        "v": 435,
                        "pe": 0.3
                    },
                    "3": {
                        "v": 363.1,
                        "pe": 0.06
                    },
                    "4": {
                        "v": 203.5,
                        "pe": 0.007
                    },
                    "5": {
                        "v": 85.9,
                        "pe": 0.4
                    },
                    "6": {
                        "v": 21.1,
                        "pe": 1
                    },
                    "7": {
                        "v": 1.9,
                        "pe": 0.2
                    },
                    "8": {
                        "v": 4.2,
                        "pe": 0.01
                    },
                    "9": {
                        "v": 35.8,
                        "pe": 0.1
                    },
                    "10": {
                        "v": 125.4,
                        "pe": 0.08
                    },
                    "11": {
                        "v": 257.3,
                        "pe": 0.007
                    },
                    "12": {
                        "v": 421.2,
                        "pe": 0.03
                    }
                }
            }
        }
    }
}

You can generate a similar response yourself by running the example JSON request further above through the JSON API test tool. But please note that the annotated response above is just an example to show the response structure; the figures you get in your live response will be different.

Failures and failure codes

If something goes wrong, the JSON response will contain a Failure.

Every Failure has a "code" that indicates the cause of the failure.

These codes are named in a hierarchical way. For example, if a failure is caused by an invalid request, its code will begin with "InvalidRequest". The idea is that you can quickly test for broader types of failure code without having to know or itemize all the sub-types (like "InvalidRequestAccount" and "InvalidRequestSignature").

New codes may be added into the API at any time. New codes might be sub-types of existing types (like if "InvalidRequestSomeNewCode" was added as a sub-type of "InvalidRequest"), or they might be completely new (like "SomeCompletelyNewCode"). If you're writing logic that checks for different failure codes, make sure that it won't blow up if it comes across a code that it doesn't recognize.

Request failure

Any request can fail completely for a variety of reasons. Here's an example of the sort of JSON response you can expect if it does:

{
    // Note how the "metadata" comes through as usual even though the request
    // failed.  This is expected, you can rely on the "metadata" being there.
    "metadata": {
        "rateLimit": {
            "requestUnitsAvailable": 1954,
            "minutesToReset": 11
        }
    },
    // Note how the "response" property is an object of type "Failure" rather
    // than the "LocationDataResponse" or "LocationInfoResponse" you'd usually
    // expect.
    "response": {
        "type": "Failure",
        "code": "LocationNotRecognized",
        "message": "Sorry, we do not recognize the location that you specified.  Our postal-code database did not recognize the specified PostalCodeLocation, and was consequently unable to find its longitude/latitude position."
    }
}

Here are some failure codes you might see:

Location failures (all codes starting with "Location"):

RateLimit failures (all codes starting with "RateLimit"):

InvalidRequest failures (all codes starting with "InvalidRequest"):

Service failures (all codes starting with "Service"):

The list above is not complete, and more failure codes may be added at any time. Instead of trying to get your system to handle them all, we suggest you just watch out for the codes you want to do something specific with. Always test for codes with e.g. code.startsWith("LocationNotRecognized") so that your system will be able to handle us adding new sub-types of the codes you're watching out for.

DataSet failure (a partial failure)

A LocationDataRequest can succeed partially, but some or all of its DataSpec/DataSet items can fail. For example, you might specify that you want data from 100 years ago from a specific PostalCodeLocation. The API might find a good weather station to match the postal code (partial success), but not one with coverage going that far back in time (partial failure).

In such instances, you can expect to receive a Failure in place of the DataSet that you would usually receive if everything had worked. Like a DataSet, the Failure will have a name that matches the name of the corresponding DataSpec from your request. You can check the "type" property to see if you have a Failure or the DatedDataSet/AverageDataSet you were hoping for.

{
    "metadata": {
        // details ommitted for clarity
    },
    "locationDataResponse": {
        // the JSON that comes before "dataSets" is ommitted for clarity
        "dataSets": {
            // successful DataSet items ommitted for clarity
            "nameOfCorrespondingDataSpecFromRequest": {
                "type": "Failure",
                "code": "SourceDataCoverage",
                "message": "Sorry, the source does not have enough recorded temperature readings for us to be able to generate data covering enough time to satisfy your specification."
            }
            // successful DataSet items ommitted for clarity
        }
    }
}

LocationInfoResponse

If you submit a LocationInfoRequest you'll get a LocationInfoResponse back. LocationInfoResponse is basically just LocationDataResponse without the "dataSets" property. And of course its "type" will be "LocationInfoResponse" instead of "LocationDataResponse".

Beyond these differences, LocationInfoResponse is identical to LocationDataResponse (including the same request-failure possibilities), and you should be able to re-use most of your parsing code.

You can try this out with the JSON API test tool by copy/pasting in the example LocationDataRequest further above, changing "type": "LocationDataRequest" to "type": "LocationInfoRequest", and sending it to the API to get a LocationInfoResponse back.

Back to overview

Higher-level integration help

This page focuses on the technical details, but is also worth reading the higher-level integration guide for tips on the various approaches to integrating with the API. We have helped a lot of businesses integrate their software with our API so we are very familiar with the patterns that work well for common use cases.

Choose your Plan and Sign Up Today!

© 2019 BizEE Software Limited - About | Contact | Privacy | Web Tool | API | Integration Guide | API FAQ | API Sign-Up