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
- Create a batch with a
typeand its settings. - Add items, one per request. Each item is checked when you add it.
- Optionally, read the batch summary to preview what it will create and update.
- Process the batch. Wingspan works through the items in the background.
- Check the batch until it finishes, then read the items that failed.
Batch types
type | Each item creates or updates |
|---|---|
PayeeImport | A 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. |
PayableImport | A 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 status | Meaning |
|---|---|
Created | Accepting items. |
Pending | Submitted for processing and waiting to start. |
Processing | Items are being processed. |
Completed | Processing finished. Individual items can still have failed. |
Failed | The 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:
| Field | Meaning |
|---|---|
processingStrategy | Required. Single creates one payable per item. Merge combines each payee's items into one payable. An item's payableItemMergeKey overrides that grouping. |
payableStatus | The status for new payables: Created (the default), Opened, Paid, or Cancelled. An item can override it. |
payerApprovalStatus | The 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:
| Field | Meaning |
|---|---|
payeeEngagementId | Required. The payee engagement the payable is issued under. |
uniqueReferenceKey | Required. Your key for this item, unique within the batch (1 to 256 characters). |
dueDate | Required. YYYY-MM-DD. |
totalCost, or quantity and unitCost | The amount, in the payer Account's payables currency. When both forms are sent, quantity times unitCost is used. |
description, detail, notes | Line item and payable text. |
lineItemType | Reimbursement for a reimbursement line. Leave it out for a standard line. |
payableStatus, payerApprovalStatus | Override the batch defaults for this item. |
paidDate | For payables recorded as already paid. |
payableItemMergeKey | With the Merge strategy, items that share a key become one payable. |
attachmentFileId | A vault file to attach to the payable. See Files and documents. |
customFields | Line 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
Completedbatch can contain failed items. Always list the failed items and handle eachstatusReason. 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:
| Field | Meaning |
|---|---|
context | Required. The kind of worker every new payee in the batch is created as: Contractor or Employee. |
engagementId | Optional. 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
| Task | Endpoint |
|---|---|
List batches of one type (filter[type][eq] is required) | GET /v3/platform/batches |
| Read a batch | GET /v3/platform/batches/{batchId} |
| Read one item | GET /v3/platform/batches/{batchId}/items/{itemId} |
| Remove an item | DELETE /v3/platform/batches/{batchId}/items/{itemId} |
| Delete a batch | DELETE /v3/platform/batches/{batchId} |
Smaller bulk endpoints
Two endpoints handle up to 100 records in a single request, without a batch:
| Task | Endpoint | Response |
|---|---|---|
| Send invitations to existing payees | POST /v3/payments/payees/bulk-invite with payeeIds | The number accepted, and a rejected list with a reason for each payee that wasn't invited |
| Add payees to a group | POST /v3/payments/groups/{groupId}/members/bulk with members | 202 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
| V1 | V3 |
|---|---|
| Bulk collaborator batch | PayeeImport batch |
| Bulk payable batch | PayableImport batch |
Batch status Open then Pending | Created, then process moves it to Pending |
| Processing strategy set on the batch | configuration.processingStrategy, fixed at creation |
bulkPayableItemReference and 208 Already Reported | uniqueReferenceKey (unique within the batch) and Idempotency-Key on each add |
Item errors in metadata.errorMessage | Item status: Failed and statusReason |
labels.bulkBatchId on created records | filter[batchIds][eq] on the search rows |
Related pages
Updated 10 days ago