Add contractors and off-platform payments
Get every 1099 recipient and every payment made outside Wingspan into Wingspan, one at a time or in bulk, in the web app or the V3 API.
Use this page to make sure every contractor who should receive a 1099 is in Wingspan, and every payment you made to them outside Wingspan counts toward their total. You can do both in the web app or through the V3 API. Contractors are called Payees in the V3 API (collaborators in V1).
You need this when you moved to Wingspan partway through the year, paid contractors through other systems, or use Wingspan mainly to file 1099s.
For the 2025 tax year, you could run imports as many times as you needed until the January 25, 2026 filing deadline.
Add contractors in the web app
One contractor at a time
- Go to Contractors and click Add Contractor.
- Enter the contractor's email and name. Use an email the contractor reads: Wingspan sends the invitation there.
- Click Add contractor. Wingspan emails the contractor an invitation to confirm their tax information.
- Optional: Enter W-9 information if you already collected it. Wingspan verifies the TIN automatically.
Each contractor needs a unique email (unless they're assigned to different organization accounts), a unique External ID, and a unique TIN. Anyone with access to the organization can add an individual contractor; admin rights aren't required.
If the contractor doesn't respond to Wingspan's W-9 request, Wingspan uses the W-9 information you entered. If neither of you provided it, the 1099 can't be filed. Even when you enter W-9 details, Wingspan still asks the contractor to confirm them and choose how to receive their 1099.
Many contractors at once
Only administrators can run bulk imports.
- From the Contractors list, open Actions (the three dots) and choose Bulk upload contractors. There are also shortcuts on the 1099 filing dashboard.
- Choose the engagement for the whole upload. Engagement isn't a column: Your choice applies to every row.
- Upload a CSV or Excel file (XLS, XLSX), UTF-8 encoded. There's no limit on file size or row count. One row is one contractor.
Columns:
| Column | Required | Notes |
|---|---|---|
| Wingspan User ID | One identifier required | Highest priority. Only used to update an existing contractor; ignored with a warning when creating. |
| External ID | One identifier required | Your own ID for the contractor. |
| Email Address | One identifier required | Unique and valid. A personal email works best. |
| First Name, Last Name | No | If omitted, the contractor's email shows on their profile until they finish onboarding. |
| Business Name (Legal Name) | No | |
| Custom fields | No | Values must match each field's data type. |
| SSN or EIN | Required if you provide W-9 data | One, not both. Hyphens are optional. |
| W-9 Legal Business Name | Required if you provide W-9 data | Must match IRS records. |
| Address Line 1, City, State, Postal Code, Country | Required if you provide W-9 data | No P.O. boxes. Country defaults to US. State is a two-letter code for the US. |
| Federal Tax Classification | Required if you provide W-9 data | One of: Sole proprietorship, S-corp, C-corp, Partnership, LLC (single member), LLC (Taxed as S-corp), LLC (Taxed as C-corp), LLC (Taxed as Partnership). Must fit the TIN type. |
Names and business names accept A to Z, 0 to 9, hyphens, and ampersands. Contractors can't select Trust or Estate as a classification. The federal tax classification is used for internal processing only; Form 1099 has no field for it.
How rows are matched:
- Wingspan looks for an existing contractor by Wingspan User ID, then External ID, then email.
- No match: The row creates a contractor (create mode) and Wingspan invites them.
- A match: The row updates the contractor (update mode). Only custom fields, External ID, payer-provided W-9 details, and the payer-provided name or company name can change. An empty cell clears that field. Other fields, including email, are ignored if the contractor has already signed in.
- If External ID and email point to different contractors, or the Wingspan User ID conflicts with either, the row fails. To fix a wrong pairing, remove the External ID from the wrong profile and upload again.
- An existing contractor's organization account can't be changed by upload.
- Updating an archived contractor reactivates them without sending another email.
- Each email can be linked to only one TIN.
Wingspan validates each row. A row with any error is rejected as a whole, and you can download a CSV of the errors. Fix those rows and upload them again; successful rows don't need to be re-sent.
Add contractors through the API
Create one Payee
Create a payee with POST /v3/payments/payees. email and context are required. Use context: Contractor for a 1099 contractor or vendor.
curl -X POST https://api.wingspan.app/v3/payments/payees \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"email": "[email protected]",
"context": "Contractor",
"externalId": "VND-1042",
"profile": { "displayName": "Priya Shah" }
}'// 201 Created (trimmed)
{
"id": "kY9pF34Qy6nB3Wwd25rq4f",
"externalId": "VND-1042",
"context": "Contractor",
"status": "Activated",
"w9SourcePolicy": "PayeeSuppliedFallbackToPayer"
}Then send the invitation with POST /v3/payments/payees/{payeeId}/invite, or invite up to 100 Payees at once with POST /v3/payments/payees/bulk-invite.
A Payee doesn't need a linked Wingspan Account to be paid or to have payments recorded against it. See Payees and Invite a payee.
What can go wrong:
409 ResourceConflictwhen theexternalIdis already used by another Payee on your Account. Create isn't an upsert: find the existing Payee withGET /v3/payments/payees?filter[externalId][eq]=VND-1042.- A duplicate
emaileither returns409 ResourceConflict, or returns201with the existing Payee unchanged. Handle both.events.createdAttells you whether the Payee is new.
Supply W-9 information you already hold
In the API, tax identity lives on a ComplianceEntity, not on the Payee. You can attach a payer-supplied ComplianceEntity when you create the Payee (payerSuppliedComplianceEntityId). Wingspan uses it until the contractor shares their own verified tax information, depending on the Payee's w9SourcePolicy. See Tax information and Recipient information logic.
Import many Payees with a batch
Use a PayeeImport batch to create or update Payees in bulk. See Batches and bulk operations for the full lifecycle.
-
Create the batch.
configuration.contextis required:Contractorfor 1099 contractors and vendors. To place every Payee on one engagement, also setconfiguration.engagementId.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": "2025 contractor import", "configuration": { "context": "Contractor" } }'// 201 Created (trimmed) { "id": "S8iq9y7AjzQHb6BAEcn6zJ", "type": "PayeeImport", "status": "Created" } -
Add one item per Payee. Each item needs at least one of
payeeId,externalId, oremail. An existing Payee is matched bypayeeId, thenexternalId, thenemail, and updated. With no match, a new Payee is created and invited atemail.curl -X POST https://api.wingspan.app/v3/platform/batches/S8iq9y7AjzQHb6BAEcn6zJ/items \ -H "Authorization: Bearer $WINGSPAN_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "data": { "email": "[email protected]", "externalId": "VND-1043", "displayName": "Marcus Lee" } }' -
Start processing with
POST /v3/platform/batches/{batchId}/process(send anIdempotency-Key). The batch moves toPending, thenProcessing, thenCompletedorFailed. -
Check progress with
GET /v3/platform/batches/{batchId}/summary, and read each item's result (thepayeeIdit created or updated) withGET /v3/platform/batches/{batchId}/items.
A PayeeImport item doesn't carry W-9 data. Supply payer-held tax information separately, as described above.
Add off-platform payments in the web app
Off-platform payments are payments you made outside Wingspan. Importing them puts them in your 1099 totals. Imported payments are visible to your contractors.
Before you start, add every contractor who should receive a 1099. Only administrators can bulk import payments.
- From the Payables list, open Actions (the three dots) and choose Bulk upload payables. There are also shortcuts on the 1099 filing dashboard. Click Download template to get a spreadsheet pre-filled with your contractors.
- Fill in the rows and upload the file (CSV or Excel).
- Review the payments, then upload.
| Column | Required | Notes |
|---|---|---|
| Email or external collaborator ID | Yes | Identifies the contractor. |
| Amount (USD) | Yes | For example 100 or -50. |
| Line Item Title | Yes | Visible to the contractor. If you have no title, use something identifying, like an invoice number. |
| Pay Date | Yes | Decides which tax year the payment counts in, for example 2025-06-01. |
| Reimbursable | No | True or False. |
| Line Item Detail | No | Extra description visible to the contractor. |
| Due Date | No | For reference only. |
You can upload itemized rows (one row per payment, recommended so contractors can see which payments are on their 1099) or one summary row per contractor. For a summary row, use any single date in the tax year as the Pay Date, for example 2025-01-01.
Imported payments show as payables paid outside Wingspan. To include them in your 1099 totals, generate 1099 amounts and statuses after the import, with off-platform payments turned on in your calculation settings.
Add off-platform payments through the API
Record one payment
Create the payable, then record that you paid it outside Wingspan. No money moves.
-
Create a payable for the Payee.
curl -X POST https://api.wingspan.app/v3/payments/payables \ -H "Authorization: Bearer $WINGSPAN_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "payeeId": "kY9pF34Qy6nB3Wwd25rq4f", "currency": "USD", "dueDate": "2025-06-01", "lineItems": [ { "description": "June services (paid by check)", "totalCost": 1200.00 } ] }' -
Record the off-platform payment with
POST /v3/payments/payables/{payableId}/pay-off-platform.amount,paymentMethodDescription, andpaidDateare required.curl -X POST https://api.wingspan.app/v3/payments/payables/4A3DdvHyrNktBXtnjfObIN/pay-off-platform \ -H "Authorization: Bearer $WINGSPAN_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount": 1200.00, "paymentMethodDescription": "Check", "paidDate": "2025-06-01", "referenceNumber": "CHK-5521" }'// 200 OK (trimmed) { "id": "4A3DdvHyrNktBXtnjfObIN", "status": "PaidOffPlatform", "currency": "USD" }
What can go wrong:
409 EligibilityBlockedwithdetailCode: payments.EngagementNotPaymentsEligiblewhen the Payee's engagement has incomplete payment eligibility requirements. See Requirements and eligibility.409when the payable has collaborator splits. One off-platform payment can't reconcile split obligations.
Import many payments with a batch
Use a PayableImport batch. Set payableStatus to Paid in the configuration (or per item) and give each item a paidDate: the batch creates payables already recorded as paid on that date, and no money moves. Each item needs a payeeEngagementId, a uniqueReferenceKey that's unique within the batch, and a dueDate. Amounts are in your Account's payables currency.
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": "2025 off-platform payments",
"configuration": { "processingStrategy": "Single", "payableStatus": "Paid" }
}'curl -X POST https://api.wingspan.app/v3/platform/batches/f5AjxvUlKsiC_47wqaMl9X/items \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"data": {
"payeeEngagementId": "5zr3QA7YeEEBY3ABp3_e2z",
"uniqueReferenceKey": "2025-Q1-VND-1042",
"description": "Q1 services paid by wire",
"totalCost": 4000.00,
"dueDate": "2025-03-31",
"paidDate": "2025-03-31"
}
}'Find a Payee's engagement ID with GET /v3/payments/payees/{payeeId}/engagements. Set lineItemType to Reimbursement for reimbursable items, so your calculation settings can include or exclude them. A negative amount with a status other than Paid creates a Deduction instead of a payable.
Process the batch and check its summary the same way as a PayeeImport batch. GET /v3/platform/batches/{batchId}/summary?expand=Breakdown adds payable amounts and counts. See Bulk payables.
Next steps
Updated 10 days ago