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.

ResourceWho owns itWhat it is
PayeeThe payer's AccountThe payer's record of someone it pays.
PayerThe payee's AccountThe same relationship seen from the payee's side.
AccountThe contractor or companyA business identity in Wingspan. A contractor's own Account is tied to their login.
PersonNobody (a human)A person who can sign in.
PayerPayeeLinkRequestBoth sides can read itThe 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

FieldWhat it means
idThe Payee's ID. Use it everywhere; it stays the same before and after linking.
accountIdYour Account (the owner of this record). Read-only.
payeeAccountIdThe contractor's own Account. Absent until the contractor accepts your invitation or you associate an Account.
payerIdThe ID of the matching Payer record on the payee's side. It may equal id.
profile.emailThe invitation address. Required on create. It lives inside profile; there is no top-level email field.
contextContractor 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.
externalIdYour own ID for this Payee (from your ERP, ATS, or HRIS). Unique per Account. Filterable.
statusActivated or Deactivated. An administrative flag only (see below).
linkRequestStatusPending, Linked, or Rejected. Linking progress, read-only. Absent when no link request exists.
profileDisplay 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.
payerSuppliedComplianceEntityIdA tax identity you supplied for this Payee before it linked. See Tax information.
identitySourcePayerProvided or PayeeOwned. Whose tax identity is attached. Read-only.
complianceEntitySourceStrategyAutomatic (default), PayerProvided, or PayeeProvided. Whose ComplianceEntity a read with ?expand=ComplianceEntity shows. See Tax information.
defaults.isAutoPayEnabledYour 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.verificationStrategyAll or None. Whether tax-information verification checks are required.
defaults.paymentTermsDaysNet 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, isAutoPayRequiredRead-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, effectivePaymentMethodThe 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, quickbooksExpenseAccountIdYour accounting mappings for this Payee, as Wingspan reference IDs. Empty string clears.
taxInformationShareStatusActive or Revoked. Absent when the payee never shared tax information.
w9SourcePolicy, payoutDestinationPolicyWhose data Wingspan uses for the W-9 and for the payout destination: PayerSupplied, PayeeSupplied, or PayeeSuppliedFallbackToPayer (the default for new Payees).
internalNotesNotes only your team can see.
events, actorscreatedAt, updatedAt, activatedAt, deactivatedAt, contextChangedAt, and who did each.
metadataUp 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.

StatusMeaningHow you get there
ActivatedThe relationship is active. Every new Payee starts here.Create, or POST .../activate to reactivate.
DeactivatedYou'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 linkRequestStatus and payeeAccountId.
  • Can I pay them through this engagement? Read paymentsEligibility on 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 externalId is already used by another Payee in your Account, you get 409 with code: ResourceConflict. Look up the existing Payee with GET /v3/payments/payees?filter[externalId][eq]=ATS-04821.
  • If profile.email matches an existing Payee in your Account, the call returns 201 with the existing Payee, reactivated if it was Deactivated. Nothing you sent is applied, and events.createdAt shows 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 201 doesn't always mean a new Payee was created. Compare events.createdAt with 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 Created or Activated. You get 409, so terminate or delete those engagements first.
  • A Suspended engagement doesn't block the change, but you can't resume it afterward.
  • A Deactivated Payee returns 409. 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:

  1. POST /v3/payments/payees/{payeeId}/update-email/preview with {"targetEmail": "..."}. Nothing is written. The response tells you what would happen: resultType is UpdatedEmail, NewPayee, or ExistingPayee, and it lists the engagement, invoice, and deduction IDs that would move plus any conflictFields.
  2. POST /v3/payments/payees/{payeeId}/update-email with the same body applies it. The response's targetPayeeId is 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.

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: Activated as "ready to pay." Every Payee is Activated the moment you create it. Check paymentsEligibility on the PayeeEngagement instead.
  • Assuming a 201 means a new record. A matching email can return the existing Payee. Check events.createdAt.
  • Putting your system's ID in metadata. Use externalId. 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/payees doesn't accept one. If you already control the contractor's Account, create the Payee and then call associate-account. See Payee account linking.
  • Trying to PATCH context. Use change-context, after ending the engagements of the current context.
  • Creating a second Payee when the contractor changes email. Use the update-email flow so their engagements and payment history stay together.

Related


Did this page help you?