07-14-2023 07:30 AM - edited 07-14-2023 07:30 AM
I am attempting to make an API request to get data from a report. This is the URL I use for the request:
https://api.servicetitan.io/reporting/v2/tenant/{my tenant ID}/report-category/operations/reports/1864/data?page=1&pageSize=100
"1864" corresponds with the Call Center Performance by CSR report.
I use the following Google Apps Script (modified JavaScript) code to make my request:
const csrReportId = 1864;
const appKey = /* secret */;
// Try to over-request so that requests don't need to be repeated.
const requestItems = 100;
const requestUrl = `https://api.servicetitan.io/reporting/v2/tenant/${tenantId}/report-category/operations/reports/${csrReportId}/data?page=1&pageSize=${requestItems}`;
const csrRequestBase = {
"method": "post",
"headers": {
"Content-Type": "application/x-www-form-urlencoded",
"ST-App-Key": appKey
},
"payload": {
"client_id": clientId,
"client_secret": clientSecret
}
};
function requestCsrReport()
{
let csrRequest = addAuthToRequest(csrRequestBase);
Logger.log(csrRequest["headers"]["ST-App-Key"]);
let request = UrlFetchApp.fetch(requestUrl, csrRequest);
Logger.log(request.getContentText());
}
*addAuthToRequest is a custom function in a different file. That code is later in this post.
I have tested this code and verified it works with other parts of the API (I tested getting report categories using /report-categories and getting reports in a category using /{report_category}/reports).
The API returns a 400 error for the request, saying "Missed report parameter: [From]". Here is the full response:
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"Missed report parameter: [From]","status":400,"traceId":"7e6335346b322d22-IAD","errors":{"":["Missed report parameter: [From]"]},"data":{"ErrorCode":"MissedReportParameter"}}
I would like to edit my request so that the API successfully returns the report's data.
Here is the full content of the other file:
const tokenUrl = "https://auth.servicetitan.io/connect/token";
const clientId = /* secret! */;
const clientSecret = /* very secret!! >:O */;
const tenantId = /* a little secret? */;
const accessRequest = {
"method": "post",
"header": {
"Content-Type": "application/x-www-form-urlencoded"
},
"payload": {
"grant_type": "client_credentials",
"client_id": clientId,
"client_secret": clientSecret
}
};
// Add the access token to the given request's headers and return modified request.
function addAuthToRequest(request)
{
request["headers"]["Authorization"] = getAccessToken();
return request;
}
// Gets the current OAuth 2.0 access token or requests a new one if it is expired.
function getAccessToken()
{
let accessToken = requestAccessToken();
// Commented out to make debugging easier
// let accessToken = CacheService.getScriptCache().get("access-token");
Logger.log(accessToken);
if(accessToken !== null)
return accessToken;
else
return requestAccessToken();
}
// Requests and returns an OAuth 2.0 access token from the ServiceTitan production environment.
function requestAccessToken()
{
let response = UrlFetchApp.fetch(tokenUrl, accessRequest);
if(response.getResponseCode() === 200)
{
let json = JSON.parse(response);
let accessToken = json["access_token"];
let expireSeconds = Date.now() + json["expires_in"];
// Store the access token in the cache until it is expired.
CacheService.getScriptCache().put("access-token", accessToken, expireSeconds);
return accessToken;
}
else
throw new Error(`ServiceTitan gave response code ${response.getResponseCode()}.`);
}
Here is the cURL equivalent of my request, which gives the same error:
curl --request POST --url "https://api.servicetitan.io/reporting/v2/tenant/{my tenant id here}/report-category/operations/reports/1864/data?page=1&pageSize=100" --header "ST-App-Key: {my app key}" --header "Authorization: {my valid OAuth 2.0 token}" --data client_id={my client id} --data client_secret={my client secret}
As a side note, I have also tried adding a "From" parameter in the URL, headers, payload/data, and in various other places in my request, including as an array (i.e. ["From"]: 1). This has given the same error.
Solved! Go to Solution.
07-25-2023 08:54 AM
I resolved the issue with the Integrations team! The request body must contain a parameters object that sets the date range for the report.
We edited my code to this:
const csrRequestBase = {
"method": "post",
"headers": {
"Content-Type": "application/json",
"ST-App-Key": appKey,
"client_id": clientId,
"client_secret": clientSecret
},
// JSON.stringify needed so that Apps Script doesn't convert the "parameters" array into its type name.
"payload": JSON.stringify({
"parameters": [
{
"name": "From",
"value": "2023-07-01"
},
{
"name": "To",
"value": "2023-07-11"
}
]
})
};
We also found that the Content-Type header must be "application/json".
07-25-2023 08:54 AM
I resolved the issue with the Integrations team! The request body must contain a parameters object that sets the date range for the report.
We edited my code to this:
const csrRequestBase = {
"method": "post",
"headers": {
"Content-Type": "application/json",
"ST-App-Key": appKey,
"client_id": clientId,
"client_secret": clientSecret
},
// JSON.stringify needed so that Apps Script doesn't convert the "parameters" array into its type name.
"payload": JSON.stringify({
"parameters": [
{
"name": "From",
"value": "2023-07-01"
},
{
"name": "To",
"value": "2023-07-11"
}
]
})
};
We also found that the Content-Type header must be "application/json".
07-19-2023 01:30 AM
Hi @LaineG and thanks for bringing this to our attention!
Are you still getting the error? Let me know if you need help with this so I resubmit a case to support for further investigation. Thank you for letting us assist you!
07-24-2023 08:30 AM
Yes, I am still having this error. I am working with the Integrations team to try to resolve the issue. Any additional support would be appreciated, as we have not yet found a solution.
Thanks!