Payees
How a Payee works in the V3 API, why it doesn't need a linked Account to be paid, and how to create, update, and deactivate one.
A Payee is your record of a contractor or vendor you pay. This page explains what a Payee holds, how it relates to the payee's own Wingspan Account, and how to create, update, and deactivate one. Payees were called collaborators in the V1 API.
How a Payee fits with other resources
A Payee belongs to you, the payer. It's the durable record of one relationship: "Northwind Staffing pays Priya Shah." Everything you do with that person points at the Payee's id: engagements, requirements, payables, payout routing, and payment history.
| Resource | Who owns it | What it is |
|---|---|---|
Payee | The payer's Account | The payer's record of someone it pays. |
Payer | The payee's Account | The same relationship seen from the payee's side. |
Account | The contractor or company | A business identity in Wingspan. A contractor's own Account is tied to their login. |
Person | Nobody (a human) | A person who can sign in. |
PayerPayeeLinkRequest | Both sides can read it | The record that links a Payee to the payee's own Account. |
A Payee doesn't need a linked Account. You can create a Payee, put it on an engagement, and pay it before the contractor ever signs in. When the contractor accepts your invitation, Wingspan sets payeeAccountId on the Payee. The Payee's id never changes, so every engagement, payable, and payment you created before linking still points at the same record.
Key fields
| Field | What it means |
|---|---|
id | The Payee's ID. Use it everywhere; it stays the same before and after linking. |
accountId | Your Account (the owner of this record). Read-only. |
payeeAccountId | The contractor's own Account. Absent until the contractor accepts your invitation or you associate an Account. |
payerId | The ID of the matching Payer record on the payee's side. It may equal id. |
profile.email | The invitation address. Required on create. It lives inside profile; there is no top-level email field. |
context | Contractor or Employee: the kind of worker this Payee is to you. Required on create. It decides which engagement types the Payee can hold. See Worker context. |
externalId | Your own ID for this Payee (from your ERP, ATS, or HRIS). Unique per Account. Filterable. |
status | Activated or Deactivated. An administrative flag only (see below). |
linkRequestStatus | Pending, Linked, or Rejected. Linking progress, read-only. Absent when no link request exists. |
profile | Display information: displayName, doingBusinessAs, email, and phone. Stored and returned exactly as you sent them. Not the legal or tax identity. A detail read can also include logoUrl from the linked Account. |
payerSuppliedComplianceEntityId | A tax identity you supplied for this Payee before it linked. See Tax information. |
identitySource | PayerProvided or PayeeOwned. Whose tax identity is attached. Read-only. |
complianceEntitySourceStrategy | Automatic (default), PayerProvided, or PayeeProvided. Whose ComplianceEntity a read with ?expand=ComplianceEntity shows. See Tax information. |
defaults.isAutoPayEnabled | Your accounts-payable auto-pay default for this Payee. PayeeEngagements can override it with overrides.isAutoPayEnabled. This is separate from automatic collection of invoices a payee sends a client, which lives on the payee's Payer record (isAutomaticInvoiceCollectionEnabled). See Collect payment. |
defaults.verificationStrategy | All or None. Whether tax-information verification checks are required. |
defaults.paymentTermsDays | Net terms in days for payables to this Payee when dueDate is omitted. 0 means due on receipt. A PayeeEngagement override wins. Send null on update to clear it. |
canAcceptInvoicePayments, isAutoPayRequired | Read-only. Whether the payee's Account can be paid through its own invoices, and whether the payee requires you to enroll in auto-pay. |
defaultPaymentMethod, defaultPaymentMethodStatus, effectivePaymentMethod | The method you pay this Payee's invoices with, its stored status (Configured, Blocked, or NotSet), and what currently applies. Manage it with /v3/payments/payees/{payeeId}/default-payment-method. See Payout methods. |
quickbooksVendorId, quickbooksExpenseAccountId | Your accounting mappings for this Payee, as Wingspan reference IDs. Empty string clears. |
taxInformationShareStatus | Active or Revoked. Absent when the payee never shared tax information. |
w9SourcePolicy, payoutDestinationPolicy | Whose data Wingspan uses for the W-9 and for the payout destination: PayerSupplied, PayeeSupplied, or PayeeSuppliedFallbackToPayer (the default for new Payees). |
internalNotes | Notes only your team can see. |
events, actors | createdAt, updatedAt, activatedAt, deactivatedAt, contextChangedAt, and who did each. |
metadata | Up to 50 keys of your own data. |
Legal names, tax IDs, and addresses don't live on the Payee. They live on a ComplianceEntity, so identity data has one home and one verification history.
Status
status tells you whether the relationship is active in your account. It doesn't tell you whether the Payee accepted the invitation, and it doesn't tell you whether they can be paid.
| Status | Meaning | How you get there |
|---|---|---|
Activated | The relationship is active. Every new Payee starts here. | Create, or POST .../activate to reactivate. |
Deactivated | You've stopped working with this Payee. Future payments are blocked. The record stays readable. | DELETE /v3/payments/payees/{payeeId} |
stateDiagram-v2
[*] --> Activated: create
Activated --> Deactivated: DELETE
Deactivated --> Activated: POST /activate
Three separate things answer three separate questions:
- Is the relationship active? Read
status. - Has the contractor linked their Account? Read
linkRequestStatusandpayeeAccountId. - Can I pay them through this engagement? Read
paymentsEligibilityon the PayeeEngagement. See Requirements and eligibility.
Create a payee
POST /v3/payments/payees creates the record. It doesn't send an invitation, and it doesn't create an Account or Person. To invite, call POST /v3/payments/payees/{payeeId}/invite afterward (see Invite a payee).
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 '{
"externalId": "ATS-04821",
"context": "Contractor",
"profile": { "email": "[email protected]", "displayName": "Priya Shah" },
"metadata": { "region": "west" }
}'// 201 Created (trimmed)
{
"id": "M9ISYbzJElXs4zIHv76rjT",
"accountId": "z9uv0jPAxTqSLs5UKwv1DE",
"externalId": "ATS-04821",
"status": "Activated",
"context": "Contractor",
"profile": { "email": "[email protected]", "displayName": "Priya Shah" },
"events": { "createdAt": "2026-09-24T15:02:11Z" },
"metadata": { "region": "west" }
}profile.email and context are required. defaults, defaultPaymentMethod, address, profile.individual, and profile.company aren't accepted on create; payerSuppliedComplianceEntityId is rejected too (create a ComplianceEntity with the Payee as its subject instead). There's no linkRequestStatus and no payeeAccountId until you invite the Payee.
If your Account has a default Engagement, a new Contractor Payee is placed on it when you create the Payee. An Employee Payee is not.
Duplicates: externalId and email
Create is never an upsert.
- If
externalIdis already used by another Payee in your Account, you get409withcode: ResourceConflict. Look up the existing Payee withGET /v3/payments/payees?filter[externalId][eq]=ATS-04821. - If
profile.emailmatches an existing Payee in your Account, the call returns201with the existing Payee, reactivated if it wasDeactivated. Nothing you sent is applied, andevents.createdAtshows the original creation time.
If the existing Payee's context differs from the one you sent, you get 409 ResourceConflict instead of the 201. Change the context with change-context (below), not by creating again. A 409 can also mean another create for the same email changed the record while yours ran. Look the Payee up with filter[email][eq] and retry. The payments.PayeeEmailConflict detail code that the API reference mentions is not returned today.
Important: A
201doesn't always mean a new Payee was created. Compareevents.createdAtwith the time of your request, or look the Payee up by email first, before assuming the fields you sent were saved.
An email held only by a Deactivated Payee doesn't conflict; that create proceeds normally.
List and find payees
GET /v3/payments/payees supports these filters: status (eq, anyOf), linkRequestStatus (eq, anyOf), context (eq, anyOf), externalId (eq), and email (eq). A Contractor context filter also returns Payees created before context existed, which read as Contractor.
# Payees who were invited but haven't accepted yet
curl -G https://api.wingspan.app/v3/payments/payees \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
--data-urlencode "filter[linkRequestStatus][eq]=Pending" \
--data-urlencode "page[size]=50"Keep calling with page[token] set to pagination.nextPageToken until it's an empty string. See Pagination.
GET /v3/payments/payees/{payeeId} returns one Payee, including branding resolved from the linked Account when there is one. Add ?expand=ComplianceEntity to include a summary of the tax identity selected by complianceEntitySourceStrategy as complianceEntity, with complianceEntitySource saying whose it is. The summary has names, legal form, business details, phone, and addresses. It never includes tax IDs, dates of birth, or verification results. Any other expand value returns 422.
For full-text search across payees, use /v3/search/payee-rows.
Update a payee
PATCH /v3/payments/payees/{payeeId} changes only the fields you send. You can update profile (displayName, doingBusinessAs, phone), externalId, defaults, internalNotes, quickbooksVendorId, quickbooksExpenseAccountId, complianceEntitySourceStrategy, w9SourcePolicy, payoutDestinationPolicy, and metadata. Send an empty string to clear externalId or internalNotes. profile.email isn't editable here; see Change a payee's email. payerSuppliedComplianceEntityId is rejected on update. profileSource was removed, and a request that sends it is rejected.
curl -X PATCH https://api.wingspan.app/v3/payments/payees/M9ISYbzJElXs4zIHv76rjT \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"internalNotes": "Prefers text messages for shift updates.",
"defaults": { "isAutoPayEnabled": true }
}'email and context aren't editable through PATCH. See the next two sections.
Worker context
context records the kind of worker a Payee is to you: Contractor (a 1099 contractor or vendor) or Employee (a W-2 employee). A Payee has exactly one context at a time, and it decides which engagement types the Payee can hold. Only an Employee engagement fits the Employee context. The other engagement types fit Contractor. A mismatched PayeeEngagement returns 409 ResourceConflict. See Engagements.
Payees created before context existed read as Contractor until their first engagement records the context that engagement implies.
To change it, call POST /v3/payments/payees/{payeeId}/change-context with the new value. It works in both directions and doesn't change status.
curl -X POST https://api.wingspan.app/v3/payments/payees/M9ISYbzJElXs4zIHv76rjT/change-context \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "context": "Employee" }'The response is 200 with the updated Payee, and events.contextChangedAt records when it changed.
- You can't change context while any engagement of the current context is
CreatedorActivated. You get409, so terminate or delete those engagements first. - A
Suspendedengagement doesn't block the change, but you can't resume it afterward. - A
DeactivatedPayee returns409. Activate it first. - Sending the context the Payee already has changes nothing.
There's no webhook for a context change.
Change a payee's email
Changing the email can move records between Payees, so it's a two-step flow:
POST /v3/payments/payees/{payeeId}/update-email/previewwith{"targetEmail": "..."}. Nothing is written. The response tells you what would happen:resultTypeisUpdatedEmail,NewPayee, orExistingPayee, and it lists the engagement, invoice, and deduction IDs that would move plus anyconflictFields.POST /v3/payments/payees/{payeeId}/update-emailwith the same body applies it. The response'stargetPayeeIdis the Payee to use afterward.
A Payee created in V3 and not yet linked keeps its id. A Payee that came from V1 may be replaced or merged into another record, so always read targetPayeeId from the response.
Deactivate and reactivate
DELETE /v3/payments/payees/{payeeId} sets status to Deactivated and returns 204. It doesn't erase the record or its history.
POST /v3/payments/payees/{payeeId}/activate sets it back to Activated.
We use a POST verb for the state change instead of letting you PATCH the status field, so the audit trail always records which action happened and who took it.
Payee representatives and owners
For a Payee that is a business, you can record the people who represent or own it as Payee stakeholders. These records grant no Account access.
GET /v3/payments/payees/{payeeId}/stakeholderslists them. Filter withfilter[externalId][eq].POST /v3/payments/payees/{payeeId}/stakeholderscreates one withownershipPercentage,isController, andaccountSuppliedComplianceEntityId. Create the stakeholder first, then create the person'sIndividualComplianceEntity with the stakeholder as its subject, thenPATCHthe stakeholder to point at that ComplianceEntity. The controller becomes the business representative. Other people with at least 25% ownership become beneficial owners.PATCH /v3/payments/payees/{payeeId}/stakeholders/{stakeholderId}changes those fields. SendnullforaccountSuppliedComplianceEntityIdto clear it, or point it at the person's newer identity record.DELETE /v3/payments/payees/{payeeId}/stakeholders/{stakeholderId}revokes one.
See Tax information for ComplianceEntities.
Search and bank transfer details
POST /v3/payments/payees/search returns the first page of your Activated Payees, sized by limit. query, filters, and sort aren't supported and return 422. The response has no continuation token, so use the list endpoint to page through all Payees.
GET /v3/payments/payees/{payeeId}/bank-transfer-processing-account returns the ACH and wire instructions used to pay the Payee. It needs payments.payout-settings:read. The response contains full bank account details and is sent with Cache-Control: private, no-store, so don't cache or log it.
Payee activity history
GET /v3/payments/payees/{payeeId}/events returns the audit history for a Payee, newest first. It's paginated like any list.
Webhooks
You can subscribe to Payee.Activated. It fires when POST /v3/payments/payees/{payeeId}/activate moves a Deactivated Payee back to Activated. Creating a Payee, accepting an invitation, and associate-account don't fire it. The payload identifies the Payee, so read it with GET /v3/payments/payees/{payeeId} for details. Don't use it as an invitation-accepted signal. Read linkRequestStatus for that.
Other Payee events (Payee.Created, Payee.Invited, Payee.Deactivated, and others) are also available. You can still read GET /v3/payments/payees with a linkRequestStatus or status filter. See Event types.
Common mistakes
- Treating
status: Activatedas "ready to pay." Every Payee isActivatedthe moment you create it. CheckpaymentsEligibilityon the PayeeEngagement instead. - Assuming a
201means a new record. A matching email can return the existing Payee. Checkevents.createdAt. - Putting your system's ID in
metadata. UseexternalId. It's echoed on every read, it's unique per Account, and you can filter by it. - Passing the contractor's Account ID on create.
POST /v3/payments/payeesdoesn't accept one. If you already control the contractor's Account, create the Payee and then callassociate-account. See Payee account linking. - Trying to
PATCHcontext. Usechange-context, after ending the engagements of the current context. - Creating a second Payee when the contractor changes email. Use the
update-emailflow so their engagements and payment history stay together.
Related
Updated 11 minutes ago