Filtering, sorting, and expanding
Narrow Wingspan V3 lists with filter[field][operator], order them with sort[field], and inline related resources with expand.
Use Wingspan V3 query parameters to filter and sort list results, and expand related resources in a single request. V3 replaces the V1 schema.filter('field', '=') style with one bracketed syntax that works consistently across endpoints.
Each endpoint declares which fields and operators it accepts. The reference page for the endpoint lists them. Anything the endpoint doesn't declare is rejected with 422 ValidationError, so a typo in a filter never silently returns the whole list.
Tip: Brackets in query strings confuse some tools. With curl, pass
-g(as in the examples below) or URL-encode the brackets.
Filter
Filters use the form filter[field][operator]=value:
curl -g "https://api.wingspan.app/v3/payments/payables?filter[status][eq]=Opened" \
-H "Authorization: Bearer $WINGSPAN_TOKEN"Operators
| Operator | Meaning | Example |
|---|---|---|
eq | Equals | filter[status][eq]=Opened |
ne | Does not equal | filter[metadata.invoiceType][ne]=approvedInvoicesPayment |
gt, gte | Greater than, greater than or equal | filter[createdAt][gte]=2026-01-01T00:00:00Z |
lt, lte | Less than, less than or equal | filter[createdAt][lte]=2026-03-31T23:59:59Z |
anyOf | Matches any value in a list | filter[status][anyOf][]=Opened&filter[status][anyOf][]=Paid |
noneOf | Matches none of the values in a list | filter[status][noneOf][]=Deactivated |
Operators are words, never symbols. For the list operators anyOf and noneOf, repeat the parameter once per value with [] at the end.
Not every endpoint accepts every operator. Check the endpoint's reference page for the operators each field accepts. Recurring invoice and recurring payable status filters, and webhook metadata filters, currently accept a single eq value and don't support anyOf yet.
Add more parameters to combine filters. Every filter must match because Wingspan joins filters with AND:
curl -g "https://api.wingspan.app/v3/platform/batches?filter[type][eq]=PayableImport&filter[status][anyOf][]=Completed&filter[status][anyOf][]=Failed&filter[createdAt][gte]=2026-04-01T00:00:00Z" \
-H "Authorization: Bearer $WINGSPAN_TOKEN"Values follow the field's type: enum values in TitleCase, timestamps in ISO 8601 UTC, dates as YYYY-MM-DD.
Look up by your own ID
Resources with an externalId usually accept filter[externalId][eq]. Use it to find a record from the ID in your system:
curl -g "https://api.wingspan.app/v3/payments/payees?filter[externalId][eq]=CONTRACTOR-00417" \
-H "Authorization: Bearer $WINGSPAN_TOKEN"Filter by metadata
Some list endpoints accept filters on metadata keys with a dotted name, such as filter[metadata.region][eq]=west. Check the endpoint's reference page to confirm that it supports metadata filtering. Metadata values are strings, so use equality operators rather than ranges.
Filter across child Accounts
Some payer-side lists, including payees, payables, and invoices, accept filter[accountIds][anyOf][]=.... Use this filter to read across multiple Accounts you can access in one call. To work within one child Account, send X-Wingspan-Account instead. See Acting on behalf of Accounts.
Sort
Sort uses the form sort[field]=asc or sort[field]=desc:
curl -g "https://api.wingspan.app/v3/payments/recurring-invoices?sort[createdAt]=desc" \
-H "Authorization: Bearer $WINGSPAN_TOKEN"Each list has a default order. Check the endpoint's reference page to confirm that it accepts sort and identify the fields you can sort by. Search endpoints accept exactly one sort field. See Search.
Sort one field at a time. Accounting sync activities use sort=createdAt or sort=-createdAt instead.
Expand
Some read responses include an ID for a related resource, such as payeeId on a payable. Add expand to include that related resource inline in the response and avoid a second call:
curl -g "https://api.wingspan.app/v3/payments/payables/b3VhT6gJq1MzR8xKd5WnPe?expand=payee&expand=attachments" \
-H "Authorization: Bearer $WINGSPAN_TOKEN"Repeat expand once per value. The bracket form expand[]=payee is also accepted and means the same thing. Don't comma-separate values; expand=payee,attachments is rejected.
Only specific read endpoints support expansion, and each accepts a fixed set of values. Values are case-sensitive and use different casing conventions, so copy them exactly:
| Endpoint | Supported expand values |
|---|---|
GET /v3/payments/invoices/{invoiceId} | payer, payee, attachments, splits.payee |
GET /v3/payments/payables/{payableId} | payer, payee, attachments, splits.payee |
GET /v3/payments/payee-engagements/{payeeEngagementId} | Requirements, WorkDefinitions |
GET /v3/payments/payees/{payeeId}/engagements/{payeeEngagementId} | Requirements, WorkDefinitions |
GET /v3/payments/payers/{payerId}/engagements/{payerEngagementId} | workDefinitions |
GET /v3/payments/payees/{payeeId} | ComplianceEntity |
GET /v3/payments/payers/{payerId} | ComplianceEntity |
GET /v3/payments/payouts and GET /v3/payments/payouts/{payoutId} | routing, settingsSnapshot |
GET /v3/payments/payroll-runs/{payrollRunId} | Breakdown |
GET /v3/platform/accounts/{accountId} | ComplianceEntity |
GET /v3/platform/persons/{personId} | ComplianceEntity, IntercomIdentity |
GET /v3/platform/plans | products |
GET /v3/platform/batches/{batchId}/summary | Breakdown |
GET /v3/onboarding/payee-requirements/{requirementId} | Configuration |
GET /v3/onboarding/requirement-definitions/{requirementDefinitionId} | DataSource |
GET /v3/compliance/forms/{formId} | BlockedByIds |
GET /v3/compliance/insurance-monitors/{monitorId} | Configuration, Coverage |
An unsupported value returns 422 ValidationError. A supported value that has nothing to include (for example, a payable with no attachments) returns 200 without that key.
Expansion on invoices and payables is caller-relative: you receive the related record from your side of the relationship. For example, when a payer expands payee, the response includes that payer's Payee record, not the other party's view.
From V1 query parameters
| V1 | V3 |
|---|---|
schema.filter('status', '=') | filter[status][eq]=... |
schema.filter('status', '!=') | filter[status][ne]=... or filter[status][noneOf][]=..., whichever the endpoint lists |
schema.filter('status', 'in') | filter[status][anyOf][]=... repeated |
>, >=, <, <= | gt, gte, lt, lte |
between | gte and lte together |
contains on names and emails | filter[searchString][eq]=... on the /v3/search endpoints |
clientData.externalId / memberData.externalId | filter[externalId][eq]=... |
labels.* | filter[metadata.*][eq]=... where supported |
page=2&limit=10 | page[size]=10 and page[token]=.... See Pagination |
V1 also let you filter on tax identifiers and W-9 fields. V3 doesn't offer filters on tax identifiers.
What can go wrong
| Response | Cause | Fix |
|---|---|---|
422 ValidationError, errors[].code: UnsupportedValue | The field or operator isn't declared for this endpoint, or an enum value is misspelled | Check the endpoint's reference page for the fields and operators it accepts |
422 ValidationError on expand | The expand value isn't supported on this endpoint, its case is wrong, or several values were comma-separated | Use a value from the table above, spelled exactly |
422 ValidationError on sort | The endpoint doesn't accept sort, or not on that field | Remove sort, or use a field the reference lists |
Related pages
Updated 10 days ago