How to fetch employees from Gusto using Python

Gusto, a robust, cloud-based Human Resource Information System (HRIS), is engineered to streamline and automate HR operations. It provides a comprehensive toolkit for payroll, benefits, and HR management, all of which are intricately interconnected to decrease administrative tasks. Moreover, Gusto's API permits custom integration, facilitating businesses to access employee data and optimize their workflow.

To help you get employee data in Gusto via Python, we'll break down every step you need to follow in this article.

Obtaining your access token

To initiate this process, users should be directed to the Gusto authorization URL: <code class='blog_inline-code'>https://api.gusto.com/oauth/authorize</code>. Once the authorization flow is completed, Gusto will redirect the user to the <code class='blog_inline-code'>redirect_url</code> that was initially provided. Included in this redirect will be the authorization code as a query parameter.

The next step in the process is to exchange the authorization code for an access token. This is done by making a POST request to the Gusto token URL: <code class='blog_inline-code'>https://api.gusto.com/oauth/token</code>.

This request should include the client ID and client secret that were provided by Gusto, along with the authorization code and other necessary parameters. These should be passed as body parameters in the POST request.

For instance, a Python request might look something like this:

import requests

url = "https://api.gusto.com/oauth/token"
payload = {
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code": "AUTHORIZATION_CODE",
    "grant_type": "authorization_code",
    "redirect_uri": "YOUR_REDIRECT_URL"
}

response = requests.post(url, data=payload)

Upon successful completion of the request, the response will include an <code class="blog_inline-code">access_token</code>, an <code class="blog_inline-code">expires_in</code> value indicating the lifespan of the token, and a <code class="blog_inline-code">refresh_token</code>. The <code class="blog_inline-code">access_token</code> is used in subsequent API calls to pull employee data, while the <code class="blog_inline-code">refresh_token</code> can be used to obtain a new <code class="blog_inline-code">access_token</code> once the original has expired.

Steps for fetching employee data from the Gusto API

First, let's install the necessary package:

pip install requests

Next, we will go through the OAuth 2 flow to get the access token.

import requests

# Replace these with your client id and secret
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'

def get_access_token():
    token_url = 'https://api.gusto.com/oauth/token'
    auth = (CLIENT_ID, CLIENT_SECRET)
    response = requests.post(token_url, auth=auth)
    response.raise_for_status()
    return response.json()['access_token']

access_token = get_access_token()

Then, we'll request the current user's data:

def get_me(access_token):
    url = 'https://api.gusto.com/v1/me'
    headers = {'Authorization': f'Bearer {access_token}'}
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    return response.json()

me = get_me(access_token)

Next, we'll loop over the companies that the current user is a payroll admin for, and fetch the employees for each company.

Note: This code fetches all employees for each company the current user is a payroll admin for. It uses the <code class="blog_inline-code">include</code> parameter to include all compensations and custom fields for each employee. It also handles pagination by incrementing the page number for each request until an empty page is returned.

def get_employees(access_token, company_id):
    url = f'https://api.gusto.com/v1/companies/{company_id}/employees'
    headers = {'Authorization': f'Bearer {access_token}'}
    params = {'include': 'all_compensations,custom_fields'}
    page = 0
    while True:
        params['page'] = page
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()
        data = response.json()
        if not data:
            break
        yield from data
        page += 1

for company in me['roles']['payroll_admin']['companies']:
    for employee in get_employees(access_token, company['uuid']):
        print(employee)

An example of an individual item returned by this API Endpoint is:

{
    "uuid": "1a2b3c4d",
    "first_name": "John",
    "middle_initial": "A",
    "last_name": "Doe",
    "email": "john.doe@example.com",
    "company_uuid": "5e6f7g8h",
    "manager_uuid": "9i0j1k2l",
    "version": "1.0",
    "current_employment_status": "employed",
    "onboarding_status": "completed",
    "preferred_first_name": "Johnny",
    "department_uuid": "3m4n5o6p",
    "payment_method": "direct_deposit",
    "department": "Sales",
    "terminated": false,
    "two_percent_shareholder": false,
    "onboarded": true,
    "has_ssn": true,
    "jobs": [
        {
            "uuid": "7q8r9s0t",
            "version": "1.0",
            "employee_uuid": "1a2b3c4d",
            "location_uuid": "u2v3w4x5",
            "current_compensation_uuid": "y6z7a8b9",
            "payment_unit": "hourly",
            "primary": true,
            "two_percent_shareholder": false,
            "title": "Sales Manager",
            "compensations": [
                {
                    "uuid": "c0d1e2f3",
                    "employee_uuid": "1a2b3c4d",
                    "version": "1.0",
                    "payment_unit": "hourly",
                    "flsa_status": "exempt",
                    "adjust_for_minimum_wage": false,
                    "minimum_wages": [],
                    "job_uuid": "7q8r9s0t",
                    "effective_date": "2020-01-01",
                    "rate": "20.00"
                }
            ],
            "rate": "20.00",
            "hire_date": "2019-01-01",
            "location": {
                "zip": "10001",
                "city": "New York",
                "uuid": "u2v3w4x5",
                "state": "NY",
                "country": "USA",
                "inactive": false,
                "street_1": "123 Main St",
                "street_2": "Apt 1"
            }
        }
    ],
    "eligible_paid_time_off": [
        {
            "name": "Vacation",
            "policy_name": "Standard Vacation Policy",
            "policy_uuid": "g4h5i6j7",
            "accrual_unit": "hours",
            "accrual_rate": "1.5",
            "accrual_method": "per_pay_period",
            "accrual_period": "biweekly",
            "accrual_balance": "80",
            "maximum_accrual_balance": "120",
            "paid_at_termination": true
        }
    ],
    "terminations": [],
    "home_address": {
        "zip": "10001",
        "city": "New York",
        "state": "NY",
        "active": true,
        "country": "USA",
        "version": "1.0",
        "street_1": "123 Main St",
        "street_2": "Apt 1",
        "employee_uuid": "1a2b3c4d"
    },
    "garnishments": [],
    "custom_fields": [],
    "date_of_birth": "1980-01-01",
    "ssn": "123-45-6789",
    "phone": "(123) 456-7890",
    "work_email": "john.doe@workexample.com"
}

Final thoughts

It’s worth remembering that your clients might be using other HRIS solutions, whether that’s Workday, Namely, UKG, etc.

You can offer customer-facing integrations with all of the HRIS solutions your clients use by connecting to Merge's HRIS Unified API.

Learn more about Merge’s Ticketing Unified API, along with the other features and capabilities provided by the platform, by scheduling a demo with one of our integration experts.