Announcing CIMD support for MCP Client registration
Learn more

Should you use Gusto MCP or Gusto API for building AI Agents?

TL;DR

  • The capability gap runs backwards here. Gusto MCP calculates and submits payrolls; the App Integrations API cannot, and Gusto documents that as deliberate.
  • API production access is gated behind a Pre-Approval and Security Review, and Gusto does not support customers connecting their own systems to their own account.
  • Both paths force an admin credential scoped to one company. Gusto OAuth admits only primary or full-access admins, and strict access binds a token to one company.
  • Access tokens expire in 7,200 seconds and refresh tokens are single-use. Two threads refreshing one grant concurrently break it.
  • Scalekit's Gusto MCP connector exposes 50 tools with per-user OAuth, a token vault, and a per-call audit trail, so the credential layer is identical either way.

Two doors, and one of them has a gate

Your agent needs to work with Gusto. It has to pull last cycle's payroll totals, flag blockers on the upcoming run, log hours a contractor submitted, and hand a summary to whoever approves it. Gusto offers two entrances: a hosted MCP server at mcp.api.gusto.com and the App Integrations REST API at api.gusto.com.

Most comparisons in this series conclude that the API is the capable surface and MCP is the convenient one. Gusto inverts that, and the reason has less to do with tooling than with who Gusto will let through the door. Here is how to pick.

What Gusto MCP and the Gusto API actually are

These are two different programs aimed at two different audiences, not two views onto one integration. One is a feature a Gusto admin switches on. The other is a partnership you apply for.

Gusto MCP

The Gusto MCP server is Gusto's own hosted endpoint at https://mcp.api.gusto.com, live since August 2025 per Gusto's changelog, with a Claude-specific variant at /anthropic added that December. Transport is Streamable HTTP against MCP protocol revision 2025-06-18.

Authentication is OAuth with PKCE required, using authorization_code and refresh_token grants against https://mcp.api.gusto.com/oauth/authorize and /oauth/token. Gusto lists Dynamic Client Registration support as a platform requirement, so the client negotiates registration rather than pre-provisioning a client_id.

Who can actually connect it

Two prerequisites decide whether the MCP path is open to you at all. The connecting user must hold primary or global admin permissions in Gusto. At authorization time that admin selects both the company and the data categories the model may reach: company information, employee data, contractor data, payroll data, and time tracking.

Gusto reports more than 12,000 companies connected.

Gusto App Integrations API

The App Integrations API is the REST surface at https://api.gusto.com/v1, date-versioned through an X-Gusto-API-Version header with v2026-06-15 current. It carries the full object model: employees, jobs, compensations, contractors, benefits, garnishments, reimbursements, time sheets, departments, locations, pay schedules, and webhook subscriptions.

Two token types exist. Company-level access_token and refresh_token pairs come from the authorization code flow and are the only credentials that reach company or employee data. System access tokens, obtained with a system_access grant, cover provisioning and webhook subscriptions, and Gusto states plainly that they cannot be used for company or employee level access.

Why the API path has a gate the MCP path does not

Production API access is not self-serve. Gusto requires an approved Production Pre-Approval application and Security Review before issuing production keys, all development happens against api.gusto-demo.com, and Gusto provides no production test accounts. It also names one use case as out of scope: a Gusto customer connecting their own company systems directly to their own Gusto account via the API.

That line reshapes the decision. If you are building an internal agent against your own company's Gusto data, the REST API is not a fallback. MCP is the only supported door.

The other Gusto MCP, which is not this one

Gusto Embedded also runs a Developer Assistant MCP server that indexes API reference material for coding assistants. It answers questions about Gusto's API; it never touches a company's payroll data. If you are evaluating surfaces for a production agent, that is not the server you want.

Comparing them where it matters for agents

The two surfaces overlap heavily on reads and diverge sharply on writes, in a direction most teams guess wrong.

What your agent can actually do

Capability
Gusto MCP
Gusto App Integrations API
Read employees, jobs, and compensation history
Yes
Yes, employees:read and compensations:read
Read payrolls, pay schedules, and pay periods
Yes
Yes, payrolls:read and pay_schedules:read
Read contractors and contractor payments
Yes
Yes, contractors:read and payrolls:read
Update an unprocessed payroll before it runs
Yes, gustomcp_update_payroll
Yes, prepare then update
Calculate and submit a payroll
Yes, gustomcp_run_payroll
No, documented as unsupported
Record or read time records and time sheets
Yes
Yes, time_sheet:write plus payroll syncs
Create or update an employee
No
Yes, employees:manage and employees:write
Create or update a contractor
No
Yes, contractors:manage and contractors:write
Terminate or rehire an employee
Read only
Yes, employments:write
Benefits, garnishments, recurring reimbursements
No
Yes, full write access
Departments, locations, earning types
Read only
Yes, full write access
Time off policies and requests
No
Read, plus adding employees to a policy
Company onboarding questions
Yes, read and save answers
No equivalent
S-corp reasonable salary estimate
Yes, calculate and accept
Yes, salary_estimates:write
Webhook subscriptions and event history
No
Yes, system access token required
Provision a new Gusto company
No
Yes, system access token required
Tax form field data such as W-2 and 941
No
No

The write inversion nobody expects

Gusto's documentation answers the payroll question directly in its Payrolls guide: processing payroll through the API is not supported, because Gusto wants the user to review and confirm inside Gusto before money moves. The API takes you as far as prepare and update on an unprocessed payroll, then stops.

The MCP server does not stop there. gustomcp_run_payroll calculates and submits an existing unprocessed payroll by payroll_uuid. If your agent's job is to run payroll rather than describe it, MCP is currently the only surface that does it.

Validate the write tools in demo first

Write tools on this server are recent. Gusto shipped run_payroll first as a widgetized tool on client-specific variants in early 2026, and update_payroll followed in July 2026. Gusto also pairs writes with a confirmation step before anything moves.

Point your first run_payroll call at mcp.api.gusto-demo.com, not a live company. A tool that submits money deserves a rehearsal.

What only the API can reach

Everything upstream of a payroll run. Creating an employee, changing a compensation, enrolling someone in a benefit, adding a garnishment, terminating a worker, creating a department: all REST-only. So is every event-driven pattern, because the MCP server has no webhooks, while the API ships seventeen documented event categories plus a 30-day event backfill through GET /v1/events.

Neither surface exposes tax form field data. Gusto's guidance is to pull the underlying payroll data and compute what you need from it.

The tool surface is a moving contract

Gusto's published MCP reference lists 36 read-only tools and is stamped current to November 2025. The changelog on that same page then records get_employee_earnings_summary in April 2026, list_time_records added and list_time_sheets deprecated in May 2026, onboarding tooling in June 2026, and update_payroll in July 2026. The reference section and the changelog on one page no longer agree about whether the server writes.

Scalekit's connector, which tracks the live surface, currently lists 50 tools: 42 reads and 8 that mutate state. Treat the inventory as versionless and diff it before each release.

What versioning gets you on the REST side

The API gives you the contract the MCP server does not. You pin a date-based version, get a minimum 12-month support window, and get a 12-month deprecation runway split into six months of full support and six of limited support.

Past that, requests below your application's minimum version return 406 Not Acceptable. Gusto also emits Deprecation, Link, and Sunset response headers, so a deprecation is something you can alert on rather than discover.

The auth path each one puts you on

The two paths differ on almost everything except the shape of the credential you end up holding, which is identical and uncomfortable.

Both paths hand your agent an admin credential

Gusto restricts OAuth authorization to primary or full-access admins on the API side, and to primary or global admins on the MCP side. There is no employee-level or manager-level grant to authorize against.

That breaks the least-privilege pattern that works elsewhere. With Slack or Google Drive you can give an agent a narrow grant belonging to an ordinary user. With Gusto, your agent holds a payroll administrator's credential for an entire company. MCP narrows it slightly through data-category selection at connect time; the API narrows it through scopes Gusto assigns during review, not scopes you request per user. Either way the blast radius of one leaked credential is a company's complete payroll record.

One company per grant, on both paths

Since v2023-05-01 Gusto enforces strict access on the API: an access token is valid for exactly one company, and anything else returns 403. A multi-company administrator, which is the normal case for accounting firms, runs the flow once per company.

MCP mirrors this. The admin picks a single company during authorization, and the company-listing tool returns an array of one. For a B2B agent serving 40 customer companies, that is 40 authorizations on either path, each with its own token pair.

Single-use refresh tokens and the two-hour clock

Gusto access tokens expire in 7,200 seconds. Refresh tokens do not expire on a timer, but each is valid for exactly one use, and the refresh response returns a replacement.

That combination is a concurrency bug waiting to be written. Two agent threads that both notice an expired token and present the same refresh token produce one success and one dead grant, and a dead grant needs a human admin to reauthorize. Gusto's authentication guide tells you to store access_token_expiration as expires_in minus 60, refresh proactively rather than waiting for a 401, and lock the token row during refresh. That is an honest description of what correct token refresh costs, and it is yours to build on both paths.

What you own in production

Choosing a surface settles the request contract. It settles almost nothing about operations.

Rate limits are counted differently

The App Integrations API allows 200 requests per minute per OAuth grant on a 60-second rolling window, returning 429 past that. The MCP server allows 100 requests per minute per user and 500 per minute per company, returning 429 with a Retry-After header.

For a single connected admin the practical ceiling is roughly half on MCP. The per-company cap matters when several agents or several admins in one customer org run concurrently, and the API documents no equivalent company-wide ceiling.

Gusto assumes a human is confirming every call

Gusto's MCP guidance is unusually direct. Do not enable automatic tool execution in your client. Review each tool call before approving it. Verify every output rather than trusting it.

For an interactive assistant that is reasonable, and human-in-the-loop tool calling is the right pattern for a run_payroll call regardless of what a vendor asks for. For an unattended nightly job it means Gusto's supported operating model and your architecture disagree, and you own that gap.

The isolation guidance collides with multi-tool agents

Gusto also asks operators not to connect other MCP servers to the same client session, because payroll data pulled into a shared context can be forwarded elsewhere. A realistic HR agent reads Gusto, posts to a chat tool, and updates a ticket, which is exactly the configuration that guidance rules out.

Server-side execution changes the mechanics. When tool calls resolve outside the model runtime, the agent never opens a raw MCP client session against Gusto's server for another server to co-inhabit, and you decide which Gusto tools exist on the surface at all. It does not make payroll data un-exfiltratable once it is in context. A scoped surface shrinks blast radius; it does not remove the need to think about what you put in front of the model.

Observability is not provided on either path

Gusto retains MCP tool call logs for 90 days and lets a connected user export their own usage logs. That is user-facing self-service, not a per-tenant, per-call audit trail your product can query. The REST API offers no access log at all.

If you need to answer which tenant's agent read which employee's compensation at 2:47am, you build that yourself.

When to use Gusto MCP, when to use the Gusto API

The split is unusually clean for this series, because the gating and the write inversion push in opposite directions.

Use Gusto MCP when

  • You are building for your own company's Gusto account, or for a customer connecting their own, where the App Integrations partner program does not apply
  • The agent needs to prepare, review, and actually submit a payroll, which the REST API will not do
  • The agent is interactive and an admin is present to authorize and confirm write calls, which is Gusto's documented operating model
  • The workload is analytical: payroll cost trends, headcount by department, earnings summaries, upcoming deadlines, blockers on the next run
  • You want a working integration this week rather than after a Security Review

Use the Gusto App Integrations API when

  • You are an approved partner building an accounting, ATS, performance, time tracking, or business operations product for shared customers
  • The agent has to create or change records: hire an employee, update a compensation, terminate a worker, enroll a benefit, add a garnishment
  • You need event-driven behaviour, since webhooks and the 30-day event history exist only on REST
  • You need a pinned, date-versioned contract with deprecation headers rather than a tool list that shifts month to month
  • You need higher throughput per grant, or provisioning and other system-level operations

Most builds that qualify for both will use both: MCP for the admin-facing assistant that runs and explains payroll, REST for the background sync that keeps records in step.

Building Gusto agents with Scalekit

The path you pick changes the tool names and the request shape. It does not change the part that actually breaks in production.

The credential problem on both paths

Forty customer companies means forty admin-level Gusto grants, each expiring every two hours, each refreshing through a single-use token, each needing revocation the day an admin leaves.

Gusto's own documentation hands you a suggested database schema and a row-locking algorithm for this. That is a clear signal about who owns the credential lifecycle. Nothing about it changes between MCP and REST. The token type differs; the infrastructure required does not.

Token vault and per-user isolation

Scalekit stores the Gusto credential in a managed token vault, namespaced per tenant. It resolves server-side at request time and never enters your agent code, your logs, or the model context. Refresh is handled centrally, which removes the concurrent-refresh race Gusto warns about, and revoking a connected account kills every downstream call for that user immediately.

Every call is recorded against the identifier of the admin who authorized it. That is the per-tenant attribution neither Gusto surface provides on its own.

One connector today, and what to do about REST

Scalekit ships the Gusto MCP connector (docs) with connection name gustomcp, OAuth 2.1 with DCR, and all 50 tools. There is no separate Gusto REST connector in the catalog yet.

If your build needs the App Integrations surface, you have two routes. Define it with bring your own connector, declaring the auth pattern and tool schemas while Scalekit keeps handling credential storage and injection. Or request it through Talk to us, where connector requests typically ship inside a week.

Connect a user to Gusto MCP

The admin authorizes once. Scalekit vaults the grant and injects it on every later call, so your code passes a connection name and a user identifier and never touches a token. The connection_name string must match the connection configured in your Scalekit dashboard.

import os from scalekit.client import ScalekitClient from dotenv import load_dotenv load_dotenv() scalekit = ScalekitClient( env_url=os.getenv("SCALEKIT_ENV_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit.actions connection_name = "gustomcp" identifier = "user_123" # Gusto requires a primary or full-access admin to complete this flow link = actions.get_authorization_link( connection_name=connection_name, identifier=identifier, ) print("Authorize Gusto MCP:", link.link) input("Press Enter after authorizing...") # Confirm which company and scopes the grant actually carries token_info = actions.execute_tool( tool_input={}, tool_name="gustomcp_get_token_info", connection_name=connection_name, identifier=identifier, ) print(token_info.data)

The same flow in TypeScript

Node takes the same two steps, with a read against the upcoming payroll standing in for the first real call.

import { ScalekitClient } from '@scalekit-sdk/node' import 'dotenv/config' const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET, ) const actions = scalekit.actions const connector = 'gustomcp' const identifier = 'user_123' const { link } = await actions.getAuthorizationLink({ connectionName: connector, identifier, }) console.log('Authorize Gusto MCP:', link) // After the admin authorizes, list unprocessed payrolls with their blockers const result = await actions.executeTool({ connector, identifier, toolName: 'gustomcp_list_payrolls', toolInput: { processing_statuses: 'unprocessed', payroll_types: 'regular', include: 'totals,risk_blockers', }, }) console.log(result.data)

Run the agent loop with the Claude SDK

Fetch the tools scoped to this admin's connected account first, then hand them to the model. list_scoped_tools returns what this specific grant authorizes rather than the full catalog, so the surface the model reasons over is already narrowed by who authorized it.

from google.protobuf.json_format import MessageToDict import anthropic client = anthropic.Anthropic() scoped_response, _ = actions.tools.list_scoped_tools( identifier="user_123", filter={"connection_names": ["gustomcp"]}, page_size=100, ) llm_tools = [ { "name": MessageToDict(t.tool).get("definition", {}).get("name"), "description": MessageToDict(t.tool).get("definition", {}).get("description", ""), "input_schema": MessageToDict(t.tool).get("definition", {}).get("input_schema", {}), } for t in scoped_response.tools ] messages = [{ "role": "user", "content": "What is blocking our next payroll, and how does its gross cost compare to the last two runs?", }] while True: response = client.messages.create( model="claude-sonnet-4-6", max_tokens=1024, tools=llm_tools, messages=messages, ) if response.stop_reason == "end_turn": print(response.content[0].text) break tool_results = [] for block in response.content: if block.type == "tool_use": result = actions.execute_tool( tool_name=block.name, identifier="user_123", connection_name="gustomcp", tool_input=block.input, ) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": str(result.data), }) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})

Run the agent loop with LangChain

actions.langchain.get_tools returns native StructuredTool objects, so nothing needs reshaping between Scalekit and the model. Bind them and run the loop.

from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, ToolMessage tools = actions.langchain.get_tools( identifier="user_123", connection_names=["gustomcp"], page_size=100, ) tool_map = {t.name: t for t in tools} llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [HumanMessage( "Summarise year-to-date earnings by department and flag anyone hired in the last 30 days" )] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: result = tool_map[tc["name"]].invoke(tc["args"]) messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Runnable versions of both patterns live in Scalekit's LangChain example and built-in tools guide.

Scope the surface with a virtual MCP server

Fifty tools on a payroll connector is a risk surface, not just a token cost. gustomcp_run_payroll moves money. A virtual MCP server lets you declare which tools exist on the endpoint your agent talks to, so a reporting agent cannot submit a payroll even if the model decides it should.

Create the server once per agent role, not once per user. Add the other connections that role needs to the same mapping list.

import os from scalekit.client import ScalekitClient from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping scalekit = ScalekitClient( env_url=os.environ["SCALEKIT_ENV_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) # A read-only payroll reporting role: no run_payroll, no update_payroll vmcp_response = scalekit.actions.mcp.create_config( name="payroll-reporting-agent", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="gustomcp", tools=[ "gustomcp_list_payrolls", "gustomcp_get_payroll", "gustomcp_list_payroll_blockers", "gustomcp_get_employee_earnings_summary", "gustomcp_list_employees", ], ), ], ) config_id = vmcp_response.config.id mcp_server_url = vmcp_response.config.mcp_server_url

Mint a session token before each run

Confirm the admin's connections are still active, then mint a short-lived token bound to that identity. OAuth grants expire and get revoked between runs, so check rather than assume.

from datetime import timedelta accounts_response = scalekit.actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier="user_123", include_auth_link=True, ) for account in accounts_response.connected_accounts: if account.connected_account_status != "ACTIVE": print(f"{account.connection_name} needs auth: {account.authentication_link}") token_response = scalekit.actions.mcp.create_session_token( mcp_config_id=config_id, identifier="user_123", expiry=timedelta(minutes=30), ) mcp_server = { "url": mcp_server_url, "headers": {"Authorization": f"Bearer {token_response.token}"}, }

This is where the multi-tenant story stops being a promise. One server definition serves every customer, and the session token decides whose Gusto company the run touches. It is also the practical answer to Gusto's isolation guidance: the tools sit on a surface you defined, at the granularity you chose, instead of two vendor servers sharing one client session. When to use a virtual MCP server goes deeper on the pattern.

Connect a Mastra agent in TypeScript

Mastra speaks MCP natively, so it consumes the same virtual server. Generate the per-user URL on your backend and pass it in per request. Never share one URL across users; each is pre-authenticated for exactly one identity.

# Backend (Python): one instance per user session inst_response = actions.mcp.ensure_instance( config_name="payroll-reporting-agent", user_identifier="user_123", ) mcp_url = inst_response.instance.url
import { Agent } from '@mastra/core/agent'; import { MCPClient } from '@mastra/mcp'; import { openai } from '@ai-sdk/openai'; // Resolved on the server for the authenticated admin, never a shared constant const mcpUrl = await getMcpUrlForUser(currentUserId); const mcp = new MCPClient({ servers: { scalekit: { url: new URL(mcpUrl) }, }, }); const tools = await mcp.getTools(); const agent = new Agent({ name: 'payroll_reporting_agent', instructions: 'You report on Gusto payroll. Never state a figure you did not retrieve from a tool call.', model: openai('gpt-4o'), tools, }); const result = await agent.generate( 'Summarise the blockers on our next payroll run and who they affect' ); console.log(result.text); await mcp.disconnect();

The Mastra example covers the same setup end to end.

Downstream tool calling and observability

A Gusto agent is rarely a Gusto-only agent. New hire provisioning touches an HRIS, a directory, and a chat tool. A PTO request touches a calendar. Each one adds a credential and an attribution question.

Because list_scoped_tools returns only what the current user authorized across every connector, the model works from a handful of tools rather than a merged catalog, which improves selection accuracy and cuts the token overhead a large surface burns before any work starts. Every call across every connector lands in one audit log keyed to the authorizing user, exportable for SOC 2 evidence and incident review, and revocation becomes a single action that cuts access everywhere instead of a per-tool cleanup.

Where to start for HR and payroll builds

Adding the fortieth tenant should mean adding an identifier, not a fortieth auth implementation. These templates are already wired that way: the new hire provisioning agent, the PTO leave request agent, the offer letter routing agent, and the performance review collector agent.

The full catalog sits at Scalekit connectors and the AgentKit connector docs. Pricing covers what tool calling and audit cost at volume.

Which one to build against

Answer the gating question before the capability question, because it disqualifies one path outright for most teams.

Start with whether the API is even open to you

If you are not an approved Gusto App Integrations partner, or you are building against your own company's Gusto account, the REST API is not available and Gusto MCP is the build. If you are an approved partner and your agent has to create employees, change compensation, manage benefits, or react to webhook events, build on REST and accept that a human finishes the payroll run inside Gusto.

If you qualify for both, split by verb

Use MCP for the admin-facing assistant that reads, stages, and submits payroll under supervision. Use REST for the background pipeline that keeps records in sync and reacts to events. The QuickBooks MCP vs API comparison covers the adjacent finance surface if you are wiring both.

The part that does not change

Both paths hand your agent an admin-level, single-company, two-hour token with a single-use refresh, and neither stores, rotates, revokes, or attributes it. That is the layer that decides whether a Gusto agent survives its second tenant.

Browse the Scalekit Gusto MCP connector: scalekit.com/connectors/gustomcp

Read the connector docs: docs.scalekit.com/agentkit/connectors/gustomcp

Building a Gusto agent and hitting an edge case? Talk to us for immediate help.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.