
BambooHR Employee Data: The Complete API Surface Map

Summarise the blog with AI
Key takeaways
- BambooHR employee data lives across five distinct surfaces: employee records, field metadata, tables, datasets, and benefits endpoints, plus custom fields layered on top.
- What a call returns is bounded by the key holder's access: a non-admin key silently returns fewer employees and fields. List Employees and datasets flag withheld values in
_restrictedFields; Get Employee just omits them. - Get Employee returns only the record ID unless fields are named, and both Get Employee and custom report requests share a 400-field cap.
- Job, compensation, and status changes append a row instead of overwriting a value, so history lives in the employee tables.
- Webhooks cover employee and custom fields only, which means custom tables, benefits, dependents, and time off have to be polled.
- Benefits data now has its own endpoints,
company_benefit,employee_benefit, andmember-benefits, separate from the employee record and its tables.
BambooHR's API documentation lists a long, granular set of employee endpoints, and none of them hands your integration a fixed schema. Every call runs "as if a real user were using the software," so the permissions behind the key or token decide the response. Two customers calling the identical endpoint can get different employees and different fields back, often with nothing in the response to flag it.
Employee data itself spans five surfaces, records, field metadata, tables, datasets, and benefits, with custom fields layered on top. This page maps all five, the permission rule that bounds every one of them, and where a unified model has to merge them back together, based on BambooHR's API documentation as of September 2026. Authentication, base URLs, and rate limits are covered separately in the BambooHR API integration guide.
Here's where each kind of employee data lives, and where this page covers it:
Your API key decides what comes back, not your integration
The mechanism is permission inheritance. Whoever created the API key, or authorized the OAuth token, hands their own field and employee permissions to every call made with it. A non-admin key returns fewer employees and fewer fields than an admin key against the identical account and endpoint, with no error and no warning.
How a withheld field shows up depends on the endpoint. List Employees returns it as null and names it in the record's _restrictedFields array, and dataset queries return it empty with the same array, which is how you tell "this field is genuinely empty" from "this key can't see it." Get Employee gives you no such marker: it silently omits any field the caller can't view, and returns a 403 for an employee the caller can't view at all.
Access is granular by design: OAuth scopes such as benefit and employee:custom_fields are separate grants, an API key inherits its creating user's access wholesale, and email addresses from /v1/meta/users/ have required admin access since March 2025.
Restriction shows up in a few recognizable ways:
- A List Employees response with fewer employees than the account has, especially when you filter or sort on a field the key can't read, which drops those employees from the result entirely
- A field value of
nullwhose name appears in that record's_restrictedFieldsarray - A Get Employee response that is simply missing a field you asked for
- No email addresses from
/v1/meta/users/, if the calling user isn't an admin
This is BambooHR enforcing property-level authorization, the control OWASP's API3:2023, Broken Object Property Level Authorization asks every API to apply (the 2023 edition folded the older Excessive Data Exposure and Mass Assignment categories into it). The practical takeaway: before you debug a "missing data" ticket, check what the calling key can see.
Fetching employee records: List Employees vs Get Employee
List Employees and Get Employee answer different questions: the roster, or one record in full.
List Employees returns the roster, cursor-paginated, with a default set of identity and job fields unless you add more with fields. Older third-party guides claim employee lists can't be paginated. That was true of the legacy directory endpoint, which returns every employee in one response. List Employees pages, up to 2,500 records per request.
Get Employee is the opposite shape: one record, and by default just the id. Nothing else comes back until you name fields, and that request shares a 400-field cap with BambooHR's custom report requests.
Get Employee returns JSON by default, or XML with Accept: application/xml, against the documented base {companyDomain}.bamboohr.com/api/. Knowing which record you need still depends on knowing what to name in fields, which is where field naming becomes the next problem.
Where field names come from, and why they don't match everywhere
The field names that work in fields on Get Employee aren't the same names that work in a dataset query. Some overlap, like firstName, but send a dataset a name it doesn't recognize and the request fails with a 400 INVALID_ARGUMENT.
The source of truth for an account is GET /api/v1/meta/fields (List Fields), which returns each field's ID, display name, data type, and API alias where one exists, flags deprecated fields, and includes the account's custom fields alongside the standard ones. For datasets, use Get Fields from Dataset instead, because the names differ even for the same data:
One trap sits in both vocabularies: employeeNumber is the editable Employee # value, not the internal ID, so never pass it where an employee ID is expected.
Custom fields have had a full CRUD API since August 2026, reachable at /api/v1/hris/custom-fields, and each one carries both a string ID and a numeric legacyId for backward compatibility.
BambooHR also has an acaStatusCategory field. Its API changelog describes it as read-only, with values such as full-time, part-time-eligible, seasonal, and terminated, calculated from how the account maps its employment statuses. That is a status mapping, and it doesn't run the eligibility test itself.
That test comes from the IRS: for the employer shared responsibility provisions, a full-time employee averages at least 30 hours of service per week, or 130 hours per month, measured under either the monthly or the look-back method. Treat the BambooHR field as an input to your own eligibility logic. Before wiring any field, especially ssn or dateOfBirth, into a customer-facing data model, decide which fields each customer needs; that's covered in scoping employee data by field.
Where history lives: employee tables
A Get Employee call returns an employee's currently effective values (plus future-dated ones with onlyCurrent=false). If your product needs to know that a pay rate changed in March or that an employee moved departments in June, the employee record won't tell you. BambooHR appends history to tables.
Get Employee Table Data (GET /api/v1/employees/{id}/tables/{table}) takes the internal employee ID plus the table's API name, or all in place of the ID to pull every accessible employee's rows for that table at once. Fields the caller can't access are left out of each row. Updating job, compensation, or employment status adds a new row, so the table itself is the history. Terminations are written into the employmentStatus table, and terminationDate is read-only: it reflects what was recorded there, and your integration can't set it directly.
Custom tables (custom1, custom2, and so on) are read the same way. Knowing where history lives still doesn't tell you when it changed. BambooHR's webhooks cover employee fields and custom fields only. GET /api/v1/employees/changed returns employees whose record or job, compensation, or employment-status tables changed since a timestamp, and /employees/changed/tables/{table} returns those employees' rows. Custom tables, benefits, dependents, and time off sit outside both signals: time-off requests take their own updatedSince filter, and the rest need a scheduled read.
When to query a dataset instead
List Employees and Get Employee are built around one employee, or one page of employees, at a time. When the question is "give me many employees across many fields in a single call," use Get Data from Dataset (v2). The datasets an account can query come from List Datasets; employee is the one most integrations use, with a catalog of over 450 fields.
- The field names available per dataset come from a separate call, Get Fields from Dataset, and don't match the employee-endpoint names covered above
- Page size defaults to 100 and can go up to 1,000 per request, a different cap from List Employees' 2,500
- Every field in
orderBymust also be in the requested fields, or the request fails with a 400 (UE-1001) - One filter expression can use
andoror, never both; mixing them returns a 422INVALID_FILTER - Dataset access needs elevated permissions, so a key without them gets a 403 whatever fields it asks for
That last point cuts both ways. BambooHR's own reference recommends datasets over List Employees for counts and breakdowns, because a restricted caller gets an error from a dataset where List Employees would hand back a quietly partial answer. That makes datasets the right surface for reporting and bulk sync jobs.
What the benefits endpoints expose
Benefits data doesn't live inside the employee record or its tables. It has its own set of endpoints, and BambooHR's reference doesn't connect them to the rest of the employee data model.
Under OAuth, all three need the benefit scope. Two behaviors catch integrations. employee_benefit is a GET that requires a JSON body with a filters object (employee, plan, or effective date), which some HTTP clients and proxies strip. And future-dated enrollments are silently left out when the caller can't view scheduled benefit changes. The dependents themselves come from GET /api/v1/employeedependents, which needs Benefits Administration permissions and returns SSNs masked.
Pulling enrollment and dependent data naturally raises a compliance question: does this fall under HIPAA? The answer isn't a blanket yes or no. HHS is explicit that the Privacy Rule does not protect employment records, even health-related ones, and in most cases does not apply to an employer's own actions; it governs how health plans and covered providers share protected health information with an employer, not what the employer does with HRIS-held records afterward.
Whether a given piece of benefits data is subject to HIPAA depends on who holds it and in what capacity, not on which BambooHR endpoint it came from.
Mapping five surfaces into one model
A BambooHR integration has to reconcile five surfaces, each with its own field names, paging, and permission behavior, before it can present "employee data" as one coherent object to your product. A unified API is the layer built to do that: it normalizes each provider's version of employee, job, compensation, and benefits data into a shared model, so your product code writes against one schema instead of five.
What that normalization has to merge, concretely: identity and job fields from the employee records, appended history rows from the tables, enrollments and dependents split across the benefits and dependents endpoints, and whatever account-specific fields List Fields turned up. Bindbee handles the last piece through Custom Fields, JMESPath mappings onto its unified models, and a passthrough mode for making raw requests directly against BambooHR's API when a field hasn't been modeled yet.
On the benefits side, Bindbee's docs list four benefits models for BambooHR: Employer Benefit (the plan, with its deduction code), Benefit (one employee's enrollment, with coverage tier and contributions), Dependent Benefit (which plan covers a dependent), and Benefit Coverage (insured amounts per covered person), alongside Dependent as its own model.
Because that data includes health-plan enrollment and dependent records, it matters that Bindbee is HIPAA compliant and publishes a standard HIPAA business associate addendum. Access works the way this page has described throughout: the customer connects BambooHR with their subdomain plus an API key or OAuth, so the permission rules from the first section still govern what any integration, including a unified one, can see.
On change detection, Bindbee syncs BambooHR on a 24-hour default schedule, adjustable per connection, and sends webhook notifications when a sync finds created or updated records and when a sync starts, finishes, or fails. That reaches the benefits, dependent, and time-off models BambooHR's own webhooks skip, on the sync's schedule. For the full list of what the connector reads and writes, see Bindbee's BambooHR integration.
Frequently asked questions
Why does the BambooHR API return fewer employees or fields than the account has?
Every call inherits the permissions of the user behind the API key or OAuth token. A non-admin credential returns fewer employees and fields with no error. List Employees flags withheld values in _restrictedFields; Get Employee just omits them.
How do I get all employees from BambooHR?
Use List Employees (GET /api/v1/employees). It's cursor-paginated, up to 2,500 records per request, and you follow _links.next until it's absent. The legacy directory endpoint returns everyone at once and doesn't page.
Why does Get Employee only return the employee ID?
Get Employee has no default field set. Name the fields you want in fields, and keep each request under the 400-field cap it shares with custom reports.
Where is an employee's compensation or job history in BambooHR?
In the employee tables. Job, compensation, and employment-status changes append a new row, so GET /api/v1/employees/{id}/tables/{table} returns the history, and the most recent row is the current value.
Can I get benefits data from the BambooHR API?
Yes, through dedicated endpoints rather than the employee record: company_benefit, employee_benefit, and member-benefits, all under the benefit OAuth scope. Dependents come from GET /api/v1/employeedependents.






