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, ...]
- 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. - You attach definitions to an Engagement, each with a
blockingMode. - When you assign a Payee with a PayeeEngagement, Wingspan creates a Requirement instance for each definition.
- Each instance is backed by a resource the Payee acts on, such as a signature request. When that resource completes, the requirement completes.
- The PayeeEngagement's
paymentsEligibilityrolls up the blocking requirements intoEligibleorNotEligible.
V1 attached requirements to collaborator groups. In V3 you attach them to Engagements. Groups are for organizing Payees; see Groups.
Requirement types
type | What the Payee does | Needs a dataSourceId? |
|---|---|---|
Signature | Signs a document from your document template. | Yes: a document template ID. |
DocumentUpload | Uploads a file, such as a license or certificate. | Yes: a shared file request configuration ID. |
BackgroundCheck | Completes a background check with the vendor. | Yes: background check settings. |
InsuranceCoverage | Provides insurance coverage that Wingspan monitors. | Yes: an insurance monitor configuration. |
TaxVerification | Has a verified tax identity. Completed by the Tax verification of their ComplianceEntity. | No |
PayoutMethod | Sets up a payout method. See Payout methods. | No |
Registration | Accepts your invitation and links an Account. | No |
Acknowledgement | Accepts an agreement. | No |
BiometricIdentityVerification | Completes a document and selfie identity check. | No |
ExternalCompletion | Nothing 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
| Field | What it does |
|---|---|
name, description, type | name and type are required. |
dataSourceId | The configuration instances are created from. Required for Signature, DocumentUpload, BackgroundCheck, and InsuranceCoverage; rejected with 422 for other types. |
reviewPolicy | NeverRequired: 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, expirationStrategy | How long a completed requirement stays valid. Strategies: DaysAfterComplete (the default when expirationDays is set), DaysAfterYearStart, DaysBeforeYearEnd, None. |
daysUntilEffective | A grace period, in days from creation, before an incomplete instance starts blocking. Create only. |
daysUntilExpiration | An expiry counted from when the instance is created, separate from expirationDays. Must be greater than daysUntilEffective. Create only. |
manualRenewalAllowed | Whether an instance can be renewed by hand. |
externalId, metadata | Your 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 field | Values |
|---|---|
scope | NewOnly, IncompleteAndNew, or All |
strategy | Immediate, or Renewal (existing instances move over when they renew) |
expiresAt | For 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
POST .../deactivatearchives it (status: Inactive). Reversible withPOST .../activate.DELETE .../{requirementDefinitionId}removes it permanently. Definitions attached to active engagements can't be deleted.
Requirement instances
List instances with GET /v3/onboarding/payee-requirements. Useful filters:
| Filter | Operators |
|---|---|
status, type | anyOf, noneOf |
payeeId, payeeAccountId, payerAccountId, payeeEngagementId, requirementDefinitionId | anyOf |
payerOverrideStatus, expirationStatus, dataSourceMetadata.status | anyOf |
createdAt, expiresAt | gte, lte |
externalId | eq, 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
status | Meaning |
|---|---|
PendingCompletion | Waiting for the Payee. Also the state after you reject or reset it. |
PendingPayerReview | The Payee finished; waiting for you to approve or reject (blockedReason: AwaitingPayerReview). |
Completed | Done. |
Inactive | No 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:
dataSourceis the backing resource the Payee acts on, as{ type, id }. See the table below for where to read it.dataSourceStatusis the backing resource's own state, before any decision by you.payerOverrideStatusis your manual decision, if you made one. When it's null,statusfollowsdataSourceStatus.blockedReasonexplains a stall:AwaitingPayerReview,VendorProcessing,ExternalResultFailed,AwaitingDataSource, orTaxDocumentShareNotGranted(an identity verification result can't be released to you until the Payee shares tax documents).effectiveDateis when an incomplete instance starts blocking, if the definition set a grace period.dataSourceAppliedByisPayerorPayee: who attached the current backing resource. Older requirements without it read as payer-applied.dataSourceChangeReasonsays 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), orExternalResultFailed(an external result came back unsuccessful).requiresFreshDataSourceistrueafter 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 thereasonit was replaced.
dataSource.type | Read it at |
|---|---|
SignatureRequest | GET /v3/compliance/signature-requests/{id} |
SharedFileRequest | GET /v3/compliance/shared-file-requests/{id} |
BackgroundCheck | GET /v3/compliance/background-checks/{id} |
InsuranceMonitor | GET /v3/compliance/insurance-monitors/{id} |
BiometricIdentityVerification | GET /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
requirementIdthe 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 withoutrequirementIdreturns400, whatever else it contains. WithrequirementId, a payer-only field returns422. - The payer can leave out
requirementIdand name the configuration or template directly. If you send both, the requirement's configuration wins.
| Backing resource | Create it with | Payer names |
|---|---|---|
| Signature request | POST /v3/compliance/signature-requests | templateId |
| Shared file request | POST /v3/compliance/shared-file-requests | configurationId |
| Background check | POST /v3/compliance/background-checks | packageId |
| Insurance monitor | POST /v3/compliance/insurance-monitors | configurationId |
| Identity verification request | POST /v3/compliance/biometric-identity-verification-requests | Nothing 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.
revertit 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 returns409.
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.
| Action | What it does |
|---|---|
approve | Completes a requirement waiting in PendingPayerReview. Optional expiresAt to pin an expiration. |
reject | Sends it back to PendingCompletion. Optional reason, shown to the Payee. |
extend-expiration | Moves expiresAt (required) without changing status. Optional reason. |
revert | Removes your approve or reject decision so status follows the backing resource again. |
reset | Clears what was submitted and returns it to PendingCompletion. If it detaches the backing resource, requiresFreshDataSource becomes true until a new one is applied. |
renew | Starts a fresh round for an expired, time-bound requirement such as an annual certificate. |
apply-data-source | Attaches a backing resource you already created. Either party can call it. See above. |
fulfill | For 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 PayeeEngagement | Meaning |
|---|---|
paymentsEligibility | Eligible or NotEligible, computed from the requirements. |
areAllRequirementsComplete | true 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(andCreated,Sent,Reminded,Cancelled)SharedFileRequest.Completed(and otherSharedFileRequest.*events)BackgroundCheck.Completed(and otherBackgroundCheck.*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.statusas onboarding progress. ReadpaymentsEligibilityon the PayeeEngagement. - Expecting a
requirementIdon a create to link the resource. It only authorizes the create. Callapply-data-sourceto attach it. - Trying to complete a requirement with
PATCH. The Payee completes the backing resource. You approve, reject, waive, or (forExternalCompletion) fulfill. - Filtering with
inornotIn. UseanyOfandnoneOf. - Changing a definition and expecting every existing instance to update. Material changes follow
transition, which by default applies to new instances only.
Related
Updated 10 days ago