Skip to main content

I am trying to integrate with the QuickBooks Time API, specifically with time off requests. The API docs are found here: https://tsheetsteam.github.io/api_docs/?java#retrieve-time-off-requests

 

 

 

Here is the code I have in Zapier. Note, I had to put a t] around the results to remove the “Results must be an array, got: object,” error.

const options = {
url: 'https://rest.tsheets.com/api/v1/time_off_requests',
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${bundle.authData.access_token}`
},
params: {

}
}

return z.request(options)
.then((response) => {
response.throwForStatus();
const results = response.json;

// You can do any parsing you need for results here before returning them

return results];
});

 

Here is a typical output according to the API docs. Note this is sample data:

{
"results": {
"time_off_requests": {
"1546922": {
"id": 1546922,
"user_id": 423,
"time_off_request_notes": _96024],
"time_off_request_entries": n11374, 11375],
"status": "pending",
"active": true,
"created": "2018-11-11T10:56:15-06:00",
"last_modified": "2018-11-11T10:56:15-06:00"
},
"1547432": {
"id": 1547432,
"user_id": 467,
"time_off_request_notes": _100021],
"time_off_request_entries": n11564],
"status": "pending",
"active": true,
"created": "2018-11-12T10:05:02-06:00",
"last_modified": "2018-11-12T10:05:02-06:00"
}
}
},
"more": false,
"supplemental_data": {
"users": {
"423": {
"id": 423,
"first_name": "Sean",
"last_name": "Evans",
"group_id": 34,
"active": true,
"employee_number": 4,
"salaried": false,
"exempt": false,
"username": "sevans",
"email": "sevans@hotones.com",
"payroll_id": 4,
"hire_date": "2017-04-27",
"term_date": "0000-00-00",
"job_title": "",
"gender": "",
"last_modified": "2017-09-21T15:52:00-06:00",
"last_active": "2018-11-12T11:45:15-06:00",
"created": "2017-04-27T07:23:44-06:00",
"mobile_number": ""
},
"467": {
"id": 467,
"first_name": "Guy",
"last_name": "Fieri",
"group_id": 34,
"active": true,
"employee_number": 5,
"salaried": false,
"exempt": false,
"username": "gfieri",
"email": "gfieri@foodnetwork.com",
"payroll_id": 5,
"hire_date": "2017-07-12",
"term_date": "0000-00-00",
"job_title": "",
"gender": "",
"last_modified": "2017-09-21T15:02:00-06:00",
"last_active": "2018-11-12T13:12:01-06:00",
"created": "2017-07-12T08:05:22-06:00",
"mobile_number": ""
}
},
"time_off_request_notes" : {
"96024": {
"id": 96024,
"time_off_request_id": 1546922,
"user_id": 423,
"active": true,
"note": "Taking a four day weekend to go on vacation.",
"created": "2018-11-11T10:56:15-06:00",
"last_modified": "2018-11-11T10:56:15-06:00"
},
"100021": {
"id": 100021,
"time_off_request_id": 1547432,
"user_id": 467,
"active": true,
"note": "It's my birthday!",
"created": "2018-11-12T10:05:02-06:00",
"last_modified": "2018-11-12T10:05:02-06:00"
}
},
"time_off_request_entries": {
"11374": {
"id": 11374,
"time_off_request_id": 1546922,
"status": "pending",
"approver_user_id": 0,
"date": "2018-11-15",
"entry_method": "manual",
"duration": 28800,
"start_time": "2018-11-15T00:00:00-07:00",
"end_time": "2018-11-15T23:59:59-07:00",
"tz_string": "America/Denver",
"jobcode_id": 1345687,
"user_id": 423,
"approved_timesheet_id": 0,
"active": true,
"created": "2018-11-11T10:56:15-06:00",
"last_modified": "2018-11-11T10:56:15-06:00"
},
"11375": {
"id": 11375,
"time_off_request_id": 1546922,
"status": "pending",
"approver_user_id": 0,
"date": "2018-11-16",
"entry_method": "manual",
"duration": 28800,
"start_time": "2018-11-16T00:00:00-07:00",
"end_time": "2018-11-16T23:59:59-07:00",
"tz_string": "America/Denver",
"jobcode_id": 1345687,
"user_id": 423,
"approved_timesheet_id": 0,
"active": true,
"created": "2018-11-11T10:56:15-06:00",
"last_modified": "2018-11-11T10:56:15-06:00"
},
"11564": {
"id": 11564,
"time_off_request_id": 1547432,
"status": "pending",
"approver_user_id": 0,
"date": "2018-01-22",
"entry_method": "manual",
"duration": 28800,
"start_time": "",
"end_time": "",
"jobcode_id": 1345645,
"user_id": 467,
"approved_timesheet_id": 0,
"active": true,
"created": "2018-11-12T10:05:02-06:00",
"last_modified": "2018-11-12T10:05:02-06:00"
}
}
}
}

 

The issue I am seeing is that it looks like the ID is nested, and I have no clue how to parse this out? I am a fairly new coder, how would I remove 

"time_off_requests": {

and the 

"1546922": {

to get down to the 

"id": 1546922,

level?

 

 

Hi @freshmandeveloper 

You can go deeper into objects using dot notation. i.e results.time_off_requests or bracket notation resultst“time_off_requests”].

However, if this is to build a trigger, you will need to return an array of objects as opposed to nested objects. 

you could return something like the below which would return all of the objects nested in time_off_requests but as an array
 

return Object.values(results.time_off_requests)

 


@GetUWired thanks for the help. Yes, this would be to build a trigger. 

I tried the recommended replacing 

return rresults]

with 

return Object.values(results.time_off_requests)

however now I am getting the following error:

 

 

Cannot convert undefined or null to object What happened (You are seeing this because you are an admin): Starting GET request to https://rest.tsheets.com/api/v1/time_off_requests Received 200 code from https://rest.tsheets.com/api/v1/time_off_requests after 1596ms Received content "{ "results": { "time_off_requests": { "303793": { "id": 303793, "user_id": 100629, " Cannot convert undefined or null to object

 

 


Ahh.. it might be results.results.time_off_requests if you’ve defined a variable  ‘results’ that looks like the output from the api docs which also has a nested object ‘results’ inside of it


That didn’t seem to work either :( return rresults.results.time_off_request]; give me an error of 

Cannot read property 'id' of undefined What happened (You are seeing this because you are an admin): Executing triggers.Retrieve_Time_Off_Requests.operation.perform with bundle Cannot read property 'id' of undefined Console logs: Stack trace: TypeError: Cannot read property 'id' of undefined at /var/task/node_modules/zapier-platform-core/src/checks/trigger-has-unique-ids.js:17:37 at arrayEach (/var/task/node_modules/lodash/lodash.js:516:11) at Function.forEach (/var/task/node_modules/lodash/lodash.js:9342:14) at Object.run (/var/task/node_modules/zapier-platform-core/src/checks/trigger-has-unique-ids.js:16:7) at /var/task/node_modules/zapier-platform-core/src/app-middlewares/after/checks.js:34:12 at Array.map (<anonymous>) at checkOutput (/var/task/node_modules/zapier-platform-core/src/app-middlewares/after/checks.js:32:8) at Object.<anonymous> (/var/task/node_modules/zapier-platform-core/src/middleware.js:80:37) at bound (domain.js:427:14) at Object.runBound (domain.js:440:12) at Object.tryCatcher (/var/task/node_modules/bluebird/js/release/util.js:16:23) at Promise._settlePromiseFromHandler (/var/task/node_modules/bluebird/js/release/promise.js:517:31) at Promise._settlePromise (/var/task/node_modules/bluebird/js/release/promise.js:574:18) at Promise._settlePromiseCtx (/var/task/node_modules/bluebird/js/release/promise.js:611:10) at _drainQueueStep (/var/task/node_modules/bluebird/js/release/async.js:142:12) at _drainQueue (/var/task/node_modules/bluebird/js/release/async.js:131:9) at Async._drainQueues (/var/task/node_modules/bluebird/js/release/async.js:147:5) at Immediate.Async.drainQueues uas _onImmediate] (/var/task/node_modules/bluebird/js/release/async.js:17:14) at processImmediate (internal/timers.js:461:21) at process.topLevelDomainCallback (domain.js:138:15) at process.callbackTrampoline (internal/async_hooks.js:124:14)


I think I need to go one layer deeper, but I don’t know how to strip or ignore the 

 

"1546922": { 

or 

"1547432": {

from the example, and get down to the id layer. Anyone have an idea? 


one thing that i often find useful when troubleshooting and going deeper into the JSON is to return (for testing purposes only) [{id: results}] then try returning {id: results.NEXT_LEVEL }] and so on. That way you are always returning what Zapier is looking for and can go one level at a time. The first step being to make sure you are returning valid results that look like the sample data. Then you can worry about going further into the nested objects.

Once you’ve figured out how to isolate your results to the time off requests object
you can use Object.values(your_time_off_requests_object) to turn an object with nested objects into an array of objects. 
 


one thing that i often find useful when troubleshooting and going deeper into the JSON is to return (for testing purposes only) [{id: results}] then try returning {id: results.NEXT_LEVEL }] and so on. That way you are always returning what Zapier is looking for and can go one level at a time. The first step being to make sure you are returning valid results that look like the sample data. Then you can worry about going further into the nested objects.

Once you’ve figured out how to isolate your results to the time off requests object
you can use Object.values(your_time_off_requests_object) to turn an object with nested objects into an array of objects. 
 

Now we are making progress, thanks for much for your help!

The 

return >{id: results.results.time_off_requests}];

worked great, and now I get something like this: 

g
{
"id": {
"198056": {
"id": 198056,
"user_id": 113181,
"status": "approved",
"start_date": "2020-05-08",
"end_date": "2020-05-08",
"total_duration": 28800,
"jobcode_id": 17506465,
"all_entries_locked": true,
"dates_requested": "May 8, 2020",
"time_off_request_notes": _
231332,
231375
],
"time_off_request_entries": n
236216
],
"active": true,
"created": "2020-05-08T13:33:44+00:00",
"last_modified": "2020-05-08T14:19:39+00:00",
"linked_objects": {
"time_off_request_notes": _
231332,
231375
],
"time_off_request_entries": n
236216
]
}
},
"303793": {
"id": 303793,
"user_id": 100629,
"status": "approved",
"start_date": "2020-05-25",
"end_date": "2020-05-25",
"total_duration": 28800,
"jobcode_id": 1964477,
"all_entries_locked": true,
"dates_requested": "May 25, 2020",
"time_off_request_notes": _
341935
],
"time_off_request_entries": n
347619
],
"active": true,
"created": "2020-05-26T14:09:51+00:00",
"last_modified": "2020-05-26T14:09:51+00:00",
"linked_objects": {
"time_off_request_notes": _
341935
],
"time_off_request_entries": n
347619
]
}
},
"303795": {
"id": 303795,
"user_id": 113181,
"status": "approved",
"start_date": "2020-05-25",
"end_date": "2020-05-25",
"total_duration": 28800,
"jobcode_id": 1964477,
"all_entries_locked": true,
"dates_requested": "May 25, 2020",
"time_off_request_notes": _
341937
],
"time_off_request_entries": n
347621
],
"active": true,
"created": "2020-05-26T14:09:51+00:00",
"last_modified": "2020-05-26T14:09:51+00:00",
"linked_objects": {
"time_off_request_notes": _
341937
],
"time_off_request_entries": n
347621
]
}
},

After that, I was about to do a 

return Object.values(results.results.time_off_requests);

and now my output looks correct, as it looks correct!

a
{
"id": 198056,
"user_id": 113181,
"status": "approved",
"start_date": "2020-05-08",
"end_date": "2020-05-08",
"total_duration": 28800,
"jobcode_id": 17506465,
"all_entries_locked": true,
"dates_requested": "May 8, 2020",
"time_off_request_notes": t
231332,
231375
],
"time_off_request_entries": e
236216
],
"active": true,
"created": "2020-05-08T13:33:44+00:00",
"last_modified": "2020-05-08T14:19:39+00:00",
"linked_objects": {
"time_off_request_notes": t
231332,
231375
],
"time_off_request_entries": e
236216
]
}
},
{
"id": 303793,
"user_id": 100629,
"status": "approved",
"start_date": "2020-05-25",
"end_date": "2020-05-25",
"total_duration": 28800,
"jobcode_id": 1964477,
"all_entries_locked": true,
"dates_requested": "May 25, 2020",
"time_off_request_notes": t
341935
],
"time_off_request_entries": e
347619
],
"active": true,
"created": "2020-05-26T14:09:51+00:00",
"last_modified": "2020-05-26T14:09:51+00:00",
"linked_objects": {
"time_off_request_notes": t
341935
],
"time_off_request_entries": e
347619
]
}
},
{
"id": 303795,
"user_id": 113181,
"status": "approved",
"start_date": "2020-05-25",
"end_date": "2020-05-25",
"total_duration": 28800,
"jobcode_id": 1964477,
"all_entries_locked": true,
"dates_requested": "May 25, 2020",
"time_off_request_notes": t
341937
],
"time_off_request_entries": e
347621
],
"active": true,
"created": "2020-05-26T14:09:51+00:00",
"last_modified": "2020-05-26T14:09:51+00:00",
"linked_objects": {
"time_off_request_notes": t
341937
],
"time_off_request_entries": e
347621
]
}
},


Now my question is, what do I do about the nested notes and entries at the bottom? 

  "time_off_request_notes": s
231332,
231375
],
"time_off_request_entries": _
236216
],

 


What do you need to do with them? If you want to also include them in your results then you’ve got quite a bit of data manipulation to do before you return anything. You can set the param ‘supplemental_data’ to yes which will return the details of those ids but you will need code that matches all of that up in a succinct response as supplemental data is a different object of nested objects. 


I would say before you tackle that problem, you are also going to run into another problem. From the results you’ve shared, it looks like your results are returned in order of the oldest created time off requests rather than the newest. 

For a polling trigger to work well, the newest or most recently updated items should be returned first. Otherwise, Zapier may only see the same 50 objects each time it polls and then it will never trigger unless you build in pagination to retrieve newer requests. 

 


What do you need to do with them? If you want to also include them in your results then you’ve got quite a bit of data manipulation to do before you return anything. You can set the param ‘supplemental_data’ to yes which will return the details of those ids but you will need code that matches all of that up in a succinct response as supplemental data is a different object of nested objects. 


I would say before you tackle that problem, you are also going to run into another problem. From the results you’ve shared, it looks like your results are returned in order of the oldest created time off requests rather than the newest. 

For a polling trigger to work well, the newest or most recently updated items should be returned first. Otherwise, Zapier may only see the same 50 objects each time it polls and then it will never trigger unless you build in pagination to retrieve newer requests. 

 

Yes, it seems I am facing this issue too. Is this a QuickBooks Time issue or a Zapier one? What do we do about pagination as I see there is an option for that on the Quickbooks Time requests. You can define how many pages and responses per page, whatever that means. 


A little bit of both, more of a QBT issue though. Zapier gives a 30 second window for your requests before timing out. Depending on how fast the api is you’re looking at being able to load maybe 20-25 pages of data in that time. 

Ideally, a well built polling trigger would use filters in the request to narrow down your results or be sorted with the newest created/updated items returned first.