Payroll Digests API
Fetch a summary of payroll state across many companies in a single request
The Payroll Digests API lets partners fetch a summary of payroll stateβupcoming pay dates, due dates, statuses, blockers, pay periods, and totalsβacross many companies in a single asynchronous request.
It is designed for partners building multi-company dashboards on top of Gusto Embedded Payroll. Typical use cases include:
- Accountant and bookkeeper tools that show payroll status across a firm's client companies at a glance.
- Operations dashboards that monitor which companies have upcoming payrolls, which are blocked, and which need attention.
- Any partner-side workflow that needs a "what's happening this week and next" view of payroll across a portfolio of companies.
The API is asynchronous: a single POST kicks off a server-side computation, and a GET returns the results once they're ready. The pattern mirrors the People Batch API so partners already familiar with that API can integrate quickly.
Unlike the People Batch API, the Payroll Digests API is read-only. No new records are createdβGusto computes a point-in-time snapshot of existing payroll data within a fixed date window (7 days past, 30 days future).
Key features
The Payroll Digests API supports:
- Multi-company lookupβ request payroll digest data for up to 25 companies in a single batch.
- Asynchronous processingβsubmission and fulfillment are decoupled, so requests do not time out regardless of how much data needs to be computed.
- Standardized async patternβPOST to submit, GET to poll, with the same status lifecycle, idempotency contract, and result envelope as our other async batch APIs.
- Partial successβcompanies that return data successfully appear in
results; companies that could not be processed appear inexclusionswith acategoryandmessage. A single bad UUID never fails the whole batch. Duplicate UUIDs will be listed inexclusions. - System-level authenticationβuses your partner application's system access token, the same token used for provisioning and bulk onboarding status.
- Idempotent submissionβduplicate POSTs with the same
idempotency_keyreturn the original batch without triggering a second computation. - Fixed date windowβeach digest covers a consistent rolling window (-7 days/+30 days). Filtering, sorting, and pagination are handled client-side.
Process overview
Step 1: Generate an idempotency_key
Generate a unique UUID for each new request. The key is unique per (idempotency_key, partner_id). If the same key arrives a second time, Gusto returns the existing batchβit will not recompute.
Step 2: Submit the request
Make a single POST to /v1/payroll_digests, authenticated with your system access token and the payroll_digests:read OAuth scope.
POST /v1/payroll_digests
Authorization: Bearer {system_access_token}
X-Gusto-API-Version: 2024-04-01
Content-Type: application/json
{
"idempotency_key": "8a1f2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"batch_action": "fetch",
"batch": [
{ "entity_type": "company", "uuid": "5df911a0-..." },
{ "entity_type": "company", "uuid": "9c128b3e-..." },
{ "entity_type": "company", "uuid": "a7f04d12-..." }
]
}Each entry in batch references an existing company by its Gusto uuid. Companies must be mapped to your partner applicationβGusto enforces this server-side, and unmapped companies are returned in exclusions.
Step 3: Receive the batch acknowledgement
Gusto validates authorization and the request body, creates a batch record, and enqueues the computation.
Response codes:
201 / CreatedBatch accepted and created. Use the returned uuid for all subsequent polling.409 / ConflictDuplicateidempotency_key. Existing batchuuidis returned β not recomputed.422 / Unprocessable EntityRequest body validation error (e.g. malformed JSON, missing required fields).
A 201 response looks like the following:
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"idempotency_key": "8a1f2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"batch_action": "create",
"status": "pending"
}Step 4: Async processing
Gusto fetches payroll data for all requested companies in the background.
Step 5: Poll for results
Poll the GET endpoint with the uuid returned on submission until the batch reaches a terminal status (completed or failed).
GET /v1/payroll_digests/{batch_uuid}
Authorization: Bearer {system_access_token}
X-Gusto-API-Version: 2024-04-01
Content-Type: application/jsonResponse codes:
200 / OKBatch found. Batch states:pending,processing,completed,failed404 / UnknownUnknown batchuuid410 / GoneBatch was processed, but results have expired. Re-submit.
The batch goes through this lifecycle:
pending β processing β completed | failed
Importantβcompleted β "everything succeeded".The batch status reports the lifecycle of the work, not the per-company outcome. A completed batch may include companies in exclusions, or per-company
partial_successentries inside results.Always inspect the arrays to determine what actually came back.
Response while processing:
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"submitted_at": "2026-04-01T14:30:00Z",
"submitted_items": 4,
"processed_items": 0,
"excluded_items": 0,
"results": [],
"exclusions": []
}Step 6: Read the results
Once the batch is in a terminal state, the GET response contains the full digest inline. Example shape:
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"idempotency_key": "8a1f2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"status": "completed",
"submitted_at": "2026-04-01T14:30:00Z",
"completed_at": "2026-04-01T14:30:12Z",
"submitted_items": 4,
"processed_items": 2,
"excluded_items": 2,
"results": [
{
"idx": 0,
"entity_type": "company",
"uuid": "company-uuid-1",
"name": "Acme Corporation",
"status": "success",
"payrolls": [
{
"payroll_uuid": null,
"payroll_type": "regular",
"display_title": "Run biweekly payroll",
"auto_pilot": false,
"status": "ready_to_start",
"blockers": [
{
"type": "missing_bank_account",
"description": "Company bank account not set up"
}
],
"pay_period": {
"start_date": "2026-03-16",
"end_date": "2026-03-29",
"check_date": "2026-04-03",
"run_payroll_by": "2026-03-31"
},
"pay_schedule": {
"uuid": "schedule-uuid-1",
"frequency": "Every other week",
"custom_name": "Custom - every 1st and 15th"
},
"totals": null
},
{
"payroll_uuid": "payroll-uuid-1",
"payroll_type": "regular",
"display_title": "Run biweekly payroll",
"auto_pilot": true,
"status": "submitted",
"blockers": [],
"pay_period": {
"start_date": "2026-03-02",
"end_date": "2026-03-15",
"check_date": "2026-03-20",
"run_payroll_by": "2026-03-17"
},
"pay_schedule": {
"uuid": "schedule-uuid-1",
"frequency": "Every other week",
"custom_name": "Custom - every 1st and 15th"
},
"totals": {
"total_debit_amount": "15234.56",
"net_pay": "11456.78",
"total_employer_cost": "16890.12"
}
}
]
},
{
"idx": 3,
"entity_type": "company",
"uuid": "company-uuid-2",
"name": "Widget Inc",
"status": "success",
"payrolls": []
}
],
"exclusions": [
{
"idx": 1,
"entity_type": "company",
"uuid": "company-uuid-3",
"status": "failed",
"category": "not_found",
"message": "Company not found."
},
{
"idx": 2,
"entity_type": "company",
"uuid": "company-uuid-4",
"status": "failed",
"category": "company_inactive",
"message": "Company is inactive or not fully onboarded."
}
]
}
About totalspay totals are only included when calculated_at is present on the underlying payroll. If a payroll's totals have not been calculated yet, totals will be omitted.
Response fields
Top-level response fields
uuidβ The batch UUID returned on POST.idempotency_keyβ The key supplied on POST, echoed back.statusβ Batch lifecycle:pending,processing,completed, orfailed.submitted_atβ When the POST was received.completed_atβ When processing finished.nulluntil terminal.submitted_itemsβ Total companies in the original request.processed_itemsβ Companies that returned a result (entries inresults).excluded_itemsβ Companies that could not be processed (entries inexclusions).resultsβ One entry per company Gusto attempted, including companies with no payrolls in window.exclusionsβ One entry per company that could not be looked up or processed.
Per-company entries in `results[]`
idxβ Zero-based position from the original batch array β use to correlate back to the input.entity_typeβ Always "company" for this API.uuidβ The company UUID you submitted.nameβ Company business name.statusβ Per-company outcome: success, partial_success, or failed.payrollsβ Array of payroll digest entries within the date window. Empty when the company has no payrolls in the window.
Per-payroll entries in `payrolls[]`
payroll_uuidβ UUID of the persisted payroll, or null for upcoming payrolls that haven't been started yet (ready_to_start). Use this UUID with existing per-payroll endpoints once it's populated.payroll_typeβ regular, new_hire, termination, bonus, etc.display_titleβ Human-readable label (e.g. "Run biweekly payroll").auto_pilotβ Boolean β whether AutoPilot is enabled for this payroll.statusβ Payroll status: ready_to_start, in_progress, submitted, completed, failed.blockersβ Array of reasons the payroll cannot be submitted. Empty array when none. Each entry has type and description.pay_periodβ Object with start_date, end_date, check_date, run_payroll_by.pay_scheduleβ Object with uuid, frequency, and custom_name (if any).totalsβ Object withtotal_debit_amount,net_pay,total_employer_costβ ornullif the payroll has not been calculated, or the calculation is stale.
Entries in `exclusions[]`
idxβ Zero-based position from the original batch array.entity_typeβ Always "company" for this API.uuidβ The company UUID you submitted.statusβ Always "failed" for exclusion entries.categoryβ Reason classification β e.g.not_found,company_inactive.messageβ Human-readable description of why the company was excluded.
Reasons for exclusions
- incorrect UUID
- Company is not mapped to your partner application
- Company exists and is mapped, but is currently inactive.
Status vocabularies
Two distinct status vocabularies appear in the response. Don't conflate them.
Batch status (top-level)
Reports the lifecycle of the batch request itself.
pendingβ Request accepted, processing has not started yet.processingβ Data is being fetched for one or more companies.completedβ Processing finished. Inspect results and exclusions for outcomes.failedβ Request failed (e.g., internal error). Can be retried.
Per-company status (inside results[] and exclusions[])
results[] and exclusions[])Reports the outcome for an individual company.
successβ The company's digest was fetched successfully.partial_successβ Some of the company's data could not be fetched. Partial data is returned.failedβ Results could not be fetched for this company. Appears in exclusions.
FAQ
How do I correlate results back to the companies I requested?
Each entry in both results and exclusions includes the original company.uuid you submitted, along with idx, the zero-based position from your request batch array. Every UUID you submit appears in exactly one of the two arrays.
What does "partial success" mean for this API?
partial_success is reported at the batch level when some companies returned data successfully and others ended up in exclusions. Per-company status is either success or failed. Unlike the People Batch API, an individual company is never reported as partial_success β Gusto either has digest data for the company or it doesn't.
What OAuth scopes are required?
You need the payroll_digests:read scope on your system access token. This scope is gated per partner application; reach out to your Gusto Embedded contact to enable it.
How fresh is the data?
Each POST triggers a fresh computation. The digest reflects the state of payroll at the moment your request is processed. There is no caching between requests β if you need updated data, submit a new batch.
How long are results available?
Results are stored ephemerally and expire after a TTL (currently 24 hours from completion). Fetch results promptly after the batch reaches a terminal state. After expiry, GET returns 410 Gone and you'll need to re-submit the request.
How do I handle partners with more than 25 companies?
Submit multiple non-overlapping batches β for example, three batches of 25 companies each for a 75-company firm. Each batch is processed independently and has its own uuid. Results can be merged on your side.
What's the date window?
The digest covers a fixed rolling window β roughly a week in the past through one to two months in the future. Server-side filtering, sorting, and pagination are not supported; filter and sort on your side after reading the response.
Should I poll or use a webhook?
Currently polling is the only supported delivery mechanism. Poll the GET endpoint every few seconds until the batch reaches success, partial_success, or failed.
Can the same idempotency_key be reused?
No. Each idempotency_key is unique per partner application. Reusing a key returns the original batch β it will not trigger a new computation, even if the company list has changed. Generate a fresh UUID for every new request.
What if a company is mapped but the request still fails for it?
If Gusto encounters an error while fetching data for a specific company, that company is returned in exclusions with a descriptive reason_code and message. The rest of the batch completes normally.
Updated about 2 hours ago