Announcing CIMD support for MCP Client registration
Learn more

BambooHR MCP vs BambooHR API for AI Agents (2026)

Nishant Choudhary
Tech Evangelist

BambooHR now gives an AI agent two doors into a company's HR data: an official MCP server, in open beta, and a REST API with OAuth 2.0, API keys, and webhooks. They overlap on reading people and time off and diverge on writes, events, time tracking, and whose permissions each call runs under. This guide compares what each path lets an agent do, the credential each one puts you on, and how to run either across many users in production.

TL;DR

  • BambooHR hosts an official MCP server on each company's own subdomain (for example https://acme.bamboohr.com/api/mcp), in open beta since its Claude connector launched on October 8, 2026. It exposes 56 tools (41 read, 12 write, 2 delete, 1 utility) covering the directory, employee records, time off, reports, datasets, goals, hiring, and BambooHR Global Employment. It has no time tracking, benefits, training, or change-feed tools, and its only write on an employee record is update_employee.
  • The MCP server takes OAuth 2.0 only: Authorization Code, a required mcp scope, one-hour access tokens, and no Dynamic Client Registration (DCR). The REST API, also served per company (for example https://acme.bamboohr.com/api/v1/), takes OAuth 2.0 apps from BambooHR's Developer Portal or an API key sent as the HTTP Basic username. Either way, the call runs with the permissions of the BambooHR user behind the credential.
  • An API key never expires and is shown only once. It acts as whoever generated it. One HR admin's key wired into a multi-user agent gives every user of that agent HR-admin reach into compensation, dependents, and documents. On the MCP path, each user signs in separately and BambooHR enforces their access level.
  • MCP is the faster path for an HR lead or manager asking Claude who is out next week or how many vacation hours someone has left. The API is the right path for headless agents that react to webhooks, approve timesheets, sync job and compensation history, or run time-off workflows across many customer companies.
  • Scalekit's bamboohr connector exposes 89 prebuilt tools over the BambooHR API. Each user connects their own API key once, Scalekit stores it encrypted, and the agent never sees it, so the MCP vs API decision doesn't change your auth infrastructure.

The decision your BambooHR agent forces first

Your agent needs to work inside your customers' BambooHR accounts: tell a manager which direct reports are out the week before a launch, check whether a vacation request fits the employee's balance, pull headcount by department from a saved report, or cancel a departing employee's future time off. BambooHR now hosts an official Model Context Protocol (MCP) server and ships a Claude connector built on it. It also has a REST API with OAuth 2.0, API keys, and webhooks. They overlap on reading people and time off. They diverge on writes, event delivery, time tracking, and on whose permissions an action runs under. Here's how to pick.

What BambooHR MCP and BambooHR API actually are

BambooHR MCP

The BambooHR MCP server is hosted by BambooHR at https://{your-subdomain}.bamboohr.com/api/mcp over Streamable HTTP. Nothing runs locally. BambooHR announced it on October 8, 2026 alongside a Claude connector, in open beta, and its MCP docs also describe a ChatGPT connector for beta participants. BambooHR calls the beta path "best-effort." The product update lists it for the Core, Pro, and Elite plans.

Clients authenticate with OAuth 2.0 Authorization Code: the user signs in on BambooHR's login page and approves access. An admin must first enable the AI Connectors app under Settings > Apps, and by default only admins may connect. The 56 tools use names such as list_whos_out, get_time_off_balance, create_time_off_request, update_time_off_request_status, get_report_by_id, and get_data_from_dataset_v2. Tool names switched from hyphenated to snake_case form on August 20, 2026, according to BambooHR's API changelog.

Before the official server, the community filled the gap with local servers. acalder-techpm/bamboohr-mcp, for example, runs as a local stdio process, advertises 74 tools, and reads a BAMBOOHR_API_KEY and BAMBOOHR_SUBDOMAIN from the environment. Servers like it are unofficial, and they run with one API key, so every request carries the permissions of whoever generated that key.

BambooHR API

The BambooHR API is a REST API served from each company's subdomain, for example https://acme.bamboohr.com/api/v1/ for a company whose BambooHR address is acme.bamboohr.com. It covers employees and the directory, employee history tables such as jobInfo and compensation, time off requests, balances, and policies, time tracking and timesheets, custom reports and datasets, goals, applicant tracking, benefits and dependents, training records, files, and webhooks. There are two ways to authenticate:

  • API key: sent through HTTP Basic auth, with the key as the username and any string as the password. "Requests are authorized as if the key's associated user were making them."
  • OAuth 2.0: the primary method since March 31, 2025, with self-service apps in the Developer Portal since April 15, 2025. Users authorize at authorize.php, apps exchange the code at token.php, access tokens last 3,600 seconds, and you only get a refresh token if you request offline_access.

The older OpenID Connect Login API, which let marketplace partners fetch a user's API key after an SSO sign-in, has been closed to new applications since April 14, 2025.

Comparing them where it matters for agents

What your agent can actually do

Capability
BambooHR MCP (/api/mcp)
BambooHR API (/api/v1)
Who's out and the company calendar
Yes, list_whos_out
Yes, Scalekit bamboohr_whos_out_list, bamboohr_calendar_events_list with department and type filters
Time off balances and requests
Yes, get_time_off_balance, create_time_off_request, update_time_off_request_status
Yes, Scalekit bamboohr_time_off_request_approve, bamboohr_time_off_request_deny, bamboohr_time_off_request_cancel, bamboohr_time_off_balance_adjust
Read employee records
Yes, get_employee, list_employees, get_employees_directory
Yes, Scalekit bamboohr_employee_get, bamboohr_employees_list (cursor-paginated, filter by manager), bamboohr_employee_directory_get
Create or change employee records
Limited: update_employee only; no create
Yes, Scalekit bamboohr_employee_create, bamboohr_employee_update
Job, compensation, and status history
Limited: get_employee_table_data reads only
Yes, Scalekit bamboohr_employee_table_rows_list, bamboohr_employee_table_row_create, bamboohr_employee_table_row_update
Saved reports and datasets
Yes, list_reports, get_report_by_id, get_data_from_dataset_v2
Yes, Scalekit bamboohr_custom_reports_list, bamboohr_custom_report_get, bamboohr_dataset_data_query
Hiring pipeline
Limited: get_applications, get_application_details, create_application_comment; no status changes
Yes, Scalekit bamboohr_applications_list, bamboohr_application_status_update, bamboohr_application_comment_create
Goals
Yes, 16 goal and goal-comment tools, including delete_goal
Yes, Scalekit bamboohr_goal_create, bamboohr_goal_progress_update, bamboohr_goal_close; no delete
Time tracking and timesheets
No tools
Yes, Scalekit bamboohr_time_tracking_clock_in, bamboohr_timesheet_entries_list, bamboohr_timesheet_approve
Benefits, dependents, and training
Not in the published tool list
Yes, Scalekit bamboohr_company_benefits_list, bamboohr_employee_dependents_list, bamboohr_employee_training_record_create
Global Employment workers and contracts
Yes, 10 read tools such as list_remote_employments; not part of the public REST API
Not available
Change detection
No change feed or triggers
Yes, Scalekit bamboohr_employees_changed_list, bamboohr_employees_changed_table_rows_list; webhooks are configured outside the connector

BambooHR frames its MCP server as a way to "read and act on HR data using plain language," and its own docs say "not every BambooHR capability is exposed yet." The server is built for a person asking questions in a chat window, with a human confirming each write. The API is built for systems that keep HR data in sync and run workflows without anyone watching.

What the MCP path makes easier

Answering "how many vacation hours does each of my direct reports have left, and who is already out in December" on the raw API takes several calls. You resolve the caller's employee ID, list their direct reports, read the time off types to find the company's name for vacation, call the balance endpoint once per employee, and then pull the calendar. Each response lands in the model's context.

BambooHR MCP folds some of that into the server. calculate_date handles date arithmetic server side, so the model doesn't have to work out what "next Friday" means. get_data_from_dataset_v2 answers headcount and tenure questions as one query. The Claude connector lets each user turn off individual tools under Configure, and BambooHR recommends switching off write tools during the beta. Writing the schema is the hard part. Not the API call.

The tradeoff is that BambooHR owns the surface. Tool names have already changed once in the beta, and BambooHR's docs warn that older hyphenated names may linger in some clients until the connector is refreshed. An agent tuned against today's tool names can change behavior without a release on your side.

The auth path each one puts you on

On the MCP path, auth is OAuth in the browser, and the result is a user-level identity. The server "enforces existing BambooHR permissions and can't grant access the user doesn't already have." Every client needs the mcp scope, and entity scopes such as employees, time_off, payroll, and benefits decide which data it can reach. Claude and ChatGPT use client credentials that BambooHR issues to beta participants. Any other MCP client, including your own agent, has to register an application in the BambooHR Developer Portal with an exact redirect URI, because Dynamic Client Registration is not supported. Admins control who can connect under Settings > Apps > AI Connectors, and disabling the app revokes every connection in the company.

On the direct API path, you choose between two credentials. An OAuth 2.0 app gives you per-user tokens that expire after an hour and refresh only if you asked for offline_access. An API key is a 160-bit hex string that a user generates from their own profile menu, if their permissions allow it. BambooHR shows it once and then stores it encrypted. It does not expire. Send an unknown key repeatedly and BambooHR temporarily disables API access with 403 Forbidden.

Both paths share one property: BambooHR authorizes every call as a specific BambooHR user. The difference is which user. On MCP, it's the person in the chat. On the API with a key, it's whoever generated the key, and that's often the HR admin who set up the integration. What the user can't do, the agent can't do. That holds only when the credential belongs to the user the agent is acting for.

BambooHR adds a twist that matters for agents: permissions fail quietly. get_employee drops fields the caller can't read without saying so. list_employees returns null for restricted values and can drop employees entirely when a filter field is unreadable. In BambooHR's words, an empty result means "nothing visible to this caller," not "nothing exists." A manager-level credential produces confident, incomplete answers.

Both paths still need per-user isolation

In a multi-tenant B2B agent, MCP gives you a credential per user and the direct API gives you a credential per user. In neither case does the path itself solve storage, rotation, or revocation; those are infrastructure problems regardless of which path you choose.

BambooHR makes the boundary sharper than most tools, because the data is personal: salaries, home addresses, dependents, and time off reasons. On the API path, the convenient setup is one admin key per customer company, and that key reads every record the admin can. Your app then has to filter by reports_to_id or department whenever an ordinary manager asks a question, because BambooHR answers for the admin. The safer setup is one key or OAuth grant per user, so BambooHR's own access levels do the filtering. That multiplies the credentials you hold.

Recommended reading: access control for multi-tenant AI agents

What you own in production

On the MCP path, BambooHR hosts the server, owns the tool schemas, and enforces each user's access level. You own each user's OAuth session in a client you may not control, plus a Developer Portal application and redirect URI if you bring your own client. You also own the gaps. Tools only run inside a conversation someone starts, so nothing happens when an employee's status changes. Time tracking, benefits, and training aren't reachable. Writes "may run through approval workflows that the connector doesn't bypass," so a created time off request sits in REQUESTED until a human approver acts.

On the API path, you own schemas, pagination, retries, and the credential lifecycle. BambooHR doesn't publish numeric rate limits. Since September 16, 2026, requests that exceed a limit return 429 with a Retry-After header instead of the 503 BambooHR returned before, so older retry code that only watched for 503 now misses throttling. The technical overview also tells you to expect dropped connections with no HTTP response at all. GET /api/v1/employees has used cursor pagination since October 2, 2025, but the directory and custom reports return everything at once, and a single employee read is capped at 400 fields.

Webhooks are how a headless agent learns that something changed, and BambooHR has two kinds. Global webhooks are configured by an admin in BambooHR settings and watch a fixed list of standard and custom fields. Permissioned webhooks are created through the API and can watch any field the creating user can read, which also means they see nothing that user can't. Both have been event-driven since August 5, 2025. Each delivery is signed with HMAC-SHA256 over the raw body concatenated with the X-BambooHR-Timestamp value, and the result arrives in X-BambooHR-Signature. Payloads batch several employees into one POST, each with id, changedFields, and fields. BambooHR retries up to 5 times on network failures or 5xx responses, at roughly immediate, 5, 10, 20, and 40 minutes, plus 0 to 30 seconds of jitter.

When to use MCP, when to use the API

Use BambooHR MCP when:

  • An HR lead in Claude asks for headcount by department or tenure across the company, and get_data_from_dataset_v2 or a saved report answers it in one turn.
  • A manager asks who on their team is out the week of a launch and how much vacation each person has left, with BambooHR's access levels deciding what they see.
  • An employee wants self-service: check a balance, submit a request with create_time_off_request, or look up a colleague's reporting line, all with a human confirming each write.
  • You are prototyping an HR assistant for your own company, each person signs in under their own access level, and the Claude or ChatGPT connector is the client.

Use the BambooHR API when:

  • The agent runs headless: a permissioned webhook fires when an employee's status changes, and the agent cancels their future time off with bamboohr_time_off_request_cancel.
  • The workflow is a deterministic pipeline: approving PENDING_APPROVAL timesheets with bamboohr_timesheet_approve, or writing a promotion into jobInfo with bamboohr_employee_table_row_create.
  • You serve many customer companies and need a credential per user or per company, webhooks per company, and retries that respect 429 and Retry-After.
  • The task needs capabilities the MCP server doesn't expose, such as time tracking, benefits and dependents, training records, employee creation, or moving candidates between hiring stages.

The credential problem that exists on both paths

Both paths hand you a BambooHR credential per user. Neither hands you a vault. A multi-tenant agent serving 60 customer companies, with 10 managers each, holds 600 credentials if it does isolation properly. On the API-key path, those are long-lived secrets that never expire, which BambooHR shows only once, and which can only be revoked by deleting the key in BambooHR. Each one reads salaries and home addresses for everyone its owner can see. Each must be stored encrypted, kept out of logs and LLM context, mapped to exactly one user and one company, and deleted when that user leaves or the customer churns. On the OAuth path, the secrets expire hourly instead, so you add refresh scheduling, offline_access handling, and a Developer Portal app with a client secret to protect.

The MCP path changes the count, not the problem: 600 OAuth sessions living in chat clients, each one gated by an admin setting you don't control. The tempting shortcut on the API path is one HR admin key per company. Fewer secrets, larger blast radius per secret: one leaked key exposes the whole company's HR data, and every manager using the agent can see what only HR should see.

Scalekit's bamboohr connector stores each user's BambooHR API key encrypted in the token vault and injects it into every call, so the MCP vs API decision doesn't change your auth infrastructure. Your agent passes an identifier; credentials never touch the agent runtime or the LLM context.

Recommended reading: OAuth vs API keys for AI agents

How to connect a BambooHR agent with Scalekit

Scalekit's bamboohr connector is an API-path connector built by Scalekit with 89 tools: 54 read, 35 write, and none destructive. It covers employees and the directory, employee tables, fields and list fields, time off requests, balances, policies, and types, who's out and the company calendar, holidays, time tracking and timesheets, custom reports and datasets, goals, hiring, benefits, dependents, training records, files, users, and change feeds. It authenticates with API keys: each user connects their own BambooHR API key once, and Scalekit sends it with every call.

Prerequisites and the connection name

In the Scalekit dashboard, go to AgentKit > Connections and create a BambooHR connection. The name you give it is the connection_name your code passes, and the string must match the dashboard exactly. This is the most common integration error.

BambooHR keys carry their owner's permissions, so plan who generates them. For the examples below, each manager generates a key from their own BambooHR profile menu under API Keys. Balances need the Time Off view permission for each employee, and being someone's manager isn't enough on its own. Copy SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials into .env.

pip install scalekit-sdk-python python-dotenv langchain-openai

Authorize each user once

get_or_create_connected_account returns the connected account for an identifier. If it is not ACTIVE, get_authorization_link returns a link to Scalekit's Hosted Connect Widget. For an API-key connection like BambooHR, the widget shows a form where the user enters the credentials the connection requires, instead of an OAuth consent screen. Scalekit encrypts and stores what they enter; your code never receives the key. The identifier is your app's ID for the person, because a BambooHR key acts as exactly one BambooHR user.

import os from dotenv import load_dotenv from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions CONNECTION_NAME = "bamboohr" # must match the connection name in AgentKit > Connections identifier = "acme_user_8812" # your app's stable ID for this manager at this customer account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=identifier, ) print("Open this link and enter your BambooHR API key:", link.link) input("Press Enter after submitting the form...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": raise RuntimeError(f"BambooHR is {account.connected_account.status}, not ACTIVE")

Retrieve the BambooHR tools this account can call

This is not a catalog load. The agent asks for the tools this connected account is authorized to call, then narrows them to the six a time-off assistant needs. Loading all 89 BambooHR tools would put bamboohr_employee_update, bamboohr_time_off_balance_adjust, and bamboohr_application_status_update in front of a model whose job is to answer coverage questions. None of the six below changes anything in BambooHR.

tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CONNECTION_NAME], tool_names=[ "bamboohr_employee_get", "bamboohr_employees_list", "bamboohr_time_off_types_list", "bamboohr_time_off_requests_list", "bamboohr_time_off_balance_get", "bamboohr_whos_out_list", ], page_size=100, ) tool_map = {t.name: t for t in tools} print(sorted(tool_map))

Run the agent loop

The loop runs until the model stops requesting tools. Every call runs with this manager's BambooHR permissions, so the system prompt tells the model how to resolve "my team" and how to read BambooHR's quiet permission gaps.

from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ SystemMessage( "You help a manager plan time off coverage in BambooHR. Call " "bamboohr_employee_get with employee_id '0' to get the manager's own ID, " "then bamboohr_employees_list with reports_to_id set to that ID and " "status 'active' to find direct reports. Use bamboohr_time_off_types_list " "to find this company's vacation category, because names differ by company. " "Filter bamboohr_time_off_requests_list by employeeId and dates in OData, " "for example: employeeId eq 40342 and startDate ge '2026-12-01'. " "Dates are YYYY-MM-DD. Never guess IDs. If a balance call returns 403 or " "a field is missing, say the data is not visible to this user." ), HumanMessage( "Which of my direct reports have time off requested or approved between " "2026-12-14 and 2026-12-31? Flag days when two or more people overlap, " "and show each person's remaining vacation balance as of 2026-12-31." ), ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: tool = tool_map.get(tc["name"]) if tool: result = tool.invoke(tc["args"]) else: result = f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Deterministic calls with execute_tool

For pipelines, skip the model. execute_tool runs one tool with a fixed input and returns data plus an execution_id you can log. This handler runs when a BambooHR permissioned webhook reports a change to an employee's status field. It verifies X-BambooHR-Signature, confirms the employee is now inactive, and cancels any of their requested or approved time off that hasn't started yet, leaving a comment on each request first. The connected account here should belong to an HR user who can see and cancel everyone's time off.

import hashlib import hmac import json from datetime import date def verify_bamboohr_signature(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool: """BambooHR signs the raw body concatenated with the timestamp, using HMAC-SHA256.""" expected = hmac.new( secret.encode(), raw_body + timestamp.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature.strip()) def handle_status_change(raw_body: bytes, timestamp: str, signature: str, identifier: str) -> None: secret = os.environ["BAMBOOHR_WEBHOOK_PRIVATE_KEY"] # stored per customer webhook if not verify_bamboohr_signature(raw_body, timestamp, signature, secret): raise PermissionError("Invalid X-BambooHR-Signature") payload = json.loads(raw_body) today = date.today().isoformat() for changed in payload.get("employees", []): if "status" not in changed.get("changedFields", []): continue employee_id = str(changed["id"]) employee = actions.execute_tool( tool_name="bamboohr_employee_get", tool_input={"employee_id": employee_id, "fields": "displayName,status"}, connection_name=CONNECTION_NAME, identifier=identifier, ) print(employee.execution_id, employee_id) if employee.data.get("status") != "Inactive": continue requests = actions.execute_tool( tool_name="bamboohr_time_off_requests_list", tool_input={ "filter": ( f"employeeId eq {employee_id} and status in ('requested','approved') " f"and startDate ge '{today}'" ), "page_size": 100, }, connection_name=CONNECTION_NAME, identifier=identifier, ) items = requests.data.get("data", []) if isinstance(requests.data, dict) else requests.data for req in items: request_id = int(req["id"]) actions.execute_tool( tool_name="bamboohr_time_off_request_comment_create", tool_input={ "request_id": request_id, "comment": "Canceled automatically: the employee's status changed to Inactive.", }, connection_name=CONNECTION_NAME, identifier=identifier, ) canceled = actions.execute_tool( tool_name="bamboohr_time_off_request_cancel", tool_input={"request_id": request_id}, connection_name=CONNECTION_NAME, identifier=identifier, ) print(canceled.execution_id, request_id, canceled.data.get("status"))

The webhook itself is not a connector tool. An HR admin creates a global webhook in BambooHR settings, or you create a permissioned webhook through the API with that user's credential, which Scalekit can send on the connected account's behalf through proxied API calls. Store the webhook's private key per customer. Because BambooHR retries failed deliveries and batches several employees into one POST, make the handler idempotent: canceling an already canceled request should be a no-op on your side. The BambooHR MCP server has no equivalent for either step.

Two other writes show where the API path earns its place. bamboohr_timesheet_approve takes a timesheet_id and the timesheet's hoursLastChangedAt, and returns 409 if hours changed since you read them, which is the guard a payroll-close pipeline needs. bamboohr_employee_table_row_create writes a dated jobInfo or compensation row, which is how a promotion lands in BambooHR's history. Neither belongs in a model's default tool set; both fit a pipeline with a human approval step in front of them.

Multi-tool, multi-tenant BambooHR agents on a virtual MCP server

The bamboohr connector has 89 tools. At roughly 200 tokens each, that is about 17,800 tokens of tool definitions before the agent does any work, and BambooHR's own MCP server adds about 11,200 for its 56. A model choosing among bamboohr_whos_out_list, bamboohr_calendar_events_list, bamboohr_time_off_requests_list, and bamboohr_holidays_list picks the wrong list more often than a model holding only the one it needs. Overreach is the bigger risk in HR: a coverage assistant that also holds bamboohr_employee_update and bamboohr_time_off_balance_adjust can edit a record or change a balance on a bad inference. The fix is not better prompting. It is surface reduction. A Scalekit virtual MCP server declares exactly which tools an agent role can see, and session tokens keep each run bound to one user's connected accounts. Docs: set up a virtual MCP server.

Create the server once per agent role

from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = actions.mcp.create_config( name="bamboohr-coverage-assistant", description="Answers BambooHR time off coverage questions for managers and posts a Slack summary", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="bamboohr", tools=[ "bamboohr_employee_get", "bamboohr_employees_list", "bamboohr_whos_out_list", "bamboohr_time_off_requests_list", "bamboohr_time_off_balance_get", ], ), McpConfigConnectionToolMapping( connection_name="slack", tools=["slack_send_message"], ), ], ) print(vmcp.config.id, vmcp.config.mcp_server_url)

Mint a session token per user, per run

One server definition serves every manager at every customer. Before each run, check that the user's accounts are active and mint a short-lived session token scoped to them. Any MCP client that speaks Streamable HTTP can connect; this example uses langchain-mcp-adapters (pip install langchain-mcp-adapters) and passes the token as a Bearer header.

import asyncio from datetime import timedelta from langchain_mcp_adapters.client import MultiServerMCPClient config_id = vmcp.config.id mcp_server_url = vmcp.config.mcp_server_url user_id = "acme_user_8812" accounts = actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier=user_id, include_auth_link=True, ) not_ready = [ a for a in accounts.connected_accounts if str(a.connected_account_status).upper() != "ACTIVE" ] for a in not_ready: print(f"{a.connection_name} is {a.connected_account_status}: {a.authentication_link}") if not_ready: raise RuntimeError("Connect the accounts above before starting this run") session_token = actions.mcp.create_session_token( mcp_config_id=config_id, identifier=user_id, expiry=timedelta(minutes=30), ).token async def load_tools(): client = MultiServerMCPClient( { "scalekit": { "transport": "streamable_http", "url": mcp_server_url, "headers": {"Authorization": f"Bearer {session_token}"}, } } ) return await client.get_tools() mcp_tools = asyncio.run(load_tools()) print([t.name for t in mcp_tools])

The agent sees six tools instead of 89. The endpoint is static; the identity is per user. Unlike BambooHR's own MCP server, this endpoint can include tools BambooHR MCP doesn't offer, such as bamboohr_timesheet_approve, for the one payroll role that should hold it, and leave them out of every other role.

Observability: every BambooHR action tied to a user

Every execute_tool call returns an execution_id, and every execution is attributed to a connected account, which maps to one user at one customer. When an HR director asks which agent run canceled 12 time off requests overnight, or who changed a department on Saturday, the answer is a lookup, not an investigation. BambooHR's bamboohr_employees_changed_list takes a since timestamp and lists every employee inserted, updated, or deleted after it, so you can reconcile what your execution log says the agent did against what changed in BambooHR.

Attribution is also where the paths differ. MCP calls run as the employee who signed in. API-key calls run as whoever generated the key, so if one admin key serves a whole customer, every action runs as that admin, and your execution log is the only place that records which of your users asked for it. A BambooHR key can't reach a second company, but an admin key shared by everyone inside one company still collapses all of them into one identity with one permission set. Shared credentials work for a single user. They do not survive a second user.

Recommended reading: audit trails for agent auth

Which one to build against

If your agent is an HR lead's or manager's assistant in Claude or ChatGPT, answering who's out, checking balances, summarizing saved reports, and submitting requests with a person confirming each write, BambooHR's official MCP server is a good fit, with the caveat that it is still in beta and its tool names are still moving. If your agent runs unattended, reacts to webhooks, approves timesheets, writes job and compensation history, or serves many customer companies at once, build against the BambooHR API, where you choose the credential, the tools, and the retry behavior. Either way, you hold a BambooHR credential per user that carries that user's HR permissions, often a key that never expires, and that's what needs production-grade infrastructure.

FAQs

Does BambooHR have an official MCP server?

Yes. BambooHR hosts an MCP server on each company's own subdomain at https://{your-subdomain}.bamboohr.com/api/mcp over Streamable HTTP. It launched in open beta on October 8, 2026 alongside a Claude connector, exposes 56 tools, and authenticates with OAuth 2.0 Authorization Code only. An admin must enable the AI Connectors app under Settings > Apps before anyone in the company can connect.

What can the BambooHR MCP server not do?

The published tool list has no time tracking, timesheet, benefits, dependents, or training tools, and no change feed or triggers. On employee records its only write is update_employee, with no create. The hiring tools read applications and add comments but cannot move a candidate between stages. It also does not support Dynamic Client Registration, so a custom MCP client has to register an application in the BambooHR Developer Portal with an exact redirect URI.

Whose permissions does a BambooHR API key carry?

The permissions of the user who generated it. BambooHR authorizes requests as if the key's associated user were making them. The key never expires, is shown once, and can only be revoked by deleting it in BambooHR. If one HR admin's key is wired into an agent that many people use, every one of those users gets that admin's reach into compensation, dependents, and documents.

When should an agent use the BambooHR API instead of MCP?

When the agent runs without a person in the loop: reacting to a permissioned webhook when an employee's status changes, approving timesheets, writing a promotion into jobInfo or compensation history, or serving many customer companies with a credential per user and retries that respect 429 and Retry-After. The API is also the only path for time tracking, benefits, training records, employee creation, and hiring stage changes.

How does BambooHR handle permissions the caller does not have?

Quietly. get_employee drops fields the caller cannot read without saying so, list_employees returns null for restricted values and can drop employees entirely when a filter field is unreadable, and an empty result means nothing is visible to this caller rather than nothing exists. A manager-level credential produces confident but incomplete answers, so the agent's prompt should treat missing data as not visible, not absent.

How does Scalekit connect an AI agent to BambooHR?

Through the bamboohr connector, which exposes 89 tools over the BambooHR API. Each user enters their own BambooHR API key once in Scalekit's Hosted Connect Widget. Scalekit stores it encrypted in the token vault and injects it into every call, so the key never reaches the agent runtime or the model. A virtual MCP server then narrows the 89 tools to the ones an agent role needs, session tokens bind each run to one user, and every execution returns an execution_id attributed to that user's connected account.

Build your BambooHR agent with Scalekit

Browse the Scalekit BambooHR connector and the BambooHR connector docs with the full tool list. See Scalekit pricing, or talk to us.

No items found.
On this page
Share this article
Scalekit
Connector credentials

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.