Time off requests
Submit time off requests, manage admin approvals, and apply time off automatically to payroll
This guide covers implementing time off requests and approvals using the Gusto Embedded Payroll time off request APIs. It covers the full request lifecycle β from balance checking and submission through admin approval β and payroll integration.
For managing time off policies (accrual rules, policy creation, assigning employees), see Time Off Policies.
PrerequisitesEmployees must be assigned to a time off policy before requests can be submitted. See Manage time off policies to set up policies and enroll employees.
Integration patterns
How you use these APIs depends on whether you have your own time tracking system.
Use the full request lifecycle: check balances, preview impact, create requests in pending status, then approve or decline.
Employees submit requests through your UI; Gusto handles balance tracking and payroll integration. Use the time_off_request_management flow to give admins an embedded approval interface, or call the approve/decline endpoints directly from your own UI.
Typical flow:
GET /v1/companies/{company_uuid}/time_off/balances
POST /v1/companies/{company_uuid}/time_off/requests/preview
POST /v1/companies/{company_uuid}/time_off/requests
PUT /v1/time_off/requests/{time_off_request_uuid}/approveRequired scopes
| Scope | Required for |
|---|---|
time_off_requests:read | Listing and retrieving requests; checking balances; previewing balance impact |
time_off_requests:write | Creating and canceling employee-initiated requests |
time_off_requests:manage | Approving and declining requests; creating admin-initiated requests via admin_approved_requests |
Partners building a complete integration need all three scopes. Read-only integrations (e.g. syncing request status into your platform) only need time_off_requests:read.
Request statuses
| Status | Description |
|---|---|
pending | Submitted by an employee, awaiting admin review |
approved | Approved by an admin; will be applied to the next payroll |
declined | Declined by an admin |
consumed | Applied to a completed payroll; cannot be modified or canceled |
Step 1: Check time off balances
Before displaying a request form, fetch the employee's current balances to show available hours per policy.
Call GET /v1/companies/{company_uuid}/time_off/balances.
Scope required: time_off_requests:read
Query parameters
| Parameter | Type | Description |
|---|---|---|
employee_uuids | string | Comma-separated employee UUIDs to filter results |
policy_uuids | string | Comma-separated policy UUIDs to filter results |
page | integer | Page number |
per | integer | Results per page |
Response
[
{
"employee_uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"balances": [
{
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"balance_hours": "32.0",
"accrued_hours": "40.0",
"used_hours": "8.0",
"pending_hours": "0.0"
}
]
}
]Each entry in the response contains one object per employee, with a nested balances array containing one entry per policy. All hour values are string decimals. For unlimited policies, balance_hours is null.
Step 2: Preview balance impact
Show the employee how a request would affect their remaining balance before they submit.
Call POST /v1/companies/{company_uuid}/time_off/requests/preview.
Scope required: time_off_requests:read
Request
POST /v1/companies/a4d13b1e-2f89-4c3b-a5d8-7e6c94f21a3d/time_off/requests/preview
{
"employee_uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"start_date": "2026-05-26",
"end_date": "2026-05-27",
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000"
}
}The days field is an object mapping dates (YYYY-MM-DD) to hours as string decimals. Every date in the start_date/end_date range must be included as a key β see the days field below.
To preview changes to an existing request, pass the existing request_uuid in the body; the existing request's values are used as defaults for any fields you omit.
Response
{
"balance_hours": "64.0",
"this_request_hours": "16.0",
"other_requested_hours": "0.0",
"remaining_balance_hours": "48.0",
"allow_negative_balance": false,
"unlimited": false
}remaining_balance_hours = balance_hours β this_request_hours β other_requested_hours. If this is negative and allow_negative_balance is false, the submission will be rejected. If unlimited is true, all hour fields will be null.
Step 3: Create a time off request
Call POST /v1/companies/{company_uuid}/time_off/requests to submit the request. All requests created via this endpoint start in pending status.
Scope required: time_off_requests:write
Request
POST /v1/companies/a4d13b1e-2f89-4c3b-a5d8-7e6c94f21a3d/time_off/requests
{
"employee_uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"start_date": "2026-05-26",
"end_date": "2026-05-27",
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000"
},
"employee_note": "Family vacation"
}Response
{
"uuid": "ad158cfb-99e4-4741-9db3-0bd3a267f222",
"status": "pending",
"policy_type": "vacation",
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000"
},
"employee_note": "Family vacation",
"employer_note": null,
"employee": {
"uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"full_name": "Alex Johnson"
},
"initiator": {
"uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"full_name": "Alex Johnson"
},
"approver": null
}The days field
days fieldThe days field is required on all create and preview endpoints. It must be a JSON object where:
- Keys are dates in
YYYY-MM-DDformat - Values are hours as strings or numbers, between
0and24 - Every date from
start_datethroughend_dateinclusive must appear as a key β partial ranges are not accepted
For example, a request from May 26β28 must include all three dates:
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000",
"2026-05-28": "4.000"
}Step 4: Approve or decline a request
Approve
Call PUT /v1/time_off/requests/{time_off_request_uuid}/approve. Only pending requests can be approved.
Scope required: time_off_requests:manage
The request body is optional. Omit days to approve the request as submitted. Include days to override the hours at approval time.
Request
PUT /v1/time_off/requests/ad158cfb-99e4-4741-9db3-0bd3a267f222/approve
{
"employer_note": "Approved β enjoy your vacation!",
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000"
}
}Response
{
"uuid": "ad158cfb-99e4-4741-9db3-0bd3a267f222",
"status": "approved",
"policy_type": "vacation",
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000"
},
"employee_note": "Family vacation",
"employer_note": "Approved β enjoy your vacation!",
"employee": {
"uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"full_name": "Alex Johnson"
},
"initiator": {
"uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"full_name": "Alex Johnson"
},
"approver": {
"uuid": "21d8dff4-ce09-4120-a274-3a5628bf6769",
"full_name": "Morgan Chen"
}
}The approver_uuid field defaults to the company's primary payroll admin. Pass a specific approver_uuid to attribute the approval to a different admin. Must be a Gusto payroll admin UUID β retrieve valid values from GET /v1/companies/{company_id}/admins.
Decline
Call PUT /v1/time_off/requests/{time_off_request_uuid}/decline. Both pending and approved requests can be declined. An employer_note is required.
Scope required: time_off_requests:manage
Request
PUT /v1/time_off/requests/ad158cfb-99e4-4741-9db3-0bd3a267f222/decline
{
"employer_note": "Coverage needed this week β please resubmit for a different date."
}Response
{
"uuid": "ad158cfb-99e4-4741-9db3-0bd3a267f222",
"status": "declined",
"policy_type": "vacation",
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"days": {
"2026-05-26": "8.000",
"2026-05-27": "8.000"
},
"employee_note": "Family vacation",
"employer_note": "Coverage needed this week β please resubmit for a different date.",
"employee": {
"uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"full_name": "Alex Johnson"
},
"initiator": {
"uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"full_name": "Alex Johnson"
},
"approver": null
}
Admin approval flowRather than calling the approve/decline endpoints directly, you can embed the
time_off_request_managementflow to give admins a full approval interface β including a pending request queue, balance summaries, and the ability to edit days on approval. Create the flow using POST /v1/companies/{company_uuid}/flows withflow_type: "time_off_request_management".
Admin-initiated requests
Use POST /v1/companies/{company_uuid}/time_off/admin_approved_requests when an admin records time off on behalf of an employee β for example, logging a sick day after the fact, or syncing an approval from your own time off system. These requests are always created in approved status and immediately applied to payroll.
Scope required: time_off_requests:manage
Request
POST /v1/companies/a4d13b1e-2f89-4c3b-a5d8-7e6c94f21a3d/time_off/admin_approved_requests
{
"employee_uuid": "51924fa0-26c6-4d4c-8832-3ef0b422c67e",
"policy_uuid": "c2d9b1bd-3f36-4c2d-a727-b2af057d6a7f",
"start_date": "2026-04-14",
"end_date": "2026-04-14",
"days": {
"2026-04-14": "8.000"
},
"employer_note": "Employee called in sick"
}The response is an Embedded-Time-Off-Request object with "status": "approved" and initiator reflecting the admin rather than the employee.
employee_noteis not accepted on admin-initiated requests and will return a 422 if included. Similarly,employer_noteis not accepted on employee-initiated requests (POST /requests).
Canceling a request
Call DELETE /v1/time_off/requests/{time_off_request_uuid} to cancel a request. Returns 204 No Content on success.
Scope required: time_off_requests:write
Both pending and approved requests can be canceled. declined and consumed requests cannot be deleted and will return a 422.
Canceling an approved request reverses its payroll impact if the associated pay period has not yet been processed.
Listing and retrieving requests
List requests
GET /v1/companies/{company_uuid}/time_off/requests β returns an array of Embedded-Time-Off-Request objects.
Scope required: time_off_requests:read
Query parameters
| Parameter | Type | Description |
|---|---|---|
employee_uuids | string | Comma-separated employee UUIDs |
status | string | Comma-separated statuses: pending, approved, declined, consumed |
start_date | date | Include requests that overlap with this start date |
end_date | date | Include requests that overlap with this end date |
sort_order | string | Sort by start_date: asc or desc (default: desc) |
page | integer | Page number |
per | integer | Results per page |
Get a single request
GET /v1/time_off/requests/{time_off_request_uuid} β returns a single Embedded-Time-Off-Request object.
Scope required: time_off_requests:read
Note that this endpoint is not company-scoped β the request UUID alone is sufficient.
Error handling
Common errors and how to handle them
| Error | HTTP status | Description | Resolution |
|---|---|---|---|
| Overlapping request | 422 | A request already exists for one or more of the same dates for this employee (checked against pending, approved, and consumed requests) | List existing requests for the employee and cancel or adjust the conflicting one before resubmitting |
Missing dates in days | 422 | Not all dates between start_date and end_date are present as keys in days | Provide an entry in days for every date in the range, including weekends |
| Hours out of range | 422 | A value in days is less than 0 or greater than 24 | Correct the hours value for the flagged date |
| Negative balance | 422 | The request exceeds the employee's available hours and allow_negative_balance is false for this policy | Reduce the requested hours, or verify the policy's negative balance setting |
| Employee not enrolled in policy | 422 | The employee is not assigned to the specified time off policy | Assign the employee to the policy first β see Manage time off policies |
| Employee not eligible | 422 | The employee is terminated, inactive, or has not completed onboarding | Verify the employee's status before submitting a request |
| Cannot delete request | 422 | Attempted to delete a declined or consumed request | Only pending and approved requests can be deleted |
| Cannot approve request | 422 | Attempted to approve a request that is not in pending status (approved, declined, and consumed requests cannot be approved) | Check the current request status before calling the approve endpoint |
| Cannot decline request | 422 | Attempted to decline a consumed or declined request | Only pending and approved requests can be declined |
employer_note missing on decline | 422 | The decline endpoint requires an employer_note | Include a non-empty employer_note in the request body |
Invalid approver_uuid | 422 | The UUID is not a payroll admin for this company | Retrieve valid admin UUIDs from GET /v1/companies/{company_id}/admins |
Webhooks and notifications
Subscribe to webhooks to track time off request lifecycle events without polling. The following events are available:
time_off_request.created, time_off_request.updated, time_off_request.deleted, time_off_request.approved, time_off_request.declined, pending_time_off_requests
The last two are employee- and admin-targeted notifications respectively; pending_time_off_requests is useful for prompting admins to open your approval UI or the time_off_request_management flow.
FAQ
Can an employee edit a submitted request?
No. Requests cannot be modified after submission. The employee must cancel the existing request (via DELETE) and submit a new one.
Can approver_uuid be an external user ID from my platform?
No. The approver_uuid field on the approve and decline endpoints β and when generating the time_off_request_management flow β must be a Gusto payroll admin UUID for the company. Retrieve valid UUIDs from GET /v1/companies/{company_id}/admins.
What happens to approved time off when payroll is processed?
Approved time off is automatically reflected in payroll calculations. Once a request is consumed (applied to a completed payroll), it can no longer be modified or canceled. Canceling an approved request before payroll is processed reverses its impact.
How do unlimited policies behave with balance checks?
For unlimited policies, balance_hours in the balance endpoint is null, and the preview endpoint returns "unlimited": true with all hour fields as null. Negative balance validation is not applied.
Quick reference
All time off request endpoints
Balances and preview
GET /v1/companies/{company_uuid}/time_off/balances
POST /v1/companies/{company_uuid}/time_off/requests/previewEmployee-initiated requests
GET /v1/companies/{company_uuid}/time_off/requests
POST /v1/companies/{company_uuid}/time_off/requests
GET /v1/time_off/requests/{time_off_request_uuid}
DELETE /v1/time_off/requests/{time_off_request_uuid}Admin actions
POST /v1/companies/{company_uuid}/time_off/admin_approved_requests
PUT /v1/time_off/requests/{time_off_request_uuid}/approve
PUT /v1/time_off/requests/{time_off_request_uuid}/declineRelated guides
Updated about 10 hours ago