Degree Days

Degree Days

Weather Data for Energy Professionals

Degree Days.net XML API

The XML API is carefully designed and well tested. But it's not particularly easy to use. The wide range of options and data that can be specified and returned in XML means there's a lot of XML creation and parsing involved. There's also a strict HMAC-signature-based security scheme to negotiate.

We have robust client libraries for Java, .NET, and Python; we recommend you use them if you are using Java (or Kotlin, Scala, Groovy etc.), .NET (e.g. C# or VB.NET), or Python. All 3 client libraries use the XML API internally, but they make all the request options immediately available, and use fast XML-stream parsing to convert the XML responses into a simple object model. With the client libraries you can be up and running fetching data in a couple of minutes.

For dynamic object-oriented languages with good JSON support, the JSON API is typically easier to use than the XML API, as the JSON maps naturally and automatically to objects. We have code samples for JavaScript, Node.js, PHP, and Ruby. But there is no other significant difference between the XML API and the JSON API. Both let you do exactly the same things, both have the same security scheme, both are served by the same backend system, and you can use either or both through the same API account. We suggest you choose whichever best suits your platform and experience.

If you do choose the XML API, the XML API test tool should be useful for testing different XML requests and seeing the XML responses that come back.

Using the XML API

Each of these steps is explained below:

1. Create the XML 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 XML request (a LocationDataRequest) that shows a few of the main options. You can copy/paste it into the XML API test tool to try it out. Below that are more details of the various options you can use within the XML.

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

<?xml version="1.0" encoding="UTF-8" ?>

<RequestEnvelope>
    
    <SecurityInfo>
        <!-- "Endpoint" is the URL you send your request to.  You can send it
        to http://apiv1.degreedays.net/xml (to call the API over HTTP) or
        https://apiv1.degreedays.net/xml (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/xml</Endpoint>
        <!-- "AccountKey" comes with your API account. -->
        <AccountKey>test-test-test</AccountKey>
        <!-- "Timestamp" should be the time of the request in ISO format. -->
        <Timestamp>2019-11-17T03:00:06Z</Timestamp>
        <!-- "Random" should be a random string, generated afresh for each
        request.  It can be a random number or a UUID or similar. -->
        <Random>5dd0b7b68d926</Random>
    </SecurityInfo>                         
    
    <LocationDataRequest>
        <!-- Specify the "Location" you want data for (more options further
        below): -->
        <PostalCodeLocation>
            <PostalCode>02532</PostalCode>
            <CountryCode>US</CountryCode>
        </PostalCodeLocation>
        
        <!-- 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).  Give each "DataSpec" a "key" attribute 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. -->
            <DatedDataSpec key="dailyHDD">
                <!-- The "Calculation" defines the type of degree days (e.g. 
                heating or cooling) and the base temperature. -->
                <HeatingDegreeDaysCalculation>
                    <FahrenheitBaseTemperature>65</FahrenheitBaseTemperature>
                </HeatingDegreeDaysCalculation>
                <!-- There are different types of "Breakdown" (see further 
                below). -->
                <DailyBreakdown>
                    <!-- A Breakdown has a "Period" to specify the dates that
                    the data should cover.  There's more on specifying the
                    Period further down this page. -->
                    <LatestValuesPeriod>
                        <NumberOfValues>7</NumberOfValues>
                    </LatestValuesPeriod>
                </DailyBreakdown>
            </DatedDataSpec>
            
            <!-- The following specifies CDD with a base temperature of 21.5 C,
            broken down monthly, and covering June, July, and August of 
            2019. -->
            <DatedDataSpec key="monthlyCDD">
                <CoolingDegreeDaysCalculation>
                    <CelsiusBaseTemperature>21.5</CelsiusBaseTemperature>
                </CoolingDegreeDaysCalculation>
                <MonthlyBreakdown>
                    <DayRangePeriod>
                        <DayRange first="2019-06-01" last="2019-08-31"/>
                    </DayRangePeriod>
                </MonthlyBreakdown>
            </DatedDataSpec>
            
            <!-- 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. -->
            <AverageDataSpec key="averageHDD">
                <HeatingDegreeDaysCalculation>
                    <CelsiusBaseTemperature>15.5</CelsiusBaseTemperature>
                </HeatingDegreeDaysCalculation>
                <FullYearsAverageBreakdown>
                    <LatestValuesPeriod>
                        <NumberOfValues>5</NumberOfValues>
                    </LatestValuesPeriod>
                </FullYearsAverageBreakdown>
            </AverageDataSpec>
            
        </DataSpecs>
        
    </LocationDataRequest>
    
</RequestEnvelope>

Configuring your own LocationDataRequest

The above XML is just an example, you can easily configure your own LocationDataRequest to specify exactly the data you want. The XML API test tool can be useful here: you can copy/paste in the example XML 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>
    <!-- the StationId must match [-_0-9a-zA-Z]{1,60} -->
    <StationId>EGLL</StationId>
</StationIdLocation>

<LongLatLocation>
    <LongLat longitude="-135.23127" latitude="43.92135"/>
</LongLatLocation>

<PostalCodeLocation>
    <!-- the PostalCode is the postal/zip code of the location and must match
    [- 0-9a-zA-Z]{1,16} -->
    <PostalCode>WC2N 5DN</PostalCode>
    <!-- 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</CountryCode>
</PostalCodeLocation>

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

A Calculation can specify heating or cooling degree days:

<HeatingDegreeDaysCalculation>
    <CelsiusBaseTemperature>15.5</CelsiusBaseTemperature>
</HeatingDegreeDaysCalculation>

<CoolingDegreeDaysCalculation>
    <FahrenheitBaseTemperature>65</FahrenheitBaseTemperature>
</CoolingDegreeDaysCalculation>

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>
    <!-- Period goes here. -->
</DailyBreakdown>

<WeeklyBreakdown firstDayOfWeek="Monday">
    <!-- Period goes here. -->
</WeeklyBreakdown>

<!-- 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. -->
<MonthlyBreakdown startOfMonth="---01">
    <!-- Period goes here. -->
</MonthlyBreakdown>

<!-- 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: -->
<YearlyBreakdown startOfYear="--05-21">
    <!-- Period goes here. -->
</YearlyBreakdown>

An AverageDataSpec can currently only have one type of breakdown:

<!-- This specifies that data should be averaged from the full calendar years
specified by the period. -->
<FullYearsAverageBreakdown>
    <!-- Period goes here.  Typically you'd use a LatestValuesPeriod to specify
    that the average should come from the most recent x full calendar years. -->
</FullYearsAverageBreakdown>

A Period can be specified in two ways:

<!-- Get the most recent available data: -->
<LatestValuesPeriod>
    <NumberOfValues>12</NumberOfValues>
    <!-- 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</MinimumNumberOfValues>
</LatestValuesPeriod>

<!-- Get data covering your specified dates: -->
<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"/>
</DayRangePeriod>

LocationInfoRequest and two-stage data fetching

Except in name, LocationInfoRequest looks exactly the same as LocationDataRequest. Take the example LocationDataRequest XML above, change "<LocationDataRequest>" to "<LocationInfoRequest>" and "</LocationDataRequest>" to "</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 XML API test tool, you will see that LocationInfoResponse does not contain any data (it has no <DataSets> element)... It's 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/xml or https://apiv1.degreedays.net/xml):

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 XML 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 XML 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 XML you should encode the XML 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 XML 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 XML API test tool can be useful for debugging your HTTP requests, as it generates and shows all the HTTP request parameters for any XML 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 XML request before generating the HTTP parameters.

Back to overview

3. Parse the XML response

Below is an annotated example XML response to the example XML LocationDataRequest above. It shows all the elements and attributes that you should see, apart from Failure elements (covered later), but, when writing your parsing code, please do bear in mind that we may add extra new elements or attributes in the future.

<Response>
    
    <!-- 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</RequestUnitsAvailable>
            <!-- MinutesToReset is the number of minutes until your available 
            request units are reset to their full allowance. -->
            <MinutesToReset>33</MinutesToReset>
        </RateLimit>
    </Metadata>
    
    <!-- The LocationDataResponse element is what you get in response to a
    LocationDataRequest (from your XML request). -->
    <LocationDataResponse>
        
        <Head>
            <!-- There will always be a StationId, assuming your request could
            be satisfied. -->
            <StationId>KFMH</StationId>
            <!-- The TargetLocation will always have the same format, however
            you specified the Location in your XML 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 XML
            request) it will give you the coordinates that the API used to
            represent that postal code. -->
            <TargetLocation>
                <LongLat longitude="-70.59047" latitude="42.7455"/>
            </TargetLocation>
            <!-- The Sources represent 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>
                <Source>
                    <Station>
                        <Id>KFMH</Id>
                        <LongLat longitude="-70.5215" latitude="41.6585"/>
                        <ElevationMetres>40</ElevationMetres>
                        <DisplayName>Otis Air National Guard Base, MA, US</DisplayName>
                    </Station>
                    <!-- How far is this Station from the TargetLocation?
                    (It will be 0 if you specified a StationIdLocation in your
                    request.) -->
                    <MetresFromTarget>11242</MetresFromTarget>
                </Source>
            </Sources>
        </Head>
        
        <DataSets>
            <!-- Every DataSet has a key that matches the corresponding DataSpec
            from the XML request. 
            The following examples show the formats you can expect for the
            different types of DataSet: -->
            
            <DatedDataSet key="dailyHDD">
                <Head>
                    <PercentageEstimated>1</PercentageEstimated>
                </Head>
                <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 attributes 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. -->
                    <V d="2019-11-10">3.8</V>
                    <!-- pe is short for "percentage estimated".  If it's
                    missing, it's the default value of 0. -->
                    <V d="2019-11-11" pe="0.6">2.6</V>
                    <V d="2019-11-12">1.4</V>
                    <V d="2019-11-13">0.3</V>
                    <V d="2019-11-14">2.9</V>
                    <V d="2019-11-15" pe="1">3.9</V>
                    <V d="2019-11-16">1.4</V>
                </Values>
            </DatedDataSet>
            
            <DatedDataSet key="monthlyCDD">
                <Head>
                    <PercentageEstimated>0.6</PercentageEstimated>
                </Head>
                <Values>
                    <!-- ld is short for "last date".  It's only included if
                    it's different to d (the first date) i.e. if the data is
                    weekly or monthly or yearly instead of daily. -->
                    <V d="2019-06-01" ld="2019-06-30" pe="2">17.4</V>
                    <V d="2019-07-01" ld="2019-07-31">73.6</V>
                    <V d="2019-08-01" ld="2019-08-31">24.1</V>
                </Values>
            </DatedDataSet>
            
            <AverageDataSet key="averageHDD">
                <Head>
                    <PercentageEstimated>0.2</PercentageEstimated>
                    <FirstYear>2014</FirstYear>
                    <LastYear>2018</LastYear>
                </Head>
                <Values>
                    <!-- Annual has the average-annual total. -->
                    <Annual pe="0.2">2478.3</Annual>
                    <Monthly>
                        <!-- M (Month) no 1 has the average value for January;
                        2 has the average value for February; etc. -->
                        <M no="1" pe="0.01">523.9</M>
                        <M no="2" pe="0.3">435</M>
                        <M no="3" pe="0.06">363.1</M>
                        <M no="4" pe="0.007">203.5</M>
                        <M no="5" pe="0.4">85.9</M>
                        <M no="6" pe="1">21.1</M>
                        <M no="7" pe="0.2">1.9</M>
                        <M no="8" pe="0.01">4.2</M>
                        <M no="9" pe="0.1">35.8</M>
                        <M no="10" pe="0.08">125.4</M>
                        <M no="11" pe="0.007">257.3</M>
                        <M no="12" pe="0.03">421.2</M>
                    </Monthly>
                </Values>
            </AverageDataSet>
            
        </DataSets>
        
    </LocationDataResponse>
    
</Response>

You can generate a similar response yourself by running the example XML request further above through the XML 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 XML 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 XML response you should expect if it does:

<Response>
    <!-- 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</RequestUnitsAvailable>
            <MinutesToReset>11</MinutesToReset>
        </RateLimit>
    </Metadata>
    <!-- Note how there's a Failure instead of the LocationDataResponse or
    LocationInfoResponse you'd usually expect. -->
    <Failure>
        <Code>LocationNotRecognized</Code>
        <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.</Message>
    </Failure>
</Response>

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 keyed 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 element in place of the DataSet that you would usually receive if everything had worked. The Failure will have a key attribute matching the key of the corresponding DataSpec from your request.

<Response>
    
    <Metadata>
        <!-- Details ommitted for clarity -->
    </Metadata>
    
    <LocationDataResponse>
        
        <Head>
            <!-- Details ommitted for clarity -->
        </Head>
        
        <DataSets>
            <!-- Successful DataSet items ommitted for clarity -->
            
            <Failure key="keyOfCorrespondingDataSpecFromRequest">
                <Code>SourceDataCoverage</Code>
                <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.</Message>
            </Failure>
            
            <!-- Successful DataSet items ommitted for clarity -->
        </DataSets>
        
    </LocationDataResponse>
    
</Response>

LocationInfoResponse

If you submit a LocationInfoRequest you'll get a LocationInfoResponse back. LocationInfoResponse is basically just LocationDataResponse without the <DataSets> element. It contains the <Head> element only. And of course the XML says "<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 XML API test tool by copy/pasting in the example LocationDataRequest further above, changing "<LocationDataRequest>" to "<LocationInfoRequest>" and "</LocationDataRequest>" to "</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 - About | Contact | Privacy | Web Tool | API | Integration Guide | API FAQ | API Sign-Up