Introduction
Scorebuddy supports extraction of score data (ie. results) via the OpenAPI so that it may be used in your external applications such as business intelligence platforms, or for storage of historical result data. In this article we will run through how to set up an API client for interacting with the Scorebuddy API, the endpoints that can be used for extracting the score data from Scorebuddy, and other endpoints which may be useful in understanding the data returned.
Detail on all Scorebuddy endpoints can be found within our developer documentation by navigating to Admin > Developers > Documentation, or by requesting the yaml via the API.
Creating your Scorebuddy API Client
Scorebuddy utilizes OAuths Client Credentials grant type for accessing information via the OpenAPI. (More information on this can be found at: https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/)
We must first set up our client in order to create the client ID and client secret for verification. These can be set up by navigating to Admin > Developers > API Clients
Within the API clients page you can view, access, edit or delete already created API clients. To add a new client select 'Add API Client'.
Within the pop up you can give the client a name which will generate the client ID and also determine the necessary scopes for the client (For accessing export
endpoint we will need to ensure that the client has the scope export:read
, or if we are accessing the scores
endpoint we need to make sure the scores:read
scope is selected). You can limit the access of the API client you create to select Groups or Teams within Scorebuddy, meaning that only the data associated with said Groups and Teams may be accessed using the client (Note: selecting no groups and teams will give the API client Global Access, similar to Global Admins, selecting all Groups and Teams will limit the API to the selection and not provide Global Access). Make sure to save the client secret before closing this page as it will not be visible again once the client is created.
With our API client set up we are now ready to start interacting with the API.
Using the API
Requesting Bearer token
First we will need to request our bearer token for authenticating requests to the API. This can be done via an API platform such as Postman or programmatically (example code snippets below will be shown in Python using the requests module).
NB. {{URL}} in the below endpoints refers to your own Scorebuddy URL (eg https://www.cloud.scorebuddy.co.uk/1234567)
To request a bearer token you must make a POST request to {{url}}/api/v1/authorisation/token
. You must ensure that when requesting your token that you also include the relevant scopes you wish the token to have. In our case we need to include either export:read
or scores:read
or both within a scope key.
Postman example:
Python request:
import requests
url = "https://www.cloud.scorebuddy.co.uk/1234567/api/v1/authorisation/token"
payload={
'grant_type': 'client_credentials',
'client_id': '<client_id>' #enter your own client id here
'client_secret': '<client_secret>' #enter your own client secret here
'scope': 'export:read scores:read'
}
files=[]
headers = {}
response = requests.request("POST", url, headers=headers, data=payload, files=files)
print(response.text)
Example response:
{
"token_type":"Bearer",
"expires_in":3600,
"access_token":"<bearer_token>"
}
This bearer token can now be used to validate subsequent requests made to the API.
Endpoints
Within Scorebuddy there are two different endpoints which can be used to extract score data; /export/questions
and /scores
. The main difference between the two endpoints is the structure of the data which is returned. The /export/questions
returns data in a structure similar to that which is seen by using the manual export function within Scorebuddy (located under Admin > Export). The manual export will display each question within a result in its own row, similarly, the /export/questions
endpoint will return the data structured in the same way (ie. each object returned is a question within a result). The /scores
endpoint, will return a list of scores where each object is its own individual result, and the associated data such as questions is nested within each object. Examples of the returned data will be shown within the relevant section below.
/export/questions
As mentioned above, the /export/questions
end point will return each question from each result as its own object.
The endpoint itself accepts a number of different query parameters to help filter down the returned results such as from_last_edit_date
and to_last_edit_date
(in the format of YYYY-MM-DD H:i:S
) as well as group_ids, page and limit parameters. In the below Postman example you can see the last edit date and limit parameters being used to narrow the search
Postman:
Python:
import requests
url = "https://www.cloud.scorebuddy.co.uk/1234567/api/v1/export/questions?limit=100&from_last_edit_date=2020-10-14 00:00:00&to_last_edit_date=2024-10-14 00:00:00"
payload = {}
headers = {
'Authorization': 'Bearer <bearer_token>, #insert your generated bearer token here
'Content-Type': 'application/json'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
If you wished to programmatically retrieve the information you should consider implementing some logic to substitute the hardcoded dates above. Below is an example of how that might be done to fetch the most recent two weeks worth of data.
Python:
import requests
from datetime import datetime, timedelta
# Calculate dates
to_date = datetime.now()
from_date = to_date - timedelta(days=14)
# Format dates to the required format
from_last_edit_date = from_date.strftime('%Y-%m-%d %H:%M:%S')
to_last_edit_date = to_date.strftime('%Y-%m-%d %H:%M:%S')
# Use the above date variables within the URL
url = f"https://www.cloud.scorebuddy.co.uk/1234567/api/v1/export/questions?limit=100&from_last_edit_date={from_last_edit_date}&to_last_edit_date={to_last_edit_date}"
payload = {}
headers = {
'Authorization': 'Bearer <bearer_token>, #insert your generated bearer token here
'Content-Type': 'application/json'
}
response = requests.get(url, headers=headers, data=payload)
print(response.text)
Example response:
{
"export": [
{
"score_id": 30,
"staff_id": 617,
"external_id": "A-G35T4122",
"first_name": "Carrie",
"last_name": "Gilden",
"scorecard_id": 70,
"category": "Phone Call",
"event_date": "2024-05-01 13:53:44",
"group_id": 35,
"group_name": "Outsourcer/BPO",
"evaluator": "Billy Yoder",
"team_name": "Outsourcer Team B",
"supervisor": "Billy Yoder",
"score_date": "2024-05-01",
"last_edit_date": "2022-07-13 00:32:35",
"total_score": 63,
"maximum_score_possible": 83,
"target": 90,
"question_id": 16030,
"question": "Did the agent follow the Opening Script?",
"section_id": 468,
"section_name": "Opening and Closing",
"answer": "Yes",
"sum": 1,
"answer_values": [
1,
0.5,
0
],
"weighting": 1,
"scorecard_name": "Outsourcer/BPO Phone call",
"version_number": 1,
"fail_section": false,
"fail_all": false,
"fail_all_zeros": true,
"reference": "98563214",
"event_name": "Business Direct",
"sub_event_name": "Sales",
"event_duration": "02:07:11",
"comment_ids": [],
"answer_comments": [],
"general_comments": [],
"tag_ids": null,
"custom_fields": [],
"reviewed": false,
"review_date": null,
"reviewed_by": null
},
{
"score_id": 30,
"staff_id": 617,
"external_id": "A-G35T4122",
"first_name": "Carrie",
"last_name": "Gilden",
"scorecard_id": 70,
"category": "Phone Call",
"event_date": "2024-05-01 13:53:44",
"group_id": 35,
"group_name": "Outsourcer/BPO",
"evaluator": "Billy Yoder",
"team_name": "Outsourcer Team B",
"supervisor": "Billy Yoder",
"score_date": "2024-05-01",
"last_edit_date": "2022-07-13 00:32:35",
"total_score": 63,
"maximum_score_possible": 83,
"target": 90,
"question_id": 16031,
"question": "Did the agent ask for correct account details?",
"section_id": 472,
"section_name": "Data Protection",
"answer": "Pass",
"sum": 0,
"answer_values": [
1,
0,
0
],
"weighting": 0,
"scorecard_name": "Outsourcer/BPO Phone call",
"version_number": 1,
"fail_section": false,
"fail_all": false,
"fail_all_zeros": true,
"reference": "98563214",
"event_name": "Business Direct",
"sub_event_name": "Sales",
"event_duration": "02:07:11",
"comment_ids": [],
"answer_comments": [],
"general_comments": [],
"tag_ids": null,
"custom_fields": [],
"reviewed": false,
"review_date": null,
"reviewed_by": null
}
}
/scores
The same process can be followed should you wish to access the results via the /scores
endpoint. Again, as mentioned in the Endpoints section above, the returned data structure is different from that of the /export/questions
endpoint. An example of the response data structure will be shown below.
Similar to the /export/questions
endpoint, a number of query parameters are available for use when making a request to the endpoint to help narrow down the data returned should that be required. The same parameters of from_last_edit_date
and to_last_edit_date
are available, in addition to many more which are not available for /export/questions
such as staff_id
, scorecard_id
as well as date filtering based on the score date or the event date. A full list of the available parameters for the endpoint can be found in the API documentation within the developer section of your Scorebuddy account.
Postman:
Python:
import requests
url = "https://www.cloud.scorebuddy.co.uk/1234567/api/v1/scores?from_score_date=2024-10-01&to_score_date=2024-10-31&scorecard_id=78"
payload = {}
files=[]
headers = {
'Authorization': 'Bearer <bearer_token>, #insert your generated bearer token here
'Content-Type': 'application/json'
}
response = requests.request("GET", url, headers=headers, data=payload, files=files)
print(response.text)
Example response:
{
"scores": [
{
"score_id": 469,
"scorecard_id": 78,
"version_number": 2,
"staff_id": 607,
"supervisor_id": 595,
"evaluator_id": 72,
"group_id": 34,
"team_id": 37,
"event_date": "2024-10-04 10:25:10",
"event_duration": "00:14:14",
"score_date": "2024-10-04",
"last_edit_date": "2024-10-12 10:49:54",
"reference": "A2rtE953",
"event_id": 42,
"sub_event_id": null,
"custom_objects": [
{
"object_id": 16,
"tag_ids": [
56
],
"text_input": null
},
{
"object_id": 17,
"tag_ids": [
58
],
"text_input": null
}
],
"questions": [
{
"question_id": 16352,
"answer_key": 0,
"cause_id": null,
"comment_id": null,
"free_text_comment": "Good work"
},
{
"question_id": 16353,
"answer_key": 0,
"cause_id": 953,
"comment_id": null,
"free_text_comment": null
},
{
"question_id": 16354,
"answer_key": 0,
"cause_id": null,
"comment_id": 1196,
"free_text_comment": null
}
],
"score": 72,
"percentage": 100,
"total_score": 72,
"max_score": 72,
"below_target": false,
"fail_all": false,
"comments": [],
"interaction_id": null,
"auto_score_used": true,
"draft_used": false
}
],
"total": 1,
"next_page": null,
"previous_page": null
}
Further API requests
We can see from the responses above that while the /export/questions
response returns ID's as well as name values (eg. scorecard_id
as well as scorecard_name
) the /scores
endpoint condenses the information down to just the relevant ID's.
If we want to get more information on the values returned we can target the specific endpoints to gather that data.
If we take the above /scores response we can see the scorecard_id
returned is 78, and a version_number
value of 2 is also returned. We can now make a request to /scorecards/{id}/versions/{version}
to retrieve information about that particular scorecard. Note that you will need to ensure that your bearer token has the scorecards:read
permission to access the /scorecards
endpoints.
Postman:
Python:
import requests
url = "https://www.cloud.scorebuddy.co.uk/1234567/api/v1/scorecards/78/versions/2"
payload = {}
headers = {
'Authorization': 'Bearer <bearer_token>, #insert your generated bearer token here
'Content-Type': 'application/json'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
Example response:
{
"version": {
"version_number": 2,
"scorecard_name": "Financial and Insurance E-mail",
"category_id": 8,
"group_id": 34,
"description": null,
"department": null,
"location": "Dublin",
"target": 85,
"max_score": 69,
"number_of_questions": 12,
"questions": [
{
"question_id": 16352,
"question_number": 1,
"question": "Did the agent use the correct salutation?",
"section_id": 505,
"weighting": 5,
"max_score": 5,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": []
},
{
"question_id": 16353,
"question_number": 2,
"question": "Did the agent acknowledge the customers issue and previous contact (if any)?",
"section_id": 505,
"weighting": 5,
"max_score": 5,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 3,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 2,
"answer_label": "N/A",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": true,
"cause_mandatory": false
}
],
"causes": [
{
"unique_rca_id": 6,
"rca_id": 359,
"rca_name": "Agent behavior",
"level": "section",
"deleted": false
}
]
},
{
"question_id": 16354,
"question_number": 3,
"question": "Did the agent apologize where appropriate?",
"section_id": 505,
"weighting": 2,
"max_score": 2,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": [
{
"unique_rca_id": 11,
"rca_id": 360,
"rca_name": "Lack of training",
"level": "section",
"deleted": false
},
{
"unique_rca_id": 6,
"rca_id": 361,
"rca_name": "Agent behavior",
"level": "section",
"deleted": false
}
]
},
{
"question_id": 16355,
"question_number": 4,
"question": "Did the agent summarize issues and give details of investigation?",
"section_id": 506,
"weighting": 5,
"max_score": 5,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": [
{
"unique_rca_id": 10,
"rca_id": 362,
"rca_name": "Lack of knowledge",
"level": "section",
"deleted": false
}
]
},
{
"question_id": 16356,
"question_number": 5,
"question": "Did the agent clearly explain the decision making rational and the decision?",
"section_id": 506,
"weighting": 10,
"max_score": 10,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": []
},
{
"question_id": 16357,
"question_number": 6,
"question": "Was the e-mail structure clear and concise - easy to understand?",
"section_id": 506,
"weighting": 5,
"max_score": 5,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": [
{
"unique_rca_id": 60,
"rca_id": 363,
"rca_name": "Template issue",
"level": "section",
"deleted": false
}
]
},
{
"question_id": 16358,
"question_number": 7,
"question": "Is the format of the e-mail correctly follow client guidelines (font, layout, etc.)?",
"section_id": 507,
"weighting": 5,
"max_score": 5,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": []
},
{
"question_id": 16359,
"question_number": 8,
"question": "Is the e-mail written in a polite tone using easy to understand language, avoiding 'technical' jargon?",
"section_id": 507,
"weighting": 10,
"max_score": 10,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": [
{
"unique_rca_id": 4,
"rca_id": 365,
"rca_name": "Training gap",
"level": "section",
"deleted": false
}
]
},
{
"question_id": 16360,
"question_number": 9,
"question": "Correct spelling/grammar.",
"section_id": 507,
"weighting": 15,
"max_score": 15,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": [
{
"unique_rca_id": 61,
"rca_id": 364,
"rca_name": "Soft skill upgrade needed",
"level": "section",
"deleted": false
}
]
},
{
"question_id": 16361,
"question_number": 10,
"question": "Did the agent make an appropriate offer of goodwill with in Company guidelines?",
"section_id": 508,
"weighting": 1,
"max_score": 1,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 3,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 2,
"answer_label": "N/A",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": true,
"cause_mandatory": false
}
],
"causes": []
},
{
"question_id": 16362,
"question_number": 11,
"question": "Did the agent use a personal phone number and department e-mail address?",
"section_id": 509,
"weighting": 5,
"max_score": 5,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": []
},
{
"question_id": 16363,
"question_number": 12,
"question": "Did agent close the e-mail with the appropriate invitation for customer to re-contact us if necessary?",
"section_id": 509,
"weighting": 1,
"max_score": 1,
"fail_section": false,
"fail_all_zeros": false,
"cause_mandatory": false,
"number_of_answers": 2,
"answers": [
{
"answer_key": 0,
"answer_label": "Met",
"answer_value": 1,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
},
{
"answer_key": 1,
"answer_label": "Not Met",
"answer_value": 0,
"point_of_failure": false,
"fail_section": false,
"not_applicable": false,
"cause_mandatory": false
}
],
"causes": []
}
]
}
}
Similarly if we wish to request details on the staff the score was associated with we can make a call to the /staff/employees/{employee_id}
endpoint. Again, make sure that your token has the required scopes to access the endpoint. In this case the required scope would be staff:read
.
Postman:
Python:
import requests
url = "https://www.cloud.scorebuddy.co.uk/1234567/api/v1/staff/employees/607"
payload = {}
headers = {
'Authorization': 'Bearer <bearer_token>, #insert your generated bearer token here
'Content-Type': 'application/json'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
Example response:
{
"employee": {
"staff_id": 607,
"external_id": "A-G34T378",
"first_name": "Bobby",
"last_name": "Hill",
"email_address": "bobbyhill@financialservices.com",
"group_ids": [
34
],
"team_ids": [
37
],
"notes": null,
"role": "employee",
"deleted": false,
"supervisor_id": 595,
"dashboard": true,
"self_score": false,
"can_score_peers": false,
"employment": "full_time",
"personal_goals": false,
"salesforce_id": null
}
}
Similar requests can be made with each of the ID's returned by targeting the relevant endpoints, a full list of which is available in the developer documentation within your Scorebuddy instance.