How to Export Data from BambooHR: API Limits, Methods & Portability
Learn how to export data from BambooHR using UI reports, the REST API, and webhooks. Covers rate limits (~100 req/min), custom report endpoints, file extraction, and delta sync.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
How to Export Data from BambooHR: API Limits, Methods & Portability
BambooHR gives you three ways to get data out: manual CSV exports from the UI, the REST API for programmatic extraction, and webhooks for real-time change notifications. Which one you pick depends on volume, frequency, and whether a human is doing the work or a script is. This guide covers every method, the real constraints you'll hit, and the edge cases common to HR data migrations.
Based on BambooHR API documentation and developer resources as of mid-2025.
What data can you actually export from BambooHR?
BambooHR stores employee records, job information, compensation history, time-off balances, training records, benefits enrollment, uploaded documents, and custom fields defined by your organization. All of these are exportable, but not all through the same channel.
BambooHR offers an open REST API that allows developers to programmatically access and manage employee data, time-off records, company reports, and HR events. The API provides endpoints that can retrieve and update employee records and files, company-wide data, benefits, job descriptions and applications, employee training types, and records.
The UI export handles structured report data well. But for uploaded files (offer letters, I-9s, signed documents), you'll need the API — the GET /employees/{id}/files/{fileId} endpoint downloads binary file content as an attachment, and you use GET /employees/{id}/files/view to discover file IDs first.
How to export BambooHR data via the UI
The fastest way to pull a one-time dataset is through BambooHR's built-in reporting. You can create either a Standard Report or a Custom Report. Custom Reports let you choose exactly what employee information to include. Select the fields you need, choose which employees to include, then navigate to the Export button and select your format.
Supported export formats from the UI: CSV, Excel (XLS), and PDF.
Three constraints to know before you start:
- 75-field ceiling: You can only display up to 75 data fields in one report. At larger headcounts, reporting can become slow and you'll receive an error notification if you attempt to add more than 75 fields to a single report.
- No automation: The native CSV export is a one-time pull. Every time you need fresh headcount or payroll data, you repeat the process manually.
- Standard vs. Custom reports: Standard reports (headcount, turnover, etc.) use fixed field sets. Custom reports are where the flexibility is — and where you should spend your time if you need non-default fields.
Build a dedicated "Full Export" custom report with every field you need. Save it. Then re-run it each time you need a fresh pull — BambooHR will remember the field configuration.
How to export BambooHR data via the REST API
The BambooHR API is a REST API that uses HTTP Basic Authentication and returns data in JSON, XML, CSV, XLS, or PDF format. The base URL follows the pattern https://api.bamboohr.com/api/gateway.php/{companyDomain}/v1/.
Authentication: API key vs. OAuth 2.0
BambooHR uses Basic Authentication. For the username, use your API key; for the password, use anything (commonly x). The API key inherits the permission set of the user who created it — this is critical to understand because an API key generated by a restricted user will silently omit fields that user cannot see.
For internal tooling where a single BambooHR admin owns the integration, API keys are sufficient. For B2B SaaS products connecting to customer BambooHR accounts, OAuth 2.0 is required. The OpenID Connect Login method was deprecated as of April 14, 2025 — applications created after that date must use OAuth 2.0 tokens instead of User API Keys.
To generate an API key: log in and click your name in the upper right-hand corner of any page. If you have sufficient administrator permissions, there will be an "API Keys" option in that menu.
API keys grant broad access to sensitive HR data — compensation, social security numbers, home addresses. Always send the Authorization header preemptively rather than waiting for a 401 challenge-response (which wastes rate limit budget and doubles latency). Encrypt keys at rest. Rotate them when employees with admin access leave.
Key API endpoints for data extraction
Key employee data endpoints include: GET /employees — list all employees; GET /employees/{id} — get a specific employee; GET /employees/changed — get recently modified employees (useful for incremental sync); POST /reports/custom — custom reports for bulk data export.
Here's a practical breakdown:
| Endpoint | Use case | Notes |
|---|---|---|
GET /employees/directory |
Quick directory pull | Can be restricted by user account settings, returning 403 Forbidden |
POST /reports/custom |
Bulk export with specific fields | Up to 400 fields per request; supports CSV, JSON, XML, XLS, PDF output; deprecated — use Datasets API for new builds |
GET /employees/{id}/tables/{table} |
Historical/tabular data (compensation, job history) | One employee at a time; returns all rows for that table |
GET /employees/changed |
Delta sync | Returns IDs of employees changed since a given ISO 8601 timestamp |
GET /employees/{id}/files/view |
List uploaded documents | Returns file IDs and categories |
GET /employees/{id}/files/{fileId} |
Download a specific document | Binary download; one file at a time |
GET /datasets/{datasetId} |
Bulk export (current recommended method) | Replacement for POST /reports/custom; see Datasets API section |
Why POST /reports/custom is the best legacy endpoint for bulk extraction
The employee directory endpoint can be restricted in user account settings, producing a 403 Forbidden response even with a valid API key. This makes the custom reports endpoint the safer choice for bulk extraction.
The custom reports endpoint lets you specify exactly which fields to include in a single API call and get back every matching employee. It generates an ad-hoc employee report based on a caller-specified list of fields and optional filters, returning data in JSON, XML, CSV, XLS, or PDF. Critically, the report includes both Active and Inactive employees by default, unlike the BambooHR UI which filters to Active employees only.
A minimal Python example:
import requests
company = "yourcompany"
api_key = "your-api-key"
response = requests.post(
f"https://api.bamboohr.com/api/gateway.php/{company}/v1/reports/custom?format=csv",
auth=(api_key, "x"),
headers={"Content-Type": "application/json"},
json={
"title": "Full Export",
"fields": [
"firstName", "lastName", "workEmail",
"department", "jobTitle", "hireDate",
"status", "employeeNumber"
]
}
)
with open("bamboo_export.csv", "wb") as f:
f.write(response.content)Three gotchas with this endpoint:
- 400-field maximum per request. If your org has more custom fields than that, you'll need multiple requests and a merge step.
- Strict Content-Type enforcement. The request body may be submitted as JSON or XML. To submit JSON, set
Content-Type: application/jsonexactly — any variation such asapplication/json; charset=UTF-8is not recognized as JSON and will cause a parse failure. - Custom fields require field IDs, not names. Use
GET /meta/fieldsto discover field IDs before building your request. Field IDs are account-specific and cannot be assumed consistent across BambooHR instances. - Silent field omission on permission or ID errors. Field IDs in the request that are unknown or that the caller does not have permission to view are silently omitted from the report — the endpoint still returns HTTP 200. You will not receive an error; you will just get missing columns. Always validate output column count against expected field count.
POST /reports/custom is now marked as deprecated. BambooHR recommends using the Datasets API (GET /datasets/{datasetId}) instead. The legacy endpoint still works for existing integrations, but new builds should target Datasets. See the Datasets section below for specifics.
The Datasets API (current recommended method)
BambooHR's Datasets API is the replacement for POST /reports/custom. The workflow differs from the legacy approach:
- Call
GET /datasetsto list available datasets for your account - Call
GET /datasets/{datasetId}to retrieve data from a specific dataset - Datasets return paginated results in JSON format
Key differences from the legacy custom reports endpoint:
- Paginated responses rather than a single bulk download — you must handle pagination in your client
- Predefined dataset schemas rather than caller-specified field lists — field selection is done at dataset configuration, not at query time
- JSON output only in the current implementation (as of mid-2025; check BambooHR changelog for format additions)
- No deprecation risk — this is the forward-supported path
For existing integrations built on POST /reports/custom, migration to Datasets is not urgent but should be planned before BambooHR enforces the deprecation.
What are BambooHR's API rate limits?
BambooHR does not publish exact rate limits, but enforces approximately 100 requests per minute per API key. The API returns 503 Service Unavailable with a Retry-After header when you exceed the limit.
This is the single most misunderstood aspect of the BambooHR API. Key facts:
- BambooHR uses 503, not 429, for rate limiting. Most retry libraries and middleware default to watching for HTTP 429 Too Many Requests. You must handle 503 explicitly in your retry logic, or you will silently drop throttled requests.
- The
Retry-Afterheader specifies the number of seconds to wait before retrying. - Failed authentication attempts count toward rate limits. Validate your API key before making bulk requests to avoid burning quota on auth failures.
- Repeated use of an invalid API key triggers a temporary ban. If an unknown API key is used repeatedly, the API will disable access for a period of time, returning HTTP 403 Forbidden for all subsequent requests — even from other keys associated with the same account. Users can still log in to the BambooHR website during this lockout period.
Practical throughput math:
At ~100 requests/minute:
- 500-employee org using individual
GET /employees/{id}calls: ~5 minutes for basic records only - Same org using
POST /reports/custom: 1 API call returning all employees with all requested fields - 200-employee org with 8 documents each: 1,600 file downloads + 200 listing calls = ~18 minutes at maximum throughput
The custom reports endpoint is essential for any org above ~50 employees. Using individual employee GETs at scale is not just slow — it is the primary cause of rate limit exhaustion in BambooHR integrations.
The single biggest performance improvement: use POST /reports/custom (or the Datasets API) instead of individual employee GETs. Instead of 501 API calls for 500 employees, you make 1 call that returns all employees with all needed fields.
How to run incremental (delta) exports from BambooHR
The GET /employees/changed endpoint returns only employees modified since a given timestamp, making it the foundation for incremental sync.
The Last Change Information API lets you discover which employees have been recently added, updated, or deleted. Any change to an individual field in an employee record counts as an update.
The request format:
GET /api/gateway.php/{company}/v1/employees/changed?since=2024-01-01T00:00:00Z&type=inserted
Valid type values: inserted, updated, deleted. The response returns a map of employee IDs with their last-changed timestamps. You then fetch full details for only those employees.
Important coarseness caveat for table data: For compensation history, job changes, and other tabular data, the Changed Employee Table Data endpoint is an optimization to avoid downloading all table data for all employees. However, it operates on an employee-level timestamp — meaning a change in ANY field in the employee record will cause ALL of that employee's table rows to appear in the response, not just the rows that actually changed. Expect to receive and deduplicate more rows than were actually modified.
The custom reports endpoint also supports a lastChanged filter via the filters object: pass an ISO 8601 date-time to filter employees by last-modified date, with an optional includeNull control for employees who have never been modified. You can also restrict results to specific internal employee IDs using the employeeIds filter.
Using webhooks for real-time data extraction
BambooHR webhooks send HTTP POST notifications to your endpoint when monitored employee fields change. Configure them in the BambooHR UI under Settings > Integrations > Webhooks.
Supported field types for webhook monitoring:
- Standard employee fields
- Custom fields (added in a recent update) — both global and permissioned webhooks include custom fields
- Not supported: Fields within custom tables, or the tables themselves
Security requirements:
- BambooHR will only post to HTTPS URLs — HTTP endpoints are rejected
- Webhooks are secured using SHA-256 HMAC
- Each request includes
X-BambooHR-TimestampandX-BambooHR-Signatureheaders for verification - BambooHR recommends configuring a private secret key in the webhook settings
Webhooks vs. polling decision rule: Use webhooks when you need latency under 1 minute for field changes in a downstream system (identity provider, CRM, Slack notification). Use polling with GET /employees/changed when you need guaranteed delivery semantics, replay capability, or you're working with a system that cannot expose a public HTTPS endpoint. Webhooks do not replace bulk export — you still need the API for initial data loads and document extraction.
Exporting employee documents and files
BambooHR stores uploaded documents (offer letters, tax forms, signed agreements) as binary files attached to employee records, downloadable only one file at a time via the API.
The GET /employees/{id}/files/{fileId} endpoint downloads binary file content. The response Content-Type header reflects the file's stored MIME type (e.g., application/pdf) and includes a Content-Disposition header with the original filename — use this for saving files with their original names.
Complete document export workflow:
- For each employee, call
GET /employees/{id}/files/viewto list files and categories - Parse the response for file IDs and metadata
- For each file ID, call
GET /employees/{id}/files/{fileId}to download binary content - Save using the filename from the
Content-Dispositionheader - Preserve the category structure from step 2 for downstream organization
Operational constraints:
- Archived or soft-deleted files are excluded and return 404 — you cannot recover deleted documents through the API
- Maximum file size is 20MB (the upload limit; this defines the maximum you'll encounter on download)
- No batch download endpoint exists — every file requires a separate HTTP request
At ~100 requests/minute, a 200-employee org averaging 8 documents each requires 1,800 total API calls (1,600 downloads + 200 list calls), taking approximately 18 minutes at maximum API throughput. For larger orgs or document-heavy workflows, plan for multi-hour extraction windows and implement checkpointing to resume after failures.
Common edge cases and data quality issues
Date format inconsistency: BambooHR returns dates as YYYY-MM-DD (ISO 8601) via the API and as MM/DD/YYYY in CSV exports from the UI. Use ISO 8601 as your canonical form and normalize at extraction time. Normalizing after loading causes downstream type errors in most data warehouses.
Terminated employees included by default: The API returns both active and inactive employees unless you filter. If you only want current staff, filter on status = "Active" in your application code or use the employeeIds filter with a pre-filtered list.
Custom field IDs are account-specific: BambooHR allows employers to create custom fields, which means the schema varies per employer. Never hardcode custom field IDs across different BambooHR instances — always call GET /meta/fields to resolve field IDs dynamically for each account.
75-field UI limit vs. 400-field API limit: If you're building a UI report for export and hitting the 75-field ceiling, the solution is to switch to the API, not to create multiple reports and merge them manually.
CSV encoding and special characters: BambooHR exports use its own default formatting for dates, numbers, and text. Target systems may expect different date formats, have stricter rules about special characters, or use different character encoding (UTF-8 vs. Latin-1). Without normalization, these mismatches cause import errors or silently corrupted fields. Test with employees who have non-ASCII characters in their names before assuming encoding is clean.
Silent column omission on permission errors: Field IDs that are unknown or that the caller's API key does not have permission to view are silently omitted from the custom report response — the endpoint returns HTTP 200 with fewer columns than requested. Validate output column count against expected field count on every extraction run, not just during initial setup.
Permissions scope: The minimum permission set for a read-only extraction API key should include: Employee Information (read), Payroll (read if extracting compensation), Benefits (read if extracting benefits), and Reports (run). A key with only Employee Information access will silently omit compensation and benefits data. Verify the permission set against your field list before running a production extraction.
BambooHR data portability and compliance
Data portability is the ability to extract your data in a machine-readable format and move it to another system. BambooHR supports this through its API and CSV exports, though there is no single "export everything" button.
BambooHR complies with GDPR (EU and UK), Switzerland's Federal Act on Data Protection (revFADP), CCPA, and CPRA. For European customers, BambooHR stores employee data only on servers located within the European Union.
GDPR Article 20 data portability requests (per-employee): BambooHR does not provide a pre-built per-employee data portability package. You must assemble it manually:
- Call
POST /reports/custom(or the Datasets API) with the employee's ID in theemployeeIdsfilter - Call
GET /employees/{id}/files/viewand download all associated documents - Call
GET /employees/{id}/tables/{table}for each table type (compensation, job history, time-off) to capture historical records - Package in JSON or CSV (machine-readable formats required under Article 20)
- Respond within 30 days of the request (Article 12 deadline)
There is no API endpoint that produces a complete, portable employee record in one call.
Choosing the right extraction method
| Scenario | Best method | Why |
|---|---|---|
| One-time export for analysis | UI Custom Report → CSV | No code needed, fast for < 75 fields |
| Migrating to a new HRIS | API (POST /reports/custom or Datasets + file endpoints) |
Complete data extraction including documents and history |
| Nightly sync to a data warehouse | API with GET /employees/changed |
Incremental; avoids full pulls; handles deletes |
| Real-time sync to identity provider | Webhooks + API backfill | Sub-minute latency for field changes; API handles initial load |
| GDPR Article 20 data subject request | API per-employee (fields + tables + files) | Must assemble per-employee package; no native export |
| Large org (500+ employees) with documents | API with checkpointing | Rate limits require batching; 18+ minutes for documents alone |
Extraction complexity at scale
The BambooHR API is straightforward once you understand three non-obvious behaviors:
- Rate limit signaling uses 503, not 429. Your retry logic must watch for 503 with a
Retry-Afterheader, not 429 Too Many Requests. - Permission failures are silent. Missing columns in your output do not trigger errors — validate column counts.
- Document extraction is inherently serial. There is no batch file download endpoint. For orgs with large document repositories, extraction time is dominated by file downloads, not employee record pulls.
The biggest operational variables in any BambooHR extraction are permissions (verifying the API key has access to every required field before starting) and data quality (custom field inconsistencies, date format normalization, encoding issues). Both are cheaper to resolve at extraction time than after loading into a target system.
Custom fields, historical table data, and binary files each require their own extraction strategy. Plan for them separately, test each independently, and implement checkpointing so a failure in document download does not require re-running the entire employee record extraction.
Frequently Asked Questions
- What is the BambooHR API rate limit?
- BambooHR does not publish exact rate limits, but enforces approximately 100 requests per minute per API key. Exceeding the limit returns a 503 Service Unavailable response (not the standard 429) with a Retry-After header. Failed authentication attempts also count toward the limit.
- How do I export all employee data from BambooHR as CSV?
- Use the Reports tab to create a Custom Report with the fields you need, then click Export and select CSV. Through the API, use POST /reports/custom with format=csv and a list of field IDs in the request body — this returns all employees in a single call with up to 400 fields.
- Can I download employee documents from BambooHR via the API?
- Yes. First call GET /employees/{id}/files/view to list file IDs and categories for an employee, then call GET /employees/{id}/files/{fileId} to download each file. Files are downloaded one at a time as binary attachments. Archived files return 404.
- Does BambooHR support incremental or delta data exports?
- Yes. The GET /employees/changed endpoint returns employee IDs modified since a given ISO 8601 timestamp. The custom reports endpoint also supports a lastChanged filter. For real-time change tracking, BambooHR webhooks send HTTP POST notifications when monitored fields change.
- Is the BambooHR custom reports API endpoint deprecated?
- Yes. BambooHR has marked POST /reports/custom as deprecated and recommends using the newer Datasets API (Get Data from Dataset) instead. The legacy endpoint still works for existing integrations, but new builds should target the Datasets endpoints.