Gusto Sync Runs
The sync run object
A sync run posts Harvest time entries to Gusto for a date range or a Gusto payroll. It is created by creating a sync run and read by retrieving a sync run.
| Attribute | Type | Description |
|---|---|---|
id |
integer | Unique ID for the sync run. |
status |
string | Status of the run: queued, running, completed, completed_with_errors, or failed. |
run_type |
string | The run type, always time_sheet. |
date_range_start |
date | First day of the inclusive date range. |
date_range_end |
date | Last day of the inclusive date range. |
gusto_payroll_uuid |
string | The source Gusto payroll’s UUID, or null. |
workweek_anchor |
string | The FLSA workweek start day recorded when the run was queued. |
initiated_by_user_id |
integer | The ID of the user who created the run, or null. |
created_at |
datetime | Date and time the run was created. |
started_at |
datetime | Date and time the run started, or null until it starts. |
completed_at |
datetime | Date and time the run completed, or null until it completes. |
terminal_error_message |
string | The error that ended the run, or null. |
created_count |
integer | Number of entries posted. |
skipped_count |
integer | Number of entries skipped because there was nothing to do or they were held. |
error_count |
integer | Number of entries that failed in Gusto. |
The counts report the run’s latest recorded outcome for each entry.
The sync preview object
A preview reports the entries that a sync run would post, without creating the run.
| Attribute | Type | Description |
|---|---|---|
date_range_start |
date | First day of the inclusive date range, from the sent dates or the payroll’s period. |
date_range_end |
date | Last day of the inclusive date range, from the sent dates or the payroll’s period. |
gusto_payroll_uuid |
string | The source Gusto payroll’s UUID, or null. |
locked_filtered_count |
integer | Number of entries dropped because their Gusto pay period is locked. |
api_errors |
array | Messages from upstream Gusto reads that failed. Empty when every read succeeds. |
projects |
array | Teammate and project groups with the entries that would sync. |
Each projects entry contains:
| Attribute | Type | Description |
|---|---|---|
user_mapping_id |
integer | The user mapping to select when creating the run. |
user_id |
integer | The Harvest teammate’s ID. |
project_id |
integer | The project to select when creating the run. |
dominant_state |
string | The group’s state: syncable, all_synced, all_deleted_needs_delete, all_exempt, all_owner, all_contractor_not_hourly, all_flsa_unverified, all_flsa_conflict, or mixed. |
entry_count |
integer | Number of the group’s entries a sync would post, update, or delete. |
total_hours |
decimal | Total hours across those entries. |
state_counts |
object | Number of entries per state, counting every entry in the group: syncable, already_synced, edited, deleted_needs_delete, data_error, exempt, owner, contractor_not_hourly, flsa_unverified, and flsa_conflict. |
job_required |
boolean | true when creating the run needs a gusto_job_uuid for this group. |
suggested_job_uuid |
string | The suggested Gusto job for the group, or null. |
available_jobs |
array | The group’s available Gusto jobs: each entry has a uuid and a title. |
suggested_job_uuid is the job already posted for the group, or the only available job, or null.
Required permissions
Managing Gusto sync runs requires permission to write company settings. Callers without it receive a 403 Forbidden status code with Not authorized!.
The Gusto integration must be enabled for the account. Accounts without it receive a 403 Forbidden status code with Gusto integration is not enabled for this account.
Every request must include OAuth2 bearer, personal access token, or basic auth credentials and a User-Agent header. Requests with a body require a JSON Content-Type.
Retrieving a sync run does not require an active Gusto connection. Previewing and creating one do.
Preview a sync run
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Previews a time-sheet sync for a date range or a Gusto payroll. Returns a sync preview object and a 200 OK response code. This request does not create a run.
POST /v2/integrations/gusto/sync_runs/preview
| Parameter | Type | Description |
|---|---|---|
gusto_payroll_uuid |
string | Optional. Selects a Gusto payroll. When present, the payroll’s period determines the range and the sent dates do not set it. A blank or non-string value, or a payroll without a pay period, returns 422. |
date_range_start |
date | Optional. First day of the inclusive range, as an ISO-8601 date. Defaults to six days before today. |
date_range_end |
date | Optional. Last day of the inclusive range, as an ISO-8601 date. Defaults to today. |
A malformed date, a start date after the end date, or a range longer than 90 days returns 422.
Example Request:
curl -X POST "https://api.harvestapp.com/v2/integrations/gusto/sync_runs/preview" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "User-Agent: MyApp ([email protected])" \
-d '{"date_range_start": "2026-10-13", "date_range_end": "2026-10-19"}'
Example Response:
{
"date_range_start": "2026-10-13",
"date_range_end": "2026-10-19",
"gusto_payroll_uuid": null,
"locked_filtered_count": 0,
"api_errors": [],
"projects": [
{
"user_mapping_id": 7001,
"user_id": 1782959,
"project_id": 14308069,
"dominant_state": "syncable",
"entry_count": 3,
"total_hours": 12.5,
"state_counts": {
"syncable": 3,
"already_synced": 0,
"edited": 0,
"deleted_needs_delete": 0,
"data_error": 0,
"exempt": 0,
"owner": 0,
"contractor_not_hourly": 0,
"flsa_unverified": 0,
"flsa_conflict": 0
},
"job_required": true,
"suggested_job_uuid": "9f8e7d6c-5b4a-3928-1706-e5f4d3c2b1a0",
"available_jobs": [
{
"uuid": "9f8e7d6c-5b4a-3928-1706-e5f4d3c2b1a0",
"title": "Designer"
}
]
}
]
}Create a sync run
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Creates and queues a time-sheet sync run for a date range or a Gusto payroll. Returns a sync run object, a 201 Created response code, and a Location header pointing to the run.
POST /v2/integrations/gusto/sync_runs
| Parameter | Type | Description |
|---|---|---|
gusto_payroll_uuid |
string | Optional. Selects a Gusto payroll. When present, the payroll’s period determines the range and the sent dates do not set it. A blank or non-string value, or a payroll without a pay period, returns 422. |
date_range_start |
date | Optional. First day of the inclusive range, as an ISO-8601 date. Defaults to six days before today. |
date_range_end |
date | Optional. Last day of the inclusive range, as an ISO-8601 date. Defaults to today. |
projects |
array | Required. A non-empty array with one object per teammate and project pair. Duplicate pairs are reduced to the first occurrence. |
projects[].user_mapping_id |
integer | Required. The user mapping for the pair, as a JSON integer or a string of decimal digits. |
projects[].project_id |
integer | Required. The project for the pair, as a JSON integer or a string of decimal digits. |
projects[].gusto_job_uuid |
string | Optional. The Gusto job to post the pair’s entries to. Required for an eligible employee pair that has jobs, except when the pair only needs a delete. |
An invalid date range returns 422. An array element that is not an object, a non-integer user_mapping_id or project_id, or a non-string gusto_job_uuid returns an indexed 422. Pairs that are not eligible in the range, or have no entries left to sync, return one 422 listing every such pair. A missing gusto_job_uuid where one is required returns 422 with Pick a Gusto Job for every selected project. A gusto_job_uuid is not checked against the pair’s available jobs when the run is created: an unknown job is accepted, and its entries are reported as skipped when the run processes them. Creating a run while another run is active returns 409 with A Gusto time-sync run is already in progress.
Example Request:
curl -X POST "https://api.harvestapp.com/v2/integrations/gusto/sync_runs" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "User-Agent: MyApp ([email protected])" \
-d '{"date_range_start": "2026-10-13", "date_range_end": "2026-10-19", "projects": [{"user_mapping_id": 7001, "project_id": 14308069, "gusto_job_uuid": "9f8e7d6c-5b4a-3928-1706-e5f4d3c2b1a0"}]}'
Example Response:
{
"id": 4201,
"status": "queued",
"run_type": "time_sheet",
"date_range_start": "2026-10-13",
"date_range_end": "2026-10-19",
"gusto_payroll_uuid": null,
"workweek_anchor": "Sunday",
"initiated_by_user_id": 1782884,
"created_at": "2026-10-19T14:30:00Z",
"started_at": null,
"completed_at": null,
"terminal_error_message": null,
"created_count": 0,
"skipped_count": 0,
"error_count": 0
}Retrieve a sync run
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Retrieves an existing sync run. Returns a sync run object and a 200 OK response code.
GET /v2/integrations/gusto/sync_runs/{SYNC_RUN_ID}
This request takes no parameters.
Example Request:
curl "https://api.harvestapp.com/v2/integrations/gusto/sync_runs/4201" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"id": 4201,
"status": "completed",
"run_type": "time_sheet",
"date_range_start": "2026-10-13",
"date_range_end": "2026-10-19",
"gusto_payroll_uuid": null,
"workweek_anchor": "Sunday",
"initiated_by_user_id": 1782884,
"created_at": "2026-10-19T14:30:00Z",
"started_at": "2026-10-19T14:30:05Z",
"completed_at": "2026-10-19T14:30:41Z",
"terminal_error_message": null,
"created_count": 3,
"skipped_count": 0,
"error_count": 0
}Errors
The Gusto sync run endpoints return the following status codes:
| Status | Description |
|---|---|
401 |
Credentials are missing or invalid. |
403 |
The Gusto integration is not enabled for the account (Gusto integration is not enabled for this account), or the caller cannot write company settings (Not authorized!). |
404 |
The sync run does not exist or belongs to another company. |
409 |
The account has no active Gusto connection (Connect Gusto before …), or another time-sheet sync run is active (A Gusto time-sync run is already in progress.). |
422 |
A date range, Gusto payroll, or project selection is invalid. This includes a range longer than 90 days, a payroll without a pay period, a malformed projects element (indexed), ineligible pairs (listed in one message), and a missing required Gusto job. |
502 |
The Gusto connection must be reconnected (Reconnect Gusto before …), or a Gusto read failed (We couldn’t finish …: <upstream message>). |
503 |
Gusto is rate-limiting requests (Gusto is rate-limiting us right now…). |