Bulk payables

Import many payables at once with a Wingspan V3 PayableImport batch, choose Single or Merge processing, fix rows, and check results.

This guide shows you how to create many payables in one job with a PayableImport batch: create the batch, add one item per row, process it, and check which rows succeeded. It replaces V1's CSV upload to /payments/bulk/payable/batch.

Bulk import creates payables. It doesn't pay them. After the import, pay the payables through a payroll run or one at a time.

When to use a batch

Use a batch when you have many payables to create from a spreadsheet, timesheet system, or ERP export. Use single payables when each payment needs its own review or you only create a few at a time.

A batch gives you one place to validate all the rows, see totals before anything is created, and read back per-row results. For how batches work across the API, see Batches and bulk operations.

Processing strategies

Pick a strategy when you create the batch. You can't change it later.

StrategyWhat it createsUse it when
SingleOne payable per item.Each row is its own payment.
MergeOne payable per payee, with each of that payee's items as a line item. Items with the same payableItemMergeKey are combined into one payable instead.You're paying a firm for several people's work, or several rows belong on one invoice.

Before you begin

  • The payeeEngagementId for every payee in the file. List them with GET /v3/payments/payee-engagements. Payees must already exist; create them first with a PayeeImport batch or one at a time. See Invite a payee.
  • A unique key per row, such as your timesheet or invoice ID. It goes in uniqueReferenceKey and must be unique within the batch.
  • Any attachment already uploaded to the vault, so you have its file ID. See Files and documents.
  • Values for any line-item custom fields your Account marks as required.

Step 1: Create the batch

Call POST /v3/platform/batches. Idempotency-Key is required.

curl -X POST "https://api.wingspan.app/v3/platform/batches" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: nw-import-2026-09-w4" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "PayableImport",
    "name": "September week 4 hours",
    "externalId": "NW-TS-2026-09-W4",
    "configuration": {
      "processingStrategy": "Single",
      "payableStatus": "Opened",
      "payerApprovalStatus": "Approved"
    }
  }'
// (trimmed)
{
  "id": "Bt8Kq2Xw5Mz1Rn7Lv3Pc9T",
  "type": "PayableImport",
  "status": "Created",
  "configuration": { "processingStrategy": "Single", "payableStatus": "Opened", "payerApprovalStatus": "Approved" },
  "events": { "createdAt": "2026-09-24T16:00:00Z" }
}

configuration.payableStatus is the default status for created payables: Created (the default), Opened, Paid, or Cancelled. Each item can override it. payableStatus: Opened with payerApprovalStatus: Approved creates payables that are ready for the next payroll run, which replaces V1's "set status to Open to approve immediately". Leave both out if your team reviews each payable before approving.

Step 2: Add one item per row

Call POST /v3/platform/batches/{batchId}/items once per row. Each request carries exactly one item, so a bad row is rejected on its own and the rest of the batch is untouched.

curl -X POST "https://api.wingspan.app/v3/platform/batches/Bt8Kq2Xw5Mz1Rn7Lv3Pc9T/items" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "TS-88213",
    "data": {
      "payeeEngagementId": "Tz3Nq8KpW1vXr6Ld0Ya5Mc",
      "uniqueReferenceKey": "TS-88213",
      "dueDate": "2026-10-02",
      "description": "Route coverage, week 39",
      "quantity": 38,
      "unitCost": 46.00,
      "notes": "Approved by dispatch"
    }
  }'
// (trimmed)
{ "id": "It3Lx9Qv6Wz2Kn8Rt1Mb5P", "batchId": "Bt8Kq2Xw5Mz1Rn7Lv3Pc9T", "status": "Created", "result": null }

Item fields (V1 CSV column in parentheses):

FieldRequiredWhat it does
payeeEngagementIdYesThe engagement the payable is issued under. Replaces V1's email, collaborator ID, and collaborator external ID columns.
uniqueReferenceKeyYesYour key for the row, unique within the batch.
dueDateYesYYYY-MM-DD. (Due Date)
descriptionNoLine item description. (Line Item Title)
detailNoSecondary line text. (Line Item Description)
totalCost, or quantity and unitCostNoThe amount. If you send all three, quantity times unitCost wins. (Amount)
lineItemTypeNoOnly Reimbursement is supported. Omit it for a standard line. (Reimbursable)
notesNoPayable notes. (Invoice Notes)
attachmentFileIdNoA file to attach to the payable. (Attachment ID)
payableStatus, payerApprovalStatusNoOverride the batch defaults for this row.
paidDateNoFor rows imported with payableStatus: Paid.
payableItemMergeKeyNoMerge strategy only. Rows with the same key become one payable.
customFieldsNoLine-item custom field values keyed by the custom field's key.

Amounts are in your Account's payables currency; items don't carry their own currency. A negative amount on a row whose status isn't Paid creates a deduction against the payee instead of a payable.

Fixing a row before you process

A batch accepts changes only while it's Created. To fix a row, delete it with DELETE /v3/platform/batches/{batchId}/items/{itemId} and add the corrected row. There's no endpoint to edit an item in place, which replaces V1's "update a line item in a bulk payable" flow.

List what's in the batch with GET /v3/platform/batches/{batchId}/items.

Step 3: Check totals before you process

GET /v3/platform/batches/{batchId}/summary with expand=Breakdown shows what the import will create. Compare it with your source file before you commit.

// (trimmed)
{
  "batchId": "Bt8Kq2Xw5Mz1Rn7Lv3Pc9T",
  "progress": { "total": 212, "completed": 0, "failed": 0 },
  "breakdown": {
    "payablesCount": 210,
    "payablesAmount": 318420.00,
    "deductionsCount": 2,
    "deductionsAmount": 150.00,
    "netAmount": 318270.00,
    "payeesImpactedCount": 187,
    "newPayablesCount": 210,
    "updatedPayablesCount": 0,
    "newDeductionsCount": 2,
    "updatedDeductionsCount": 0
  }
}

Step 4: Process the batch

Call POST /v3/platform/batches/{batchId}/process. Idempotency-Key is required.

curl -X POST "https://api.wingspan.app/v3/platform/batches/Bt8Kq2Xw5Mz1Rn7Lv3Pc9T/process" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: nw-import-2026-09-w4-process"

The batch moves to Pending, then Wingspan processes it in the background through Processing to Completed or Failed. A batch that's no longer Created returns 400.

Batch statusMeaning
CreatedAccepting items.
PendingSubmitted, waiting to start.
ProcessingItems are being created.
CompletedProcessing finished. Check individual items; some can still have failed.
FailedProcessing stopped.

Step 5: Check results

Poll GET /v3/platform/batches/{batchId}/summary until progress.completed + progress.failed equals progress.total, then list failed rows:

curl -g "https://api.wingspan.app/v3/platform/batches/Bt8Kq2Xw5Mz1Rn7Lv3Pc9T/items?filter[status][anyOf][]=Failed&page[size]=100" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
// (trimmed)
{
  "data": [
    {
      "id": "Pc3Wn8Kz5Qt1Rx7Lv2Mb9G",
      "status": "Failed",
      "statusReason": "...",
      "externalId": "TS-88240",
      "data": { "uniqueReferenceKey": "TS-88240", "payeeEngagementId": "Eg1Tn6Wq3Zx9Kv4Lr7Pm2D" }
    }
  ],
  "pagination": { "nextPageToken": "" }
}

Each Completed item's result holds the created payableId (or deductionId). Page through with page[token] until nextPageToken is "". See Pagination.

Batch webhooks aren't subscribable yet, so poll the summary or re-read the batch.

Common mistakes

  • Treating Completed as "every row worked". A completed batch can contain failed items. Always list failed items.
  • Reusing uniqueReferenceKey. Keys must be unique within a batch. Use the ID from your source system so you can match results back.
  • Expecting negative amounts to reduce a payable. A negative row that isn't Paid becomes a separate deduction.
  • Forgetting the payee must be eligible. Rows for payees with incomplete requirements can't be opened. Check engagement eligibility first. See Find incomplete payables.
  • Building one huge payable. Very large payables are harder to review and reconcile. Prefer Single, or Merge with a merge key that matches how you want invoices grouped.

Related pages


Did this page help you?