Submit Expenses with Speech Recognition: Sample Code

Tired of filling out expense forms? Ready to simply tell your phone what you bought? This sample code shows how to bring this capability to life. We mashed up a Web Speech API with Concur's Quick Expense API to create a hassle-free approach to expense management.

This code demonstrates how to submit business expense with voice using the Web Speech API and the Concur SDK. You can check out the demo video here


The diagram above describes the flow of the demo, and the file/code where each logic is located. The Code section below will explain how this all works. This code uses the HTML5 Audio API (use Chrome) to record voice, and node.js to drive the backend (primarily to accept client calls and call out web APIs).


You would need to sign up a Concur developer sandbox account and generate an access token:

Once you have your access token, you can plug it in at config.js like below:

config.concur.accessToken = "<insert Concur access token here>";


Please refer to the diagram at the top of this README to follow the code explanation below:

  1. Tap the microphone icon on the app to start recording voice. Tap it again to end recording. You can pause in between sentences to add a new expense item.
  2. After the spoken words have been ‘recognized’ and translated into text by the Web Speech API, you can pick out the items that you want to send as expense to Concur (e.g. you can leave out incorrect recognitions, etc). After selecting the items by checkbox, they are transformed into a format similar to 5.43/coffee and sent to nodejs. These are done by the three functions below:

    function sendToConcur() {
        var itemsToSend = getSelectedItems();
        if(!itemsToSend) {
            bootbox.alert("Please select an item to expense..", function() {});
        // set up call to nodejs/express to send selected items
        var xhr = new XMLHttpRequest();"POST", '/expense', true);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
        // receive response from server
        xhr.onloadend = function () {
            var response = xhr.responseText;
            bootbox.alert(response, function() {});
    // iterates through checkbox group to pick out and format selected items
    function getSelectedItems() {
        var check_group = document.getElementsByName("chk_group");
        var selected_items = {};
        var ctr = 0;
        for(var i = 0; i < check_group.length; i++) { 
            if(check_group[i].checked == true) { 
                selected_items[ctr.toString()] = cleanItem(check_group[i].value);             
        if(ctr > 0) return selected_items; else return false;
    // Formats to '<amt>/<comment>' e.g. '45.34/transportation'
    function cleanItem(itemText) {
            // ... 
  3. When the items are received by the server, the Concur SDK for nodejs calls the QuickExpense API for each item (e.g. 4 items = 4 QuickExpense API calls). The corresponding items then show as expense in Concur:'/expense', function(req, res) {
        var selected_items = req.body;
        var count = 0;
        var item_count = Object.keys(selected_items).length;
        // async module will call API in series...
            function () { return count < item_count; },
            function (callback) {
                sendQuickExpense(selected_items[count -1], callback);
            function (err) {
                res.send("Quick expense created!");
    function sendQuickExpense(item, callback) {
        var itemArr = item.split("/");
        var amount = itemArr[0];
        var comment = itemArr[1];
        var now = new Date();
        var year = now.getFullYear();
        var month = now.getMonth();
        var date = now.getDate();
        var fullDate = year + '-' + (month +1) + '-' + date;
        var concurBody = {
            "CurrencyCode": "USD",
            "TransactionAmount": amount,
            "VendorDescription": comment,
            "TransactionDate": fullDate
        var options = {
            oauthToken: config.concur.accessToken,
            console.log("QuickExpense created! " + amount);
        .fail(function (error) {
            //Error contains the error returned

Ready to Build?

If you're interested in building a speech recognition app or other solution using Concur APIs, here's a few tools to get started:


Loading next article