Extracting Employee Data with the BambooHR API: Practical Examples

Written by Jura Gorohovsky

Human Resource Information System (HRIS) platforms are software services that integrate HR functions and streamline HR activities. These activities may include employee data management, recruitment, onboarding and offboarding, performance management, leave and vacation tracking, processing employee feedback, analytics, and reporting. HRIS platforms also often include self-service portals for employees to access important information and track attendance.

Bamboo API docs

BambooHR is a popular cloud-based HRIS platform that's pretty powerful by itself, but it also serves as a common target with which other services can integrate. To enable this, BambooHR provides 125-plus prebuilt integrations on its Marketplace. If there's no existing integration for a given service, BambooHR also provides a REST API to access and update employee data programmatically.

This article explains one possible way of using the BambooHR API to retrieve employee data using Python. Having done that, you'll be able to use your existing knowledge to route the data to a different system with which you're integrating BambooHR.

What is the BambooHR API

The BambooHR API is a RESTful API for integrating other applications into BambooHR. It focuses on synchronizing employee data and generating employee reports. The API provides a variety of endpoints that can retrieve and update employee records and files, company-wide data, benefits, job descriptions and applications, employee training types, and records.

You can access the API using a specific API key if you're developing an integration on behalf of a single customer or use OpenID Connect for authorization if you're serving multiple BambooHR customers.

BambooHR API is particularly useful when your company is a power user of BambooHR, allowing you to automate workflows and customize the platform to perfectly meet your specific needs, such as payroll or employee onboarding. You may want to use state-of-the-art payroll processing or onboarding software as a service (SaaS) that better suits your needs. In this case, if the BambooHR Marketplace doesn't provide an existing integration, your best bet is to use the BambooHR API to synchronize employee data with the other service that you're using.

What if you're working with a SaaS vendor that's not using but developing a state-of-the-art SaaS for onboarding or payroll? If so, you absolutely need to provide integrations with BambooHR and other HRIS platforms using their respective APIs.

How to Extract Employee Data with the BambooHR API

Whatever motivates you to explore the BambooHR API, chances are that your integration efforts start with getting employee information from BambooHR. Let's see how exactly you can retrieve employee data from your BambooHR instance using the BambooHR API and Python.

To follow along, you need two things:

  1. The latest Python 3 installed on your machine.
  2. A BambooHR account.

Signing Up for a BambooHR Account

If you don't have a BambooHR account, sign up for a free trial.

After filling out the free trial form, create a name for your BambooHR domain (it's going to be used for your instance's subdomain, as in

https://your-bamboohr-domain.bamboohr.com/
) and then log in.

Your trial BambooHR account is pre-populated with sample employee records, so you don't need to worry about coming up with data to follow this tutorial.

Getting Your API Key

To make calls to the BambooHR API, you need to create and copy an API key from your BambooHR account. Here's what you should do:

  1. In BambooHR's navigation menu, click the user icon on the far right. In the pop-up menu that appears, click API Keys:

Navigating to the API key management in BambooHR

  1. In the My API Keys view, click Add New Key.
  2. In the Add New API Key view, enter
    bamboohr-python
    as the key name and then click Generate Key.
  3. When BambooHR displays your newly generated API key, click Copy Key and then click Done.
  4. When the API key is shown, copy and paste it to a safe place as BambooHR won't show it again.

Setting Up a Python Application

You need to set up some boilerplate for the Python application that grabs data from the BambooHR API.

Open the terminal and then create and go to a directory called

bamboohr
that hosts your application:

mkdir bamboohr && cd bamboohr

Inside the

bamboohr
directory, create a new Python virtual environment that is hosted in the
env
subdirectory. If you're on macOS or Linux, use the following command:

python3 -m venv env

On Windows, run this instead:

python -m venv env

Activate the new virtual environment in your terminal.

To do this on macOS or Linux, run the following:

source env/bin/activate

If you're on Windows, run the following activation script instead:

env\Scripts\activate.bat

You need Python's

requests
package to send API requests. Install it by running the following command:

pip install requests

Finally, create a Python file to host the source code that you'll write shortly:

touch main.py

Getting Employee Data from the BambooHR API

Open your newly created

main.py
file in your favorite code editor.

Once inside the file, start by adding your needed

import
statements:

from pathlib import Path
import requests
import json

These statements import the following:

  1. The
    requests
    package that you've recently installed to make requests to the BambooHR API.
  2. The built-in
    json
    module to parse the data you are getting from the API. Write it into a file.
  3. The
    Path
    class from the built-in
    pathlib
    module that helps you create a directory to save the file to.

Next, let's define the base URL that you'll be reusing in your API calls. Paste the following code into your Python file:

domain = 'your-bamboohr-domain'
base_url = f'https://api.bamboohr.com/api/gateway.php/{domain}/v1'

Make sure to replace

your-bamboohr-domain
with the actual BambooHR domain that you specified when creating your BambooHR account. It's used as the subdomain in the URL of your BambooHR instance:
https://your-bamboohr-domain.bamboohr.com/
.

The

base_url
uses your domain name to form your individual base BambooHR API URL that you need to make specific API requests later on.

Let's now save your BambooHR API key to the

api_key
constant. You also define another constant for authentication in the format that the
requests
package needs it to be in:

api_key = 'your-api-key'
auth = (api_key, '')

Remember to replace

your-api-key
with your actual BambooHR API key that you generated and stashed earlier. Note that in the
auth
tuple, the second value is an empty string: the BambooHR API uses basic HTTP authentication where the API key is used as the username and the password can be any string, including an empty string.

The last thing you do to prepare for sending API requests is to declare a

headers
object that sets the
Accept
HTTP header to JSON. If you don't do this, BambooHR will return the results as XML by default:

headers = {
    'Accept': 'application/json'
}

Now, let's add stubs for the three functions that perform the main steps of your program:

def get_all_employees():
    pass

def print_all_employees(employees_json):
    pass

def save_all_employees_to_file(employees_json):
    pass

all_employees = get_all_employees()

get_all_employees()
makes an API call and returns text data.
print_all_employees()
displays formatted data in the console output.
save_all_employees_to_file()
saves the data to a file.

You're also calling the first of these functions and saving whatever it returns to the

all_employees
variable.

Let's now fill the stub of the

get_all_employees()
function with code that makes an API request for the list of employees in the BambooHR directory. Update
get_all_employees()
to read the following:

def get_all_employees():
    try:
        response_all_employees = requests.request('GET', f'{base_url}/employees/directory', headers=headers, auth=auth)
        if response_all_employees.status_code == 200:
            return response_all_employees.text
        else:
            print(
                f'Something went wrong when trying to get the requested info from the API server. Status code: {response_all_employees.status_code}. Message: {response_all_employees.reason}')
    except Exception as e:
        print("An error occurred while performing the API call: ", e)

Here's what happens inside the updated function:

  1. The
    request
    function from the
    requests
    package makes a GET HTTP request to the BambooHR API's Get Employee Directory endpoint. It uses string interpolation to combine the base URL that you've defined earlier with the path of the endpoint. The
    headers
    and
    auth
    constants are passed as respective arguments. The result of the call is saved into the
    response_all_employees
    variable.
  2. The
    response_all_employees
    is a
    Response
    object
    . The
    request
    function returning this object may throw several exceptions, so the code in the function is wrapped into a
    try…except
    block to provide basic handling for these.
  3. The check for status code
    200
    verifies if your request has been successfully fulfilled, provided that no exceptions occur. If it's successful, then you return the textual employee data that arrived from the API, which is available through the
    text
    property of the
    response_all_employees
    object.
  4. The status code and error message that the API has returned are simply printed if the status code of the response is anything other than
    200
    .

The employee directory data that the API returns looks something like this:

{
  "fields": [
    {
      "id": "displayName",
      "type": "text",
      "name": "Display name"
    },
    {
      "id": "firstName",
      "type": "text",
      "name": "First name"
    },
    // more field definitions
  ],
  "employees": [
    {
      "id": "4",
      "displayName": "Charlotte Abbott",
      "firstName": "Charlotte",
      "lastName": "Abbott",
      "preferredName": null,
      "jobTitle": "Sr. HR Administrator",
      "workPhone": "801-724-6600",
      "mobilePhone": "801-724-6600",
      "workEmail": "cabbott@efficientoffice.com",
      "department": "Human Resources",
      "location": "Lindon, Utah",
      "division": "North America",
      "linkedIn": "www.linkedin.com",
      "instagram": "@instagram",
      "pronouns": null,
      "workPhoneExtension": "1272",
      "supervisor": "Jennifer Caldwell",
      "photoUploaded": true,
      "photoUrl": "https:\/\/images7.bamboohr.com\/619596\/photos\/4-6-4.jpg?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9pbWFnZXM3LmJhbWJvb2hyLmNvbS82MTk1OTYvKiIsIkNvbmRpdGlvbiI6eyJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTcyMDAxMTM2MH0sIkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNzIyNjAzMzcwfX19XX0_&Signature=Ewi31wP5h7aoUEQSEmWEj7vAvo7GTVNXIjk4iJQ9XKGiczzwlUR~EJLrtaClvCxxsvxq0~cdVQ1yJO1fLe0loSP1t5BE48bOZQ0biOqawfMToqTmCBEV7ADxdvqs0rMnGVm9cV6F~p7LewXfp51lgW7u6A2TTudkWCbJN5btsJVp~1UaVyMBsZFwujYvFgrXxXLYxfb4WsvW3bVsnfnmMO8eHq5SIZw~joaII7sEJdzHKaMdsNaMP9GCUY-Mrs~~Gt9JcV3rt8M-YxXH8Rxmo18xmqCqePDML8ETWDWkhzjrDSNv6shU9PjuxhujxDJ6e0BVZ8XYKMn5sTUf9tlltQ__&Key-Pair-Id=APKAIZ7QQNDH4DJY7K4Q",
      "canUploadPhoto": 1
    },
    {
      "id": "5",
      "displayName": "Ashley Adams",
      "firstName": "Ashley",
      "lastName": "Adams",
      "preferredName": null,
      "jobTitle": "HR Administrator",
      "workPhone": "+44 207 555 4730",
      "mobilePhone": "+44 207 555 6671",
      "workEmail": "aadams@efficientoffice.com",
      "department": "Human Resources",
      "location": "London, UK",
      "division": "Europe",
      "linkedIn": "www.linkedin.com",
      "instagram": "@instagram",
      "pronouns": null,
      "workPhoneExtension": "130",
      "supervisor": "Jennifer Caldwell",
      "photoUploaded": true,
      "photoUrl": "https:\/\/images7.bamboohr.com\/619596\/photos\/5-6-4.jpg?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9pbWFnZXM3LmJhbWJvb2hyLmNvbS82MTk1OTYvKiIsIkNvbmRpdGlvbiI6eyJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTcyMDAxMTM2MH0sIkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNzIyNjAzMzcwfX19XX0_&Signature=Ewi31wP5h7aoUEQSEmWEj7vAvo7GTVNXIjk4iJQ9XKGiczzwlUR~EJLrtaClvCxxsvxq0~cdVQ1yJO1fLe0loSP1t5BE48bOZQ0biOqawfMToqTmCBEV7ADxdvqs0rMnGVm9cV6F~p7LewXfp51lgW7u6A2TTudkWCbJN5btsJVp~1UaVyMBsZFwujYvFgrXxXLYxfb4WsvW3bVsnfnmMO8eHq5SIZw~joaII7sEJdzHKaMdsNaMP9GCUY-Mrs~~Gt9JcV3rt8M-YxXH8Rxmo18xmqCqePDML8ETWDWkhzjrDSNv6shU9PjuxhujxDJ6e0BVZ8XYKMn5sTUf9tlltQ__&Key-Pair-Id=APKAIZ7QQNDH4DJY7K4Q",
      "canUploadPhoto": 1
    },
    // more employee records
  ]
}

The response data consists of two arrays:

fields
and
employees
. The former lists the fields that the response data contains, along with their types. Fields are important as you can use them in API calls to exactly define what kind of data you want to be returned. However, in this simple scenario, you don't need them: what you're after is the
employees
array.

Printing and Saving Employee Data

You'll save the entire

employees
array to a file later on. For now, let's display basic information about each employee in the console.

Start by replacing the stub of the

print_all_employees()
function with the following:

def print_all_employees(employees_json):
    for entry in employees_json:
        print(
            f'{entry["displayName"]}: {entry["jobTitle"]} at {entry["department"]}, based in {entry["location"]} ({entry["division"]})')

This function now goes over all employee entries and prints a quick summary that includes each employee's full name, job title, department, location, and division.

Now, at the end of the file, following the

all_employees
declaration, add this piece of code:

if all_employees is not None:
    all_employees_json = json.loads(all_employees)['employees']
    print_all_employees(all_employees_json)

Initially, you're checking whether

all_employees
is null, which is possible if the prior API call has returned an error. If it's not null, you parse employee data as JSON and save the
employees
array from the resulting JSON object, thus stripping away the
fields
array. You then pass the JSON object to the
print_all_employees()
function.

If you call your program now by running

python main.py
in the terminal, you'll see something like this:

Charlotte Abbott: Sr. HR Administrator at Human Resources, based in Lindon, Utah (North America)
Ashley Adams: HR Administrator at Human Resources, based in London, UK (Europe)
Christina Agluinda: HR Administrator at Human Resources, based in Sydney, Australia (Asia-Pacific)
Shannon Anderson: HR Administrator at Human Resources, based in Vancouver, Canada (North America)
Maja Andev: VP of Product at Product, based in Lindon, Utah (North America)
Eric Asture: VP of IT at IT, based in Lindon, Utah (North America)
Cheryl Barnet: VP of Customer Success at Customer Success, based in Lindon, Utah (North America)
Jake Bryan: VP Learning and Development at Operations, based in Lindon, Utah (North America)

Finally, let's save the full set of employee data to a JSON file. At the end of

main.py
, following the call to
print_all_employees
, add a call to
save_all_employees_to_file
:

save_all_employees_to_file(all_employees_json)

Then replace the stub of the

save_all_employees_to_file
function with the following:

def save_all_employees_to_file(employees_json):
    destination_dir = 'data'
    Path(destination_dir).mkdir(parents=True, exist_ok=True)
    json_file = f'{destination_dir}/all_employees.json'
    with open(json_file, 'w', encoding='utf-8') as target_file:
        json.dump(employees_json, target_file, ensure_ascii=False, indent=4)
    print(f"Data saved to {json_file}.")

This function now creates the

data
directory if it doesn't exist, writes employee data to a JSON file in this directory, and reports a status update to the console.

If you want to see what your

main.py
file should look like by the end of this exercise, check out this "Extracting Employee Data with the BambooHR API" example.

If you're developing a product that relies on integrations with many different HRIS systems, you need to create and maintain multiple integrations that are going to be more sophisticated than the one shown earlier. The product would probably include authentication and authorization that are more advanced than those that use a single API key, handle error codes and retries, have a database layer instead of employee data dumped into JSON files, and more. After working through a few, chances are you'll realize that's probably not what you want to spend your development resources on.

Instead, consider using a unified HRIS API like the one that Apideck provides. The unified HRIS API is a single API that enables pulling and pushing employee data from and to more than fifty HRIS platforms in real time. You can save valuable development time and resources instead of spending them on developing and maintaining all the integrations in-house.

Summary

After reading this article, you now know how to write a proof-of-concept Python application that gets basic employee information from a BambooHR instance using the BambooHR API. See the "Extracting Employee Data with the BambooHR API" example for the source code of

main.py
that results from performing the instructions given earlier.

In a real-life integration, you need to worry about many other things, such as managing authentication and authorization, handling client- and server-side error codes, implementing retry policies, and monitoring API versions to prevent service interruptions.

Doing this for one HRIS service is fine, but if you're integrating with dozens of them, your development team can get overwhelmed quickly. If you don't want this to happen, start simplifying your HRIS integrations today by signing up to Apideck.

Ready to get started?

Start scaling your integration strategy in less than 5 minutes

Sign up