
BambooHR API Guide (2026): Auth, Rate Limits and Data Access
.jpg)
Summarise the blog with AI
Key takeaways
- BambooHR's documented base URL is now
https://{companyDomain}.bamboohr.com/api/; the legacy gateway URL still works but hasn't been the documented format since July 2025. - OAuth 2.0 has been the primary auth route since March 2025, with access tokens expiring after 3600 seconds and a refresh token issued only when you request
offline_access. - Every request runs as if the authorizing user were logged in, so a non-admin credential silently returns fewer fields and employees instead of an error.
- Since 16 September 2026, throttled requests return 429 with a
Retry-Afterheader instead of 503, and BambooHR's own Technical Overview page hasn't caught up with that change yet. - Webhooks cover employee fields and custom fields in real time, but benefits, dependents, time off and custom tables all have to be polled.
- BambooHR publishes no numeric rate limit, so any integration built around an assumed requests-per-minute figure is working from a number BambooHR never stated.
BambooHR's API looks the same as it did a few years ago: REST over HTTPS, a company subdomain, JSON if you ask for it. Underneath, most of what a working integration depends on has moved.
The base URL changed, OAuth became the default credential, dedicated benefits endpoints went public, and the status code for throttling flipped, all inside about eighteen months. An integration built against a two-year-old blog post may rest on a login flow that's now closed to new applications, point at a URL that still resolves but is no longer the documented one, and treat a 503 as a throttle signal when it now means something else.
BambooHR exposes a REST API over HTTPS, addressed per customer company rather than through one shared endpoint, and it returns JSON or XML, with XML the default unless a request asks for JSON explicitly. Every payload is UTF-8. There are two ways to authenticate: OAuth 2.0 and an API key, and which one fits your product is the first real decision this guide covers.
This page walks through where to send requests, how to authenticate, what your access covers, what data exists and whether it can be written, how to read all of it at scale, how fast you're allowed to read it, how change reaches you, and where the authoritative record of all of this lives. It's written for an engineer building or maintaining an integration that many customers will authorize.
Last verified: 2026-09-30.
Where to send requests: the current base URL
Every BambooHR request is scoped to one customer, and the URL says which one. The current, documented base is:
companyDomain is the subdomain each BambooHR customer picks when they set up their account, the same string a human sees in their login URL. An older format still resolves:
That gateway form works, and code written against it won't break on its own. It simply hasn't been the documented format since July 2025, so new code, and any code you're auditing, should target the subdomain form instead. Nothing in BambooHR's own material calls the older path deprecated or scheduled for removal; it's just no longer what the docs show.
A first authenticated call against the current format, requesting JSON explicitly and sending credentials as HTTP Basic auth:
Leave out fields and Get Employee returns only id: there is no default field set. A 401 means the credential is missing or wrong, which is where authentication comes in.
OAuth 2.0 or an API key: how BambooHR authentication works
What decides how you authenticate is really a question about your product: how many BambooHR accounts does one integration need to reach?
For OAuth, BambooHR runs a self-serve developer portal where registration is free, and you can spin up a test BambooHR account to build against before touching a real customer's data. The authorization step happens at {companyDomain}.bamboohr.com/authorize.php, and the code you get back is exchanged for tokens at token.php. The redirect URI you send has to match the one registered for your app exactly; BambooHR notes that even an extra slash or a capitalization change causes an authentication error.
BambooHR's Getting Started page shows the exchange as a POST to token.php?request=token with a JSON body:
The response carries access_token, expires_in (3600), token_type (Bearer) and, if you asked for it, refresh_token. Refreshing later is the same endpoint with a different grant:
That refresh token only exists if the original authorization request included the offline_access scope. Skip it, and your integration re-authorizes every 3600 seconds instead of refreshing quietly.
Scopes are granular rather than all-or-nothing: benefit, employee:custom_fields, and time_tracking:employees.write are separate grants, and you request only what your product reads or writes. If you're building the OAuth flow for the first time, the step-by-step authentication walkthrough covers the sequence in more detail than a reference page needs to.
One route that's worth ruling out explicitly: the older oidcLogin / User API Key login flow, along with POST /api/v1/login, has been closed to new applications since April 2025. If a guide tells you to log in that way, it's describing a path new integrations can't take. BambooHR's own developer documentation for the current flow is here: Getting Started With The API.
What your credentials can actually see
A valid token doesn't guarantee a complete response. BambooHR treats every API call the same way it treats a browser session: as if a real person were logged in and clicking through the software, and that person's permissions decide what comes back.
This is easy to miss because it fails quietly. Query employees with a non-admin credential and you don't get an error. You get a response, with fewer fields and sometimes fewer employees than the account has, because the authorizing user simply can't see the rest. On Get Employee, the API doesn't distinguish "this field doesn't exist" from "this field exists but you're not allowed to see it." Both come back the same way: absent.
List Employees is more explicit. A field the credential can't read comes back as null, and its name is listed in a per-record _restrictedFields array, so check that array before you treat a null as empty data. One more trap there: if the credential can't read a field you filter or sort on, matching employees drop out of the result entirely, and meta.total can read 0 even though they exist.
One field is worth naming specifically. Email addresses returned from /v1/meta/users/ have been admin-only since March 2025, so a request authorized by a non-admin will not include them, regardless of what other fields it returns successfully.
A few symptoms and their usual cause:
- Employee count is lower than expected: the authorizing user's employee-level access is scoped to a subset, often a specific department or reporting line.
- A field that exists in the BambooHR UI never shows up in the API response: the field is hidden from that user's access level, not missing from the account.
- Email addresses are consistently null: the credential belongs to a non-admin user.
The fix, in every case, is the same: authorize with a BambooHR user whose access level matches what your integration needs to read, and confirm that before you debug the code.
What data you can read and write
Ask "what does BambooHR expose" and the honest answer is domain by domain, because read and write behave differently depending on where you're looking, and one domain doesn't overwrite the way you'd expect.
Update an employee's job, compensation, or employment-status record, and BambooHR doesn't replace the existing row. It appends a new one. These fields live in tables specifically so a change in March and a change in September both stay on record, which means a write you intended as a correction is really an addition, and reading "current" compensation means reading the most recent row, not the only row.
The benefits endpoints are the newest domain here, and they're read-only: you can see which plans a company offers, who's enrolled, at what coverage level, and each side's contribution amount, but BambooHR states that enrollment and benefit status changes aren't supported through the API. Dependent records are the one writable piece of the benefits area: BambooHR documents endpoints to create and update them. If your integration needs a worked example of pulling employee records at scale, this walkthrough covers the request and response shape in full.
Paging through large result sets
/api/v1/employees is cursor-paginated: the response carries a data array, meta.total for the full matching count, and meta.page holding the cursor state, plus a _links object with next and prev. Page size is set with page[limit] (default 250, maximum 2500), and _links.next is a pre-built URL that already carries the cursor, so follow it until it's absent.
The older employee directory endpoint doesn't page at all. It returns everyone in one response, which is fine for a small company and a problem the moment an account has a few thousand employees.
Two caps apply regardless of which endpoint you're on. Requests to Get Employee and to custom reports are limited to 400 fields per request, so a wide field selection has to be split across calls. Dataset queries (Get Data from Dataset v2) default to a page size of 100 and cap at 1000, orderBy can't reference a field that isn't also in your fields list, and combining certain filter operators returns a 422 rather than a partial result.
Cursor pagination as a pattern isn't specific to BambooHR; if the mechanics are new to you, this general explanation of keyset pagination covers why cursors survive concurrent writes better than offsets do.
Rate limits and throttling: the 429 change
BambooHR does not publish a numeric rate limit. No requests-per-minute or requests-per-second figure appears anywhere in their documentation, which means any integration built around a specific assumed number picked that number up somewhere other than the source.
What changed is how throttling announces itself. Since 16 September 2026, a throttled request returns HTTP 429 with a Retry-After header telling you how long to wait. Before that date, throttling returned 503. Now 503 means only that the API is temporarily unavailable. BambooHR says the rate limits themselves didn't change; only the status code did.
BambooHR's own Technical Overview page hasn't been updated to reflect this. It still tells integrators to expect 503 when throttled and describes 429 as an account hitting its employee limit, which directly contradicts the newer Planned Changes to the API page. Treat the Planned Changes page as current and the Technical Overview as stale on this specific point; the date of the 429 change is the detail that resolves the conflict, whichever page you're reading.
Retry-After arrives in one of two forms: an integer number of seconds, or an HTTP date. Handle both:
Reading the header is what makes your retry logic correct, since there's no fixed number to back off against even if you wanted to hard-code one. BambooHR announced the change on its Planned Changes to the API page; changes that have already shipped are logged on Historical Changes to the API.
Webhooks: what's real-time and what needs polling
Paging through every employee on a schedule is expensive against a service with no published rate ceiling, so the natural next question is whether BambooHR can push changes to you instead. For part of the data model, it can.
There are two kinds of webhook. Global webhooks are configured by an admin inside Account Settings and deliver a fixed, standard set of fields. Permissioned webhooks are created through the API instead, and they're limited to whatever the creating credential can see, the same permission-inheritance rule that governs every other request.
Delivery mechanics worth building against from day one:
- Since August 2025, delivery is real time and event based, firing on create, update and delete rather than on a batch schedule.
- Payloads are signed with HMAC-SHA256, carried in
X-BambooHR-SignaturealongsideX-BambooHR-Timestamp. The signature is a hex digest computed over the raw request body followed by the timestamp value, and theprivateKeyneeded to verify it is shown once, at creation. - Delivery is HTTPS only and may batch multiple employees into a single payload.
- A delivery that hits a network error or a 5xx response retries up to five times: immediately, then after 5, 10, 20 and 40 minutes, each with up to 30 seconds of random jitter.
Benefits, dependents, time off and custom tables sit outside webhook coverage entirely, which is the gap that catches most integrations that assume "webhooks" means "everything." If your product needs to know when an employee's coverage changes, that has to come from a poll against the benefit endpoints, not a push. For the signature-verification mechanics in more depth, see how HMAC verification works. BambooHR's own reference is at Webhooks.
Versioning and where the documentation lives
Every section above has described something that changed within the last eighteen months, which raises the practical question: how do you find out about the next one?
BambooHR versions per endpoint rather than versioning the API as a whole. Paths carry v1, v1_1, v1_2 and v2 side by side depending on which endpoint you're calling, Datasets sits at v1.2 while Get Data from Dataset has moved to v2, and there's no single API version number to track. Deprecated endpoints stay live with no fixed sunset date, unless BambooHR issues a separate notice saying otherwise, so "deprecated" on this API means "not the current recommendation," not "scheduled for removal."
The authoritative record is at documentation.bamboohr.com, and it's worth knowing the site publishes an llms.txt file and a .md version of every page, which is useful if you're feeding docs to a tool rather than reading them yourself. Two pages matter most for tracking change: Historical Changes, which logs what already shipped, and Planned Changes, which lists what's coming. Bindbee counted 61 dated entries on the Historical Changes log between January and August 2026 (as of 30 September 2026). That is the release cadence to plan around.
If you'd rather not track HTTP details by hand, BambooHR maintains one official, MIT-licensed SDK, for PHP, with OAuth 2.0 support; it handles authentication, error handling and retries for you. The older .NET and Java SDKs are listed as unmaintained. Details are at Official SDKs.
Common mistakes once you're writing data
Reading is forgiving. Writing, and running in production, is where the API's specific rules start to bite.
- Sending only
paidPeror onlyrateon a compensation write fails with a 406. BambooHR wantspaidPerandratetogether in the same request, not one implying the other. - Writing
terminationDatedirectly doesn't work: it's read-only. Terminations go through theemploymentStatustable instead, the same append-only structure that handles every other job change. - Waiting for a 401 challenge before sending credentials, rather than sending them on every request, doubles your call count and eats into whatever rate-limit budget you have, since each attempt costs two requests instead of one.
- Retrying against an API key that's already failing doesn't help: repeated requests with an unknown key get API access disabled for a period, and every request during that window returns 403. Replace the key; don't retry it.
- Branching application logic on the
X-BambooHR-Error-Messageheader treats a debugging aid as a contract. Most 4xx and some 5xx responses carry it, and BambooHR says its text may change at any time. Log it, and branch on the status code.
Should you build this yourself or connect through a unified API?
Everything above describes an API that has changed substantially in eighteen months and, on the evidence of one page contradicting another, hasn't finished settling. Maintaining a direct integration means owning that churn indefinitely: rechecking base URLs, auth flows, and status-code semantics against a changelog that logged 61 entries in the first eight months of 2026.
Bindbee's BambooHR connector is an API connector: Bindbee calls BambooHR's own API with the credentials your customer authorizes, the same mechanics this page just walked through, with no screen scraping. A unified layer sits on top of BambooHR's auth model and doesn't change what BambooHR requires. That matters if you're weighing whether a middle layer weakens the security posture you'd otherwise control directly. Once employee and benefits data is routed through a third party, the relevant question is that party's posture: Bindbee is SOC 2 Type II and HIPAA compliant, with a BAA available.
The tradeoff runs the other way too. A direct BambooHR integration gets real-time webhooks for employee-field changes, where Bindbee's default is a 24-hour sync, adjustable but not instant.
If your product only ever needs to talk to BambooHR, and one BambooHR account at that, a single-company internal script doesn't need a unified API at all; the case for one starts once you're maintaining this same page's worth of detail across several HRIS platforms instead of just this one. This page also isn't a substitute for BambooHR's own endpoint-level documentation, which remains the authority for parameters and payload shapes once you know which endpoint you're calling.
If you're building against several HRIS and benefits systems and BambooHR is one of them, and you need fields a unified model doesn't cover natively, custom fields and passthrough explains the mechanism this table only names. Either way, the API itself will keep changing under whoever maintains the connection. The point running through this whole page is the same: the only integration that stays correct is the one that keeps checking its assumptions against a date, not against a guide someone wrote before the last change shipped.
Bindbee builds enterprise-grade unified APIs for HRIS, ben admin, and payroll integrations. If BambooHR is one of several systems on your list, book a demo to see the connection flow.
Frequently asked questions
Does BambooHR have a public API?
Yes. BambooHR exposes a REST API over HTTPS, addressed per customer company at https://{companyDomain}.bamboohr.com/api/. It returns XML by default and JSON when the request sends Accept: application/json, and it authenticates with either OAuth 2.0 or an API key.
Is there a BambooHR sandbox for testing?
BambooHR's self-serve developer portal is free to register with, and it lets you spin up a test BambooHR account to build against before you touch a real customer's data.
Should I use OAuth or an API key for BambooHR?
Use OAuth 2.0 when one integration has to reach many customers' BambooHR accounts; it has been the primary route since March 2025. An API key fits a single-company integration. Either way, every call runs with the permissions of the user who authorized it.
Can you write data back to BambooHR through the API?
Yes, with rules. Job, compensation, and employment-status updates append a new table row rather than overwriting, terminationDate is read-only (terminations go through the employmentStatus table), and compensation writes need paidPer and rate together or they fail with a 406. Benefits endpoints are read-only, apart from creating and updating dependents.
What are the BambooHR API rate limits?
BambooHR publishes no numeric rate limit. Since 16 September 2026, a throttled request returns 429 with a Retry-After header instead of 503, and 503 now means only that the API is temporarily unavailable.




.jpg)

