"Alexa? Which bins do I need to take out?" - Reverse engineering the Bexley Council bin collection API


A few years ago I wrote an Alexa skill to see what bin day it was. The skill only worked with my local council - Oxford.

I've since moved house and wanted to recreate the service for my new council - Bexley.

Demo

Brilliant, eh?

The API

Bexley Council don't publish an API. I initially thought about scraping their bin collection day web-form but I couldn't work out the calls the site made.

But Bexley have an app! Well, they use the @HOME app along with a bunch of councils. The app is... not very good. And it isn't particularly secure. Which is good news for me, as I was able to intercept all the traffic between it and its server!

The API Call

You need to register an email address along with your home address. You pass that as your username. If you know a resident's email address, you can probably find out their bin collection day. I don't know if that's a GDPR thing or not...

curl --header "X-email: whatever@example.com" "https://services.athomeapp.net/ServiceData/GetUserRoundJson"

The app also passes --header "X-country: gb" --header "X-udprn: 1234567" - that last one is your UDPRN a sort of standard reference for addresses. You don't need to supply them though.

The Response

You get back JSON about when your bins are going to be collected. I've not yet seen how it handles bank holidays. I've redacted any person information.

{
    "email": "whatever@example.com",
    "udprn": "",
    "managed": true,
    "userrounds": [{
        "roundid": "1234",
        "collectionday": "Fri",
        "containerid": "abcd",
        "collectioninterval": "Every 2 week",
        "containerdescription": "Chargeable garden waste bin",
        "containername": "Chargeable garden waste bin",
        "nextcollectiondates": [{
            "day": "18",
            "month": "10",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "18 10 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "1",
            "month": "11",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "01 11 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "15",
            "month": "11",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "15 11 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "29",
            "month": "11",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "29 11 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "13",
            "month": "12",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "13 12 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "27",
            "month": "12",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "27 12 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }],
        "containeritems": null,
        "roundbitmask": 0,
        "selected": true
    }, {
        "roundid": "2345",
        "collectionday": "Fri",
        "containerid": "xyz",
        "collectioninterval": "Every week",
        "containerdescription": "Food recycling box",
        "containername": "Food recycling box",
        "nextcollectiondates": [{
            "day": "18",
            "month": "10",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "18 10 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }],
        "containeritems": null,
        "roundbitmask": 0,
        "selected": true
    }, {
        "roundid": "987654",
        "collectionday": "Fri",
        "containerid": "qwerty",
        "collectioninterval": "Every week",
        "containerdescription": "Glass box",
        "containername": "Glass box",
        "nextcollectiondates": [{
            "day": "18",
            "month": "10",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "18 10 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }],
        "containeritems": null,
        "roundbitmask": 0,
        "selected": true
    }, {
        "roundid": "asdf",
        "collectionday": "Fri",
        "containerid": "zxcvb",
        "collectioninterval": "Every week",
        "containerdescription": "Paper & Cardboard",
        "containername": "Paper & Cardboard",
        "nextcollectiondates": [{
            "day": "18",
            "month": "10",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "18 10 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }],
        "containeritems": null,
        "roundbitmask": 0,
        "selected": true
    }, {
        "roundid": "poiuyt",
        "collectionday": "Fri",
        "containerid": "lkjhg",
        "collectioninterval": "Every week",
        "containerdescription": "Plastic and Cans",
        "containername": "Plastic and Cans",
        "nextcollectiondates": [{
            "day": "18",
            "month": "10",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "18 10 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }],
        "containeritems": null,
        "roundbitmask": 0,
        "selected": true
    }, {
        "roundid": "qazwsx",
        "collectionday": "Fri",
        "containerid": "wsxedc",
        "collectioninterval": "Every 2 week",
        "containerdescription": "Waste wheeled bin",
        "containername": "Waste wheeled bin",
        "nextcollectiondates": [{
            "day": "25",
            "month": "10",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "25 10 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "8",
            "month": "11",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "08 11 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "22",
            "month": "11",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "22 11 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "6",
            "month": "12",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "06 12 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }, {
            "day": "20",
            "month": "12",
            "year": "2019",
            "hours": "0",
            "mins": "0",
            "datestring": "20 12 2019 12:00",
            "isaltered": false,
            "alteredmessage": null,
            "weeknumber": 0
        }],
        "containeritems": null,
        "roundbitmask": 0,
        "selected": true
    }]
}

Yeah, not the greatest scrap of JSON I've seen. And a datestring of 18 10 2019 12:00 is particularly painful.

But, you know what? It gets the job done. I was able to parse through it to get what bin day it is.

Building the skill

I've written a quick guide to self-hosting an Alexa skill.

I can't release this to the public because it uses personal information. And I don't want to be responsible for storing your email address or physical address.

The crappy source code is on GitLab. Enjoy!


Share this post on…

  • Mastodon
  • Facebook
  • LinkedIn
  • BlueSky
  • Threads
  • Reddit
  • HackerNews
  • Lobsters
  • WhatsApp
  • Telegram

3 thoughts on “"Alexa? Which bins do I need to take out?" - Reverse engineering the Bexley Council bin collection API”

What are your reckons?

All comments are moderated and may not be published immediately. Your email address will not be published.

Allowed HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <p> <pre> <br> <img src="" alt="" title="" srcset="">