Summary and recommendation
Gusto's REST API (base: `https://api.gusto.com/v1`) uses OAuth 2.0 authorization code flow. The company UUID is not embedded in the token - it must be retrieved via `GET /v1/me` after authorization.
All employee lifecycle operations (create, update, terminate) are available, but each carries non-obvious side effects: employee creation does not trigger onboarding emails, termination via `POST /v1/employees/{employee_id}/terminations` is irreversible through the API, and `PUT` on employee records behaves as a near-full replace rather than a partial update.
There is no inbound SCIM 2.0 endpoint; IdP-driven provisioning into Gusto must use the REST API directly or rely on JIT SSO.
For teams building identity graph pipelines, Gusto employee records - including `uuid`, `department`, `manager_uuid`, `start_date`, and `termination_date` - provide the HR node of the identity graph, but compensation and SSN fields require careful scope and masking handling.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (authorization code flow); API key tokens available for embedded partner integrations |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Not available; Gusto does not offer inbound SCIM provisioning as of early 2025. Outbound provisioning (Gusto → Slack, Zoom, etc.) is available on Plus/Premium plans. |
Authentication
Auth method: OAuth 2.0 (authorization code flow); API key tokens available for embedded partner integrations
Setup steps
- Register an application in the Gusto Developer Portal (https://dev.gusto.com) to obtain a client_id and client_secret.
- Redirect the user to https://api.gusto.com/oauth/authorize with response_type=code, client_id, redirect_uri, and desired scopes.
- Exchange the returned authorization code for an access_token and refresh_token via POST https://api.gusto.com/oauth/token.
- Include the access token in all API requests as: Authorization: Bearer {access_token}.
- Refresh the access token using the refresh_token before expiry; access tokens expire after a short window (exact TTL not publicly documented).
Required scopes
| Scope | Description | Required for |
|---|---|---|
| employees:read | Read employee records including personal info, job details, and status. | GET /v1/companies/{company_id}/employees |
| employees:write | Create and update employee records. | POST /v1/companies/{company_id}/employees, PUT /v1/employees/{employee_id} |
| companies:read | Read company information and metadata. | GET /v1/companies/{company_id} |
| jobs:read | Read job and compensation data for employees. | GET /v1/employees/{employee_id}/jobs |
| jobs:write | Create and update job and compensation records. | POST /v1/employees/{employee_id}/jobs |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| uuid | string | Unique identifier for the employee. | system-generated | immutable | Use this as the stable employee ID. |
| first_name | string | Employee's first name. | required | optional | |
| last_name | string | Employee's last name. | required | optional | |
| string | Work email address; used for Gusto account login invitation. | required | optional | Must be unique across the company. | |
| date_of_birth | string (YYYY-MM-DD) | Employee's date of birth. | optional | optional | Required for payroll processing. |
| ssn | string | Social Security Number (masked in responses). | optional | optional | Returned masked (e.g., XXX-XX-1234) in GET responses. |
| phone | string | Employee phone number. | optional | optional | |
| preferred_first_name | string | Preferred first name if different from legal name. | optional | optional | |
| start_date | string (YYYY-MM-DD) | Employee's hire/start date. | required | optional | |
| termination_date | string (YYYY-MM-DD) | Date of termination if applicable. | n/a | set via termination endpoint | Set via POST /v1/employees/{id}/terminations, not direct field update. |
| department | string | Department name the employee belongs to. | optional | optional | |
| manager_uuid | string | UUID of the employee's manager. | optional | optional | |
| onboarded | boolean | Whether the employee has completed onboarding. | system-set | read-only | |
| has_ssn | boolean | Indicates if SSN has been provided. | system-set | read-only | |
| jobs | array | List of job/compensation objects associated with the employee. | optional | via jobs endpoint | Managed via /v1/employees/{id}/jobs sub-resource. |
| home_address | object | Employee's home address (street, city, state, zip, country). | optional | optional | Required for payroll tax calculations. |
| garnishments | array | Wage garnishment records. | n/a | via garnishments endpoint | Managed via sub-resource endpoint. |
| custom_fields | array | Company-defined custom fields for the employee. | optional | optional | Field definitions must exist in company settings first. |
Core endpoints
List employees for a company
- Method: GET
- URL:
https://api.gusto.com/v1/companies/{company_id}/employees - Watch out for: Returns all employees including terminated ones by default. Filter with ?terminated=false to get only active employees.
Request example
GET /v1/companies/abc-123/employees?page=1&per=25
Authorization: Bearer {token}
Response example
[
{
"uuid": "emp-uuid-001",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"onboarded": true
}
]
Get a single employee
- Method: GET
- URL:
https://api.gusto.com/v1/employees/{employee_id} - Watch out for: SSN is always masked in responses regardless of scope.
Request example
GET /v1/employees/emp-uuid-001
Authorization: Bearer {token}
Response example
{
"uuid": "emp-uuid-001",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"start_date": "2023-01-15",
"onboarded": true
}
Create an employee
- Method: POST
- URL:
https://api.gusto.com/v1/companies/{company_id}/employees - Watch out for: Creating an employee does NOT automatically send an onboarding invitation. A separate onboarding flow or invitation must be triggered. The employee will have onboarded=false until they complete self-onboarding.
Request example
POST /v1/companies/abc-123/employees
Content-Type: application/json
{
"first_name": "John",
"last_name": "Smith",
"email": "john@example.com",
"start_date": "2024-06-01"
}
Response example
{
"uuid": "emp-uuid-002",
"first_name": "John",
"last_name": "Smith",
"email": "john@example.com",
"onboarded": false
}
Update an employee
- Method: PUT
- URL:
https://api.gusto.com/v1/employees/{employee_id} - Watch out for: Uses PUT (full-ish update), not PATCH. Omitting optional fields may clear them. Verify field behavior before bulk updates.
Request example
PUT /v1/employees/emp-uuid-002
Content-Type: application/json
{
"first_name": "Jonathan",
"department": "Engineering"
}
Response example
{
"uuid": "emp-uuid-002",
"first_name": "Jonathan",
"last_name": "Smith",
"department": "Engineering"
}
Terminate an employee
- Method: POST
- URL:
https://api.gusto.com/v1/employees/{employee_id}/terminations - Watch out for: Termination is irreversible via API. run_termination_payroll=true triggers a final payroll run which has billing implications.
Request example
POST /v1/employees/emp-uuid-002/terminations
Content-Type: application/json
{
"effective_date": "2024-12-31",
"run_termination_payroll": true
}
Response example
{
"employee_uuid": "emp-uuid-002",
"active": false,
"effective_date": "2024-12-31",
"run_termination_payroll": true
}
Get company details
- Method: GET
- URL:
https://api.gusto.com/v1/companies/{company_id} - Watch out for: The company UUID is required for most employee endpoints. Retrieve it from the token's associated company after OAuth.
Request example
GET /v1/companies/abc-123
Authorization: Bearer {token}
Response example
{
"uuid": "abc-123",
"name": "Acme Corp",
"ein": "XX-XXXXXXX",
"entity_type": "LLC",
"tier": "plus"
}
List jobs for an employee
- Method: GET
- URL:
https://api.gusto.com/v1/employees/{employee_id}/jobs - Watch out for: Compensation data (rate, payment_unit) is nested within the job object. An employee can have multiple jobs; only one can be primary=true.
Request example
GET /v1/employees/emp-uuid-001/jobs
Authorization: Bearer {token}
Response example
[
{
"uuid": "job-uuid-001",
"title": "Software Engineer",
"rate": "120000.00",
"payment_unit": "Year",
"primary": true
}
]
Get current user (authenticated)
- Method: GET
- URL:
https://api.gusto.com/v1/me - Watch out for: Returns the authenticated user's role and associated company UUIDs. Use this to discover company_id after OAuth rather than hardcoding it.
Request example
GET /v1/me
Authorization: Bearer {token}
Response example
{
"email": "admin@example.com",
"roles": {
"payroll_admin": {
"companies": [{"uuid": "abc-123", "name": "Acme Corp"}]
}
}
}
Rate limits, pagination, and events
- Rate limits: Gusto does not publicly document specific numeric rate limits in its developer docs as of early 2025. Limits are enforced but thresholds are not published.
- Rate-limit headers: Yes
- Retry-After header: No
- Rate-limit notes: HTTP 429 is returned when limits are exceeded. Gusto recommends implementing exponential backoff. Specific header names for rate limit state are not documented publicly.
- Pagination method: offset
- Default page size: 25
- Max page size: 100
- Pagination pointer: page and per (e.g., ?page=1&per=25)
| Plan | Limit | Concurrent |
|---|---|---|
| All API partners | Not publicly documented | 0 |
- Webhooks available: Yes
- Webhook notes: Gusto supports webhooks for event-driven notifications. Webhook subscriptions are configured via the API or Developer Portal. Events are delivered via HTTP POST to a registered endpoint.
- Alternative event strategy: Poll GET /v1/companies/{company_id}/employees with updated_since parameter if webhooks are not feasible.
- Webhook events: employee.created, employee.updated, employee.terminated, payroll.created, payroll.updated, company.updated
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Not available; Gusto does not offer inbound SCIM provisioning as of early 2025. Outbound provisioning (Gusto → Slack, Zoom, etc.) is available on Plus/Premium plans.
- Endpoint: Not documented
Limitations:
- No inbound SCIM 2.0 endpoint for provisioning users into Gusto from an IdP.
- JIT (Just-in-Time) provisioning is supported via Okta and OneLogin SSO integrations.
- Gusto has indicated SCIM support is under exploration but not yet released.
- Outbound provisioning from Gusto to third-party apps requires Plus ($80/mo + $12/person) or Premium plan.
Common scenarios
Three core automation scenarios are well-supported by the API.
First, new-hire onboarding: POST /v1/companies/{company_id}/employees creates the record, followed by POST /v1/employees/{employee_id}/jobs for compensation, but the employee remains onboarded=false until they complete self-service steps in Gusto - payroll cannot run until that flag flips.
Second, directory sync: GET /v1/companies/{company_id}/employees? terminated=false&per=100 with page iteration covers full pulls; subscribe to `employee.
created, employee. updated, and employee.
terminatedwebhooks for delta sync to avoid full re-pulls. Third, offboarding:POST /v1/employees/{employee_id}/terminationswithrun_termination_payroll=falseif final pay is handled separately - setting it totrue` initiates a payroll run immediately with billing implications.
Downstream deprovisioning (Slack, Zoom) can be triggered by the employee. terminated webhook if outbound provisioning is configured on Plus/Premium.
Onboard a new hire via API
- POST /v1/companies/{company_id}/employees with first_name, last_name, email, start_date to create the employee record.
- POST /v1/employees/{employee_id}/jobs to assign a job title and compensation.
- PATCH or PUT /v1/employees/{employee_id} to add home_address and date_of_birth required for payroll.
- Trigger the Gusto self-onboarding invitation from the Gusto admin UI or via the onboarding flow endpoint (if available under your partner agreement) so the employee can complete I-9, W-4, and direct deposit setup.
- Poll GET /v1/employees/{employee_id} or listen for employee.updated webhook until onboarded=true.
Watch out for: API employee creation alone does not complete onboarding. The employee must complete self-service steps in Gusto before payroll can run for them.
Sync active employees to an external directory
- GET /v1/me to retrieve the company UUID for the authenticated token.
- GET /v1/companies/{company_id}/employees?terminated=false&page=1&per=100 to fetch the first page of active employees.
- Iterate pages (increment page param) until the response array is empty or fewer than per records are returned.
- Map Gusto employee fields (uuid, first_name, last_name, email, department, manager_uuid) to the target directory schema.
- Subscribe to employee.created, employee.updated, and employee.terminated webhooks to receive real-time delta updates instead of full re-syncs.
Watch out for: Without the terminated=false filter, terminated employees are included in list responses and must be filtered client-side, inflating payload size.
Offboard (terminate) an employee
- Confirm the employee UUID via GET /v1/companies/{company_id}/employees or GET /v1/me.
- POST /v1/employees/{employee_id}/terminations with effective_date and run_termination_payroll (set false if final pay is handled separately).
- Listen for employee.terminated webhook event to trigger downstream deprovisioning in connected systems (e.g., revoke SSO sessions, disable accounts in Slack/Zoom via Gusto outbound provisioning on Plus/Premium).
- Verify termination by checking active=false on GET /v1/employees/{employee_id}.
Watch out for: Termination via API cannot be undone through the API. Coordinate with HR before automating terminations. run_termination_payroll=true will initiate a payroll run immediately.
Why building this yourself is a trap
The most common integration traps in Gusto's API are scope and state assumptions. Rate limits are enforced via HTTP 429 but thresholds are not publicly documented - exponential backoff is required, not optional.
The PUT endpoint for employee updates is not a safe partial-update operation; omitting fields can clear existing values, making it dangerous for bulk operations without prior field-behavior testing. Multi-company access requires a separate OAuth authorization flow per company - a single token does not span companies.
SSN is always masked in read responses regardless of granted scopes, which affects identity verification workflows. Finally, there is no inbound SCIM support; any integration that assumes a SCIM endpoint exists will fail silently at the provisioning layer.
Automate Gusto workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.