Extracting Employee Data with the BambooHR API: Practical Examples

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.

Jura Gorohovsky

Jura Gorohovsky

14 min read
hero

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.

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?

Scale your integration strategy and deliver the integrations your customers need in record time.

Ready to get started?
Trusted by
Nmbrs
Benefex
Principal Group
Invoice2go by BILL
Trengo
MessageMedia
Lever
Ponto | Isabel Group