Batches and bulk operations

Import payees or payables in bulk with Wingspan V3 batches, preview the result, process it, and check every item's outcome.

This page shows you how to create or update many payees or payables at once with a batch, preview what the batch will do, process it, and find any items that failed. It also lists the smaller bulk endpoints for inviting payees and adding them to groups.

A batch replaces the V1 bulk collaborator and bulk payable batches. You add items one request at a time, so each item is validated as you add it, and a bad item is rejected right away instead of failing the whole upload later.

How a batch works

  1. Create a batch with a type and its settings.
  2. Add items, one per request. Each item is checked when you add it.
  3. Optionally, read the batch summary to preview what it will create and update.
  4. Process the batch. Wingspan works through the items in the background.
  5. Check the batch until it finishes, then read the items that failed.

Batch types

typeEach item creates or updates
PayeeImportA payee. An existing payee is matched by payeeId, then externalId, then email, and updated. With no match, a new payee is created and invited at email.
PayableImportA payable, or a deduction when the amount is negative and the status isn't Paid.

Statuses

stateDiagram-v2
    [*] --> Created
    Created --> Pending: process
    Pending --> Processing
    Processing --> Completed
    Processing --> Failed
Batch statusMeaning
CreatedAccepting items.
PendingSubmitted for processing and waiting to start.
ProcessingItems are being processed.
CompletedProcessing finished. Individual items can still have failed.
FailedThe batch as a whole couldn't be processed.

Each item has its own status: Created until the batch is processed, then Completed or Failed.

Import payables

1. Create the batch

Create a batch of type PayableImport. Idempotency-Key is required.

curl -X POST "https://api.wingspan.app/v3/platform/batches" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "type": "PayableImport",
    "name": "April week 1 site visits",
    "externalId": "PAYRUN-2026-04-W1",
    "configuration": {
      "processingStrategy": "Merge",
      "payableStatus": "Opened",
      "payerApprovalStatus": "Approved"
    }
  }'
// (trimmed)
{
  "id": "Qm7tR2vXk9LpZ4wNc8HsYa",
  "type": "PayableImport",
  "status": "Created",
  "externalId": "PAYRUN-2026-04-W1",
  "configuration": {
    "processingStrategy": "Merge",
    "payableStatus": "Opened",
    "payerApprovalStatus": "Approved"
  },
  "events": { "createdAt": "2026-04-06T08:00:12Z" }
}

configuration for PayableImport:

FieldMeaning
processingStrategyRequired. Single creates one payable per item. Merge combines each payee's items into one payable. An item's payableItemMergeKey overrides that grouping.
payableStatusThe status for new payables: Created (the default), Opened, Paid, or Cancelled. An item can override it.
payerApprovalStatusThe payer approval for new payables. Approved together with payableStatus: Opened creates payables that are already approved.

A batch's type and configuration can't change after creation. You can update its name, notes, externalId, and metadata with update a batch. metadata is merged into the existing map. A metadata key the batch uses to hold its configuration is rejected with 422 ValidationError: payableStatus for PayableImport, and engagementId, payeeContext, and isV3Batch for PayeeImport.

2. Add items

Add a batch item for each payable. The body is one item, not an array:

curl -X POST "https://api.wingspan.app/v3/platform/batches/Qm7tR2vXk9LpZ4wNc8HsYa/items" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "externalId": "VISIT-88213",
    "data": {
      "payeeEngagementId": "Hn4dW8kPq2ZxM7vRt5LcJa",
      "uniqueReferenceKey": "VISIT-88213",
      "dueDate": "2026-04-10",
      "description": "Home visit, April 2",
      "quantity": 3,
      "unitCost": 85.00,
      "customFields": { "costCenter": "east-region" }
    }
  }'

data fields for PayableImport items:

FieldMeaning
payeeEngagementIdRequired. The payee engagement the payable is issued under.
uniqueReferenceKeyRequired. Your key for this item, unique within the batch (1 to 256 characters).
dueDateRequired. YYYY-MM-DD.
totalCost, or quantity and unitCostThe amount, in the payer Account's payables currency. When both forms are sent, quantity times unitCost is used.
description, detail, notesLine item and payable text.
lineItemTypeReimbursement for a reimbursement line. Leave it out for a standard line.
payableStatus, payerApprovalStatusOverride the batch defaults for this item.
paidDateFor payables recorded as already paid.
payableItemMergeKeyWith the Merge strategy, items that share a key become one payable.
attachmentFileIdA vault file to attach to the payable. See Files and documents.
customFieldsLine item custom field values by key. Every required line item custom field must be present. See Custom fields.

An item that doesn't match the batch's type, or fails validation, is rejected with 422 ValidationError and isn't added. Items can only be added while the batch is Created.

3. Preview the result

Before processing, read the batch summary (GET /v3/platform/batches/{batchId}/summary) with the breakdown to see what the batch will do:

curl -g "https://api.wingspan.app/v3/platform/batches/Qm7tR2vXk9LpZ4wNc8HsYa/summary?expand=Breakdown" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
{
  "batchId": "Qm7tR2vXk9LpZ4wNc8HsYa",
  "progress": { "total": 240, "completed": 0, "failed": 0 },
  "breakdown": {
    "payablesAmount": 61200.00,
    "payablesCount": 236,
    "deductionsCount": 4,
    "deductionsAmount": 340.00,
    "netAmount": 60860.00,
    "payeesImpactedCount": 118,
    "newPayablesCount": 236,
    "updatedPayablesCount": 0,
    "newDeductionsCount": 4,
    "updatedDeductionsCount": 0
  }
}

Before processing, the counts describe what the import will create and update. After processing, they describe what it did. Without expand=Breakdown, breakdown is null and you get only progress.

4. Process the batch

Call POST /v3/platform/batches/{batchId}/process:

curl -X POST "https://api.wingspan.app/v3/platform/batches/Qm7tR2vXk9LpZ4wNc8HsYa/process" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

The response is the batch with status: Pending. Idempotency-Key is required. Processing a batch that's no longer Created returns 409 ResourceConflict.

5. Check progress and results

Batches don't send webhook events yet, so check progress by reading the summary or the batch every few seconds until status is Completed or Failed:

curl "https://api.wingspan.app/v3/platform/batches/Qm7tR2vXk9LpZ4wNc8HsYa/summary" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"

Then list the batch items that failed:

curl -g "https://api.wingspan.app/v3/platform/batches/Qm7tR2vXk9LpZ4wNc8HsYa/items?filter[status][anyOf][]=Failed&page[size]=100" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
// (trimmed)
{
  "data": [
    {
      "id": "Ts9mQ4vK2xLp7WzR1nBcHd",
      "externalId": "VISIT-88251",
      "status": "Failed",
      "statusReason": "(why this item failed)",
      "result": null
    }
  ],
  "pagination": { "nextPageToken": "" }
}

Important: A Completed batch can contain failed items. Always list the failed items and handle each statusReason. The batch finishing doesn't mean every payable was created.

For completed items, result holds the created resource's ID: payableId or deductionId for PayableImport, and payeeId for PayeeImport. You can also look an item up by your own ID with filter[externalId][anyOf][]=VISIT-88251.

To retry failed items, fix the data and add them to a new batch.

Import payees

A PayeeImport batch works the same way. configuration is required and holds:

FieldMeaning
contextRequired. The kind of worker every new payee in the batch is created as: Contractor or Employee.
engagementIdOptional. Puts every payee in the batch on that engagement when their item completes. The engagement's type must agree with context (only an Employee engagement agrees with Employee), or the create fails with 422.
curl -X POST "https://api.wingspan.app/v3/platform/batches" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "type": "PayeeImport",
    "name": "Spring onboarding cohort",
    "configuration": { "context": "Contractor", "engagementId": "Vd3Kq8Wz1Nx6Lp4Rt9HcMa" }
  }'

An item that matches an existing payee keeps that payee's own context. It fails if engagementId doesn't agree with it. An item whose email matches an existing unlinked payee with a different context also fails. A failed item reads back with status: Failed and the conflict in statusReason. Read the batch's context from configuration.context, not from metadata.

Each item's data needs at least one of payeeId, externalId, or email, and can also carry displayName, doingBusinessAs, phone, and customFields (payee custom field values by key). Every required payee custom field must be present:

{
  "data": {
    "externalId": "CONTRACTOR-00417",
    "email": "[email protected]",
    "displayName": "Priya Shah",
    "customFields": { "region": "west" }
  }
}

With expand=Breakdown, the summary of a PayeeImport batch reports newPayeesCount and updatedPayeesCount.

Other batch tasks

Smaller bulk endpoints

Two endpoints handle up to 100 records in a single request, without a batch:

TaskEndpointResponse
Send invitations to existing payeesPOST /v3/payments/payees/bulk-invite with payeeIdsThe number accepted, and a rejected list with a reason for each payee that wasn't invited
Add payees to a groupPOST /v3/payments/groups/{groupId}/members/bulk with members202 with an async operation. Either every member is added or none are. Requires If-Match with the group's ETag and a recent MFA check.

From V1

V1V3
Bulk collaborator batchPayeeImport batch
Bulk payable batchPayableImport batch
Batch status Open then PendingCreated, then process moves it to Pending
Processing strategy set on the batchconfiguration.processingStrategy, fixed at creation
bulkPayableItemReference and 208 Already ReporteduniqueReferenceKey (unique within the batch) and Idempotency-Key on each add
Item errors in metadata.errorMessageItem status: Failed and statusReason
labels.bulkBatchId on created recordsfilter[batchIds][eq] on the search rows

Related pages


Did this page help you?