Requirements and eligibility

Define the documents, signatures, and checks a Payee must complete, attach them to an engagement, review submissions, and read whether an engagement can be paid.

Requirements are the things a Payee must complete before you pay them: sign an NDA, upload a license, pass a background check, show proof of insurance, verify their tax information. This page shows you how to define requirements, attach them to engagements, review what Payees submit, and check whether an engagement is eligible for payment. V1 called these document requirements or eligibility requirements.

How the pieces fit

flowchart LR
    A[RequirementDefinition<br/>template you define once] --> B[Engagement<br/>template lists definitions]
    B --> C[PayeeEngagement<br/>one Payee on the Engagement]
    C --> D[Requirement<br/>one instance per definition]
    D --> E[Backing resource<br/>SignatureRequest, SharedFileRequest,<br/>BackgroundCheck, InsuranceMonitor, ...]
  1. A RequirementDefinition is your reusable template: "Sign the 2026 NDA", "Upload RN license", "Background check". Many definitions point at a configuration (the dataSourceId), such as a document template.
  2. You attach definitions to an Engagement, each with a blockingMode.
  3. When you assign a Payee with a PayeeEngagement, Wingspan creates a Requirement instance for each definition.
  4. Each instance is backed by a resource the Payee acts on, such as a signature request. When that resource completes, the requirement completes.
  5. The PayeeEngagement's paymentsEligibility rolls up the blocking requirements into Eligible or NotEligible.

V1 attached requirements to collaborator groups. In V3 you attach them to Engagements. Groups are for organizing Payees; see Groups.

Requirement types

typeWhat the Payee doesNeeds a dataSourceId?
SignatureSigns a document from your document template.Yes: a document template ID.
DocumentUploadUploads a file, such as a license or certificate.Yes: a shared file request configuration ID.
BackgroundCheckCompletes a background check with the vendor.Yes: background check settings.
InsuranceCoverageProvides insurance coverage that Wingspan monitors.Yes: an insurance monitor configuration.
TaxVerificationHas a verified tax identity. Completed by the Tax verification of their ComplianceEntity.No
PayoutMethodSets up a payout method. See Payout methods.No
RegistrationAccepts your invitation and links an Account.No
AcknowledgementAccepts an agreement.No
BiometricIdentityVerificationCompletes a document and selfie identity check.No
ExternalCompletionNothing in Wingspan. You record the result from your own system.No

The API also lists Insurance, License, Custom, SignatureRequest, and ExternalVendor in the RequirementType enum. If you rely on one of these, confirm with support how it's completed in your account before building on it.

Example: require a signed NDA

This replaces V1's upload, template, eligibility-requirement, and collaborator-group steps.

1. Put the document in the vault

POST /v3/compliance/vault-files registers the PDF. Use kind: Document for anything that will be signed. See Files and documents for upload options.

curl -X POST https://api.wingspan.app/v3/compliance/vault-files \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Northwind NDA 2026",
    "kind": "Document",
    "fileUrl": "https://files.example.com/northwind-nda-2026.pdf",
    "mimeType": "application/pdf"
  }'
// 201 Created (trimmed)
{ "id": "rwycSeYZfNJJJzQHWVOYCC", "title": "Northwind NDA 2026" }

2. Create a document template

POST /v3/compliance/document-templates turns the file into a signable template. signerRoles says who signs: ["Payee"] for the contractor only, or ["Payer", "Payee"] for a mutual signature (V1's Mutual option).

curl -X POST https://api.wingspan.app/v3/compliance/document-templates \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Northwind NDA 2026",
    "fileId": "rwycSeYZfNJJJzQHWVOYCC",
    "signerRoles": ["Payee"]
  }'
// 201 Created (trimmed)
{
  "id": "t1erTOHO7rlJI7nzF8uCpY",
  "title": "Northwind NDA 2026",
  "status": "Active",
  "signerRoles": ["Payee"],
  "editUrl": "https://...",
  "editUrlExpiresAt": "2026-09-24T16:20:00Z"
}

Open editUrl in a browser to place signature, initial, date, and text fields on the document. The URL is time limited; GET /v3/compliance/document-templates/{templateId} returns a fresh one.

3. Create the requirement definition

POST /v3/onboarding/requirement-definitions:

curl -X POST https://api.wingspan.app/v3/onboarding/requirement-definitions \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Sign the 2026 NDA",
    "type": "Signature",
    "dataSourceId": "t1erTOHO7rlJI7nzF8uCpY",
    "reviewPolicy": "NeverRequired",
    "expirationDays": 365,
    "externalId": "REQ-NDA-2026"
  }'
// 201 Created (trimmed)
{
  "id": "oP8lYOzdKQy7H_mNrx9Uf9",
  "name": "Sign the 2026 NDA",
  "type": "Signature",
  "status": "Active",
  "version": 1,
  "dataSourceId": "t1erTOHO7rlJI7nzF8uCpY",
  "reviewPolicy": "NeverRequired",
  "expirationDays": 365
}

4. Attach it to an engagement

curl -X POST https://api.wingspan.app/v3/payments/engagements/Fj9ZSOWd3LdiamTunQ5x98/requirements \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "requirementDefinitionId": "oP8lYOzdKQy7H_mNrx9Uf9", "blockingMode": "BlocksPayment" }'

This returns 204. You can also pass requirementDefinitionIds when you create the Engagement. See Engagements.

5. Assign the payee

Create a PayeeEngagement for the Payee on that Engagement. Wingspan creates the NDA requirement instance and the signature request behind it. The Payee sees it in their onboarding, and they must complete it before they can be paid through that engagement.

Requirement definition fields

FieldWhat it does
name, description, typename and type are required.
dataSourceIdThe configuration instances are created from. Required for Signature, DocumentUpload, BackgroundCheck, and InsuranceCoverage; rejected with 422 for other types.
reviewPolicyNeverRequired: completes as soon as the backing resource succeeds. AlwaysRequired: waits for you to approve. OnFail: waits for you only when the result is inconclusive or failed.
expirationDays, expirationStrategyHow long a completed requirement stays valid. Strategies: DaysAfterComplete (the default when expirationDays is set), DaysAfterYearStart, DaysBeforeYearEnd, None.
daysUntilEffectiveA grace period, in days from creation, before an incomplete instance starts blocking. Create only.
daysUntilExpirationAn expiry counted from when the instance is created, separate from expirationDays. Must be greater than daysUntilEffective. Create only.
manualRenewalAllowedWhether an instance can be renewed by hand.
externalId, metadataYour IDs and data. A duplicate externalId returns 409.

The fields isBlocking, isPayeeActionRequired, isPayerActionRequired, source, passingValues, and policy are legacy. The V3 API rejects them on create and update. Use type, reviewPolicy, and the engagement's blockingMode instead.

Change a definition

PATCH /v3/onboarding/requirement-definitions/{requirementDefinitionId} is a merge patch. Wingspan decides whether a change is material. A material change, such as pointing dataSourceId at a new template, publishes a new version. transition controls how existing instances pick it up:

transition fieldValues
scopeNewOnly, IncompleteAndNew, or All
strategyImmediate, or Renewal (existing instances move over when they renew)
expiresAtFor Renewal, the deadline to move over

The default is the safest: only new instances pick up the change, at renewal. Add a versionNote to record why. versionHistory on the definition keeps every published version.

Find definitions

GET /v3/onboarding/requirement-definitions filters by type, externalId, dataSourceId, and status (anyOf: Active, Inactive), and sorts with sort[updatedAt]=asc or desc. pagination.totalSize counts the filtered results, so you can show how many definitions are active and how many are archived. To see which Engagements use a definition, filter GET /v3/payments/engagements with filter[requirementDefinitionIds][anyOf][]=<id>.

Retire a definition

Requirement instances

List instances with GET /v3/onboarding/payee-requirements. Useful filters:

FilterOperators
status, typeanyOf, noneOf
payeeId, payeeAccountId, payerAccountId, payeeEngagementId, requirementDefinitionIdanyOf
payerOverrideStatus, expirationStatus, dataSourceMetadata.statusanyOf
createdAt, expiresAtgte, lte
externalIdeq, anyOf
# Everything still open for one engagement
curl -G https://api.wingspan.app/v3/onboarding/payee-requirements \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  --data-urlencode "filter[payeeEngagementId][anyOf][]=ru9zEafz6i4WRlIOA10wAk" \
  --data-urlencode "filter[status][noneOf][]=Completed" \
  --data-urlencode "filter[status][noneOf][]=Inactive"
// 200 OK (trimmed)
{
  "data": [
    {
      "id": "CkAmZjWbwGy8H6bX6a4lA1",
      "name": "Sign the 2026 NDA",
      "type": "Signature",
      "status": "PendingCompletion",
      "requirementDefinitionId": "oP8lYOzdKQy7H_mNrx9Uf9",
      "payeeId": "M9ISYbzJElXs4zIHv76rjT",
      "payeeEngagementIds": ["ru9zEafz6i4WRlIOA10wAk"],
      "dataSource": { "type": "SignatureRequest", "id": "8lhWK5WB9iKJtjzeTZZLw8" },
      "dataSourceStatus": "PendingCompletion",
      "reviewPolicy": "NeverRequired",
      "expiresAt": null
    }
  ],
  "pagination": { "nextPageToken": "" }
}

GET /v3/onboarding/payee-requirements/{requirementId} returns one instance. Add ?expand=Configuration to include a summary of the definition's configuration.

Status

statusMeaning
PendingCompletionWaiting for the Payee. Also the state after you reject or reset it.
PendingPayerReviewThe Payee finished; waiting for you to approve or reject (blockedReason: AwaitingPayerReview).
CompletedDone.
InactiveNo longer in effect: detached from every engagement, or cancelled. This is separate from the completion steps above.

Expiration is tracked separately and never changes status. Read expiresAt and expirationStatus (Expiring or Expired).

Other fields worth knowing:

  • dataSource is the backing resource the Payee acts on, as { type, id }. See the table below for where to read it.
  • dataSourceStatus is the backing resource's own state, before any decision by you.
  • payerOverrideStatus is your manual decision, if you made one. When it's null, status follows dataSourceStatus.
  • blockedReason explains a stall: AwaitingPayerReview, VendorProcessing, ExternalResultFailed, AwaitingDataSource, or TaxDocumentShareNotGranted (an identity verification result can't be released to you until the Payee shares tax documents).
  • effectiveDate is when an incomplete instance starts blocking, if the definition set a grace period.
  • dataSourceAppliedBy is Payer or Payee: who attached the current backing resource. Older requirements without it read as payer-applied.
  • dataSourceChangeReason says why the backing resource last changed: Manual (you asked for it again), Replaced (a new one was applied), DefinitionChanged (a new definition version moved it), DocumentRejected (you rejected the document), or ExternalResultFailed (an external result came back unsuccessful).
  • requiresFreshDataSource is true after a reset detached the previous backing resource, until a new one is applied. While it's set, create a new backing resource instead of reusing one.
  • dataSourceHistory[] lists earlier backing resources, each with the reason it was replaced.
dataSource.typeRead it at
SignatureRequestGET /v3/compliance/signature-requests/{id}
SharedFileRequestGET /v3/compliance/shared-file-requests/{id}
BackgroundCheckGET /v3/compliance/background-checks/{id}
InsuranceMonitorGET /v3/compliance/insurance-monitors/{id}
BiometricIdentityVerificationGET /v3/compliance/biometric-identity-verification-requests/{id}

How a payee completes a requirement

The Payee acts on the backing resource, not on the requirement. For a signature, your onboarding screen reads GET /v3/compliance/signature-requests/{id}/signing-urls and opens the signing URL, which is single use and expires within the hour. For a file upload, the Payee uploads to the vault and attaches the file to the shared file request. When the backing resource finishes, Wingspan moves the requirement forward.

If you use Wingspan's hosted onboarding, Wingspan shows each open requirement to the Payee for you.

Open a new backing resource

Usually Wingspan creates the backing resource when it creates the requirement. When a requirement needs a new one (for example after a reset, when requiresFreshDataSource is true), either party can open one and then attach it with apply-data-source.

Each create names the payer-payee relationship in payerPayeeId, which is on the requirement, and never an Account. The two sides have different rules:

  • The payee must send the requirementId the resource is for, and must leave out the payer's fields (externalId, metadata, and the configuration or template ID). Wingspan takes the configuration from the requirement. A payee request without requirementId returns 400, whatever else it contains. With requirementId, a payer-only field returns 422.
  • The payer can leave out requirementId and name the configuration or template directly. If you send both, the requirement's configuration wins.
Backing resourceCreate it withPayer names
Signature requestPOST /v3/compliance/signature-requeststemplateId
Shared file requestPOST /v3/compliance/shared-file-requestsconfigurationId
Background checkPOST /v3/compliance/background-checkspackageId
Insurance monitorPOST /v3/compliance/insurance-monitorsconfigurationId
Identity verification requestPOST /v3/compliance/biometric-identity-verification-requestsNothing extra. forceCreate: true starts a new verification instead of reusing a finished one.

A create that cites a requirementId is deduplicated: repeating it while an earlier one is still open returns the existing resource, so a retry is safe. A payer's direct order (no requirementId), an externalId, or a reset of the requirement opens a new one instead. Background checks are the exception: every order is deduplicated on the relationship and package, so a repeat returns the active check unless you reset the requirement. A relationship you don't hold a seat on returns 404.

A new signature request isn't sent for signing yet. The payee calls POST /v3/compliance/signature-requests/{requestId}/start to create the document and get signing URLs.

Two things to know about background checks: a 409 can mean an earlier order for the same relationship is still in flight (retry with a new Idempotency-Key), or that the requirement's definition names no configuration (retrying won't help). A 404 often means the package was deactivated. A background check also needs the payee to have linked their Account, and returns 422 until they have.

Attach a backing resource: apply-data-source

The requirementId on a create authorizes it. It doesn't link the new resource to the requirement. Attach it with POST /v3/onboarding/payee-requirements/{requirementId}/apply-data-source:

curl -X POST https://api.wingspan.app/v3/onboarding/payee-requirements/CkAmZjWbwGy8H6bX6a4lA1/apply-data-source \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "dataSourceId": "3pQx7LmN9vR2tW5yZa8KcB" }'

The response is the updated requirement. The previous backing resource moves to dataSourceHistory with reason: Replaced, dataSourceAppliedBy records who applied it, and requiresFreshDataSource is cleared.

  • The resource must belong to the same payer-payee relationship as the requirement. Its kind comes from the requirement's type.
  • You can't apply a resource the requirement has already used, including the current one. That returns 409 ResourceConflict.
  • Nobody can apply while you have an approve or reject decision standing on the requirement. revert it first.
  • A payee can't replace a backing resource the payer applied once the requirement is Completed.
  • A requirement type with no separately addressable backing resource returns 422. A requirement whose monitor is paused or cancelled returns 409.

Insurance warnings

Some insurance checks don't block a coverage but still don't pass, such as a named insured that doesn't quite match. They show up as Warning in an insurance import's or monitor's jobs[], and on the coverage in warnings[] (each with a title and message). A monitor whose linked coverage carries a warning reads ForceMatched instead of Complete. Read jobs[] to see which check it was.

Review and manage requirements

All of these are POST /v3/onboarding/payee-requirements/{requirementId}/{action} and are for the payer, except where noted.

ActionWhat it does
approveCompletes a requirement waiting in PendingPayerReview. Optional expiresAt to pin an expiration.
rejectSends it back to PendingCompletion. Optional reason, shown to the Payee.
extend-expirationMoves expiresAt (required) without changing status. Optional reason.
revertRemoves your approve or reject decision so status follows the backing resource again.
resetClears what was submitted and returns it to PendingCompletion. If it detaches the backing resource, requiresFreshDataSource becomes true until a new one is applied.
renewStarts a fresh round for an expired, time-bound requirement such as an annual certificate.
apply-data-sourceAttaches a backing resource you already created. Either party can call it. See above.
fulfillFor ExternalCompletion only. Records a Passing or Failing result from your own system, with optional fulfillmentData, verification.vendor, verification.externalId, and notes.

Approving or rejecting in the wrong state returns 409. A Payee trying to approve or reject their own requirement gets 403.

To waive a requirement for one engagement, use POST /v3/payments/payees/{payeeId}/engagements/{payeeEngagementId}/requirements/{id}/waive with an optional reason. PayoutMethod and Inactive requirements can't be waived (400).

PATCH /v3/onboarding/payee-requirements/{requirementId} only changes metadata and externalId. It doesn't accept a status.

Check eligibility

Eligibility is decided per PayeeEngagement, because that's what you pay through. The same Payee can be eligible on one engagement and not on another.

Field on the PayeeEngagementMeaning
paymentsEligibilityEligible or NotEligible, computed from the requirements.
areAllRequirementsCompletetrue when every attached requirement is complete.
curl https://api.wingspan.app/v3/payments/payee-engagements/ru9zEafz6i4WRlIOA10wAk \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
// 200 OK (trimmed)
{
  "id": "ru9zEafz6i4WRlIOA10wAk",
  "status": "Activated",
  "paymentsEligibility": "Eligible",
  "areAllRequirementsComplete": true
}

There's no Payee-level eligibility flag. If you need one per worker, compute it from the engagements you care about.

Paying through an engagement that isn't payments eligible returns 409 with code: EligibilityBlocked. See Create a payable.

A contractor can see their own progress with GET /v3/onboarding/status, which returns isEligible and a requirements summary (total, complete, incomplete, blocking) for their Account. See Identity verification.

Requirement definition groups

POST /v3/onboarding/requirement-definition-groups creates a named bundle of requirement definitions, and POST .../{requirementDefinitionGroupId}/requirements adds a definition to it. Use them to keep related definitions together. To make Payees complete requirements, attach the definitions to an Engagement.

Webhooks

Requirement.* events aren't available for subscription yet. The backing resources do publish events you can subscribe to today:

  • SignatureRequest.Completed (and Created, Sent, Reminded, Cancelled)
  • SharedFileRequest.Completed (and other SharedFileRequest.* events)
  • BackgroundCheck.Completed (and other BackgroundCheck.* events)

When one fires, re-read the requirement or the PayeeEngagement to see the effect. See Event types.

Common mistakes

  • Attaching requirements to a Group. In V3, requirements attach to Engagements.
  • Reading Payee.status as onboarding progress. Read paymentsEligibility on the PayeeEngagement.
  • Expecting a requirementId on a create to link the resource. It only authorizes the create. Call apply-data-source to attach it.
  • Trying to complete a requirement with PATCH. The Payee completes the backing resource. You approve, reject, waive, or (for ExternalCompletion) fulfill.
  • Filtering with in or notIn. Use anyOf and noneOf.
  • Changing a definition and expecting every existing instance to update. Material changes follow transition, which by default applies to new instances only.

Related


Did this page help you?