GuidesAPI ReferenceChangelogAPI StatusAPI PolicyGusto Security
Guides

Batch Payroll Cancellation API

Cancel multiple payrolls for companies

The Batch Payroll Cancellation API provides partners with a single async API call to cancel multiple payrolls across one or more client companies in a single request.

Typical use cases include:

  • payrolls could not be funded.
  • payrolls cancelled for other reasons.

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.

📘

Gusto asynchronously changes the status of the given payrolls to "cancelled".

Key features

The Payroll Cancellation API supports:

  • Multi-company operations — cancel up to 100 payrolls for companies in a single batch.
  • Asynchronous processing — submission and fulfillment are decoupled, so requests do not time out regardless of how much work is processed.
  • 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.
  • Success or Failure — payrolls for companies that are accessible successfully appear in results; companies or payrolls 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. This API does not support partial_success.
  • 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.

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_batches, and "batch_action": "cancel", authenticated with your system access token and the payroll_batches:write OAuth scope.

Max 100 payrolls per request.

Submission-time exclusions are limited to duplicates and not-found / not-owned-by-partner. No slow per-payroll pre-validation — cancellability is evaluated asynchronously per payroll.

POST /v1/payroll_batches
Authorization: Bearer {system_access_token}
X-Gusto-API-Version: 2026-06-15
Content-Type: application/json

{
  "idempotency_key": "<partner-generated uuid>",
  "batch_action": "cancel",
  "batch": [
    { "entity_type": "payroll",
			"uuid": "a1c4e6f8-3d2b-4a9c-8e1f-5b7d0c2a4e69",
      "company_uuid": "9b2d4f6a-8c1e-4d3b-a5f7-1e9c0b3d5a82"
		},
    { "entity_type": "payroll",
			"uuid": "c7f2b9d4-6e3a-4b1c-9d8f-0a2e5c7b1f43",
      "company_uuid": "9b2d4f6a-8c1e-4d3b-a5f7-1e9c0b3d5a82"
    },
    { "entity_type": "payroll",
			"uuid": "b6f1c3e9-4a2d-4e8b-9c7f-1a5d0b2e6c34",
      "company_uuid": "3e8a1d5c-9f2b-4a7e-b1c4-6d0f2a9e3b58"
		},
    { "entity_type": "payroll",
			"uuid": "d2a7e5f1-8c3b-4d6a-a9e2-7f0c1b4d8a63", 
      "company_uuid": "5c9e2a7d-1f4b-4e6c-9a3d-8b0f2c5e1d47"
		}
  ]
}

Each entry in batch references an existing payroll for an existing company — both by their Gusto uuid. Companies must be mapped to your partner application—Gusto enforces this server-side, and unmapped companies are returned in exclusions. Payrolls must be in the correct state to be cancelled, otherwise they 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, processing enqueued. Use the returned uuid for all subsequent polling.
  • 409 / Conflict Duplicate idempotency_key. Existing batch uuid is returned — no duplicate work.
  • 422 / Unprocessable Entity Request body validation error (e.g. malformed JSON, missing required fields, > 100 items, batch bad_action).

A 201 response looks like the following:

{
  "uuid": "f0b3d8a6-5e1c-4f2d-8a9b-4c6e2d0f7a15",
  "idempotency_key": "<partner-generated uuid>",
  "batch_action": "cancel",
  "status": "pending"
}

Step 4: Async processing

Gusto looks up payrolls 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). This requires the payroll_batches:read OAuth scope.

  GET /v1/payroll_batches/{batch_uuid}
  Authorization: Bearer {system_access_token}
  X-Gusto-API-Version: 2026-06-15
  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:

pendingprocessingcompleted | 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.

Always inspect the arrays to determine what actually came back.

Response while processing:

{
  "uuid": "f0b3d8a6-5e1c-4f2d-8a9b-4c6e2d0f7a15",
  "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": "f0b3d8a6-5e1c-4f2d-8a9b-4c6e2d0f7a15",
  "idempotency_key": "...",
  "batch_action": "cancel",
  "status": "pending | processing | completed | failed",
  "submitted_at": "2026-04-01T14:30:00Z",
  "completed_at": null,
  "submitted_items": 4,
  "processed_items": 3,
  "excluded_items": 1,
  "results": [
    // per-ITEM status: success | failed
    // (atomic cancel, no sub-tasks → no partial_success)
    { 
      "idx": 0, 
      "uuid": "a1c4e6f8-3d2b-4a9c-8e1f-5b7d0c2a4e69", 
      "status": "success"
    },{
      "idx": 1, 
      "uuid": "c7f2b9d4-6e3a-4b1c-9d8f-0a2e5c7b1f43", 
      "status": "failed",
      "errors": [ { "error_key": "base", "category": "not_cancellable", "message": "..." } ] 
    },{ 
      "idx": 3, 
      "uuid": "d2a7e5f1-8c3b-4d6a-a9e2-7f0c1b4d8a63", 
      "status": "success" 
    }
  ],
  "exclusions": [
    { "idx": 2, 
      "entity_type": "payroll", "uuid": "b6f1c3e9-4a2d-4e8b-9c7f-1a5d0b2e6c34",
      "company_uuid": "3e8a1d5c-9f2b-4a7e-b1c4-6d0f2a9e3b58",
		  "status": "failed", "category": "not_found", "message": "..."
    }
  ]
}

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 — Does not apply for this API.
  • 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 and payroll.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 OAuth scopes are required?

You need the payroll_batches:read and payroll_batches:write scopes on your system access token. This scope is gated per partner application; reach out to your Gusto Embedded contact to enable it.

How do I handle if I need to cancel more than 100 payrolls?

Submit multiple non-overlapping batches — for example, two batches of 100 payrolls. Each batch is processed independently and has its own uuid. Results can be merged on your side.

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 completed 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 provided list has changed. Generate a fresh UUID for every new request.

What are exclusions?

Exclusions lists items (payrolls) that you wanted to cancel, but either the company uuid that you provided is not mapped to your partner app, or the payroll might be in a state where it can not be cancelled. If Gusto encounters such errors while processing your request, that payroll is returned in exclusions with a descriptive category and message. The rest of the batch completes normally.



Did this page help you?