GuidesAPI ReferenceChangelogAPI StatusAPI PolicyGusto Security
Guides

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 in exclusions with a category and message. A single bad UUID never fails the whole batch. Duplicate UUIDs will be listed in exclusions.
  • 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_key return 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 / Created Batch accepted and created. Use the returned uuid for all subsequent polling.
  • 409 / Conflict Duplicate idempotency_key. Existing batch uuid is returned β€” not recomputed.
  • 422 / Unprocessable Entity Request 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/json

Response codes:

  • 200 / OK Batch found. Batch states: pending,processing, completed , failed
  • 404 / Unknown Unknown batch uuid
  • 410 / Gone Batch 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_success entries 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 totals

pay 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, or failed.
  • submitted_at β€” When the POST was received.
  • completed_at β€” When processing finished. null until terminal.
  • submitted_items β€” Total companies in the original request.
  • processed_items β€” Companies that returned a result (entries in results).
  • excluded_items β€” Companies that could not be processed (entries in exclusions).
  • 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 with total_debit_amount, net_pay, total_employer_cost β€” or null if 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[])

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.