Request and response conventions

How Wingspan V3 requests and responses are shaped, including IDs, casing, money, dates, metadata, externalId, events, actors, and updates.

Use the shared Wingspan V3 conventions to read and write resources consistently. The API reference lists the exact fields for each operation.

URLs

Every request goes to https://api.wingspan.app and every path starts with /v3/{domain}/:

DomainWhat lives there
/v3/paymentsPayees, payers, engagements, invoices, payables, payroll, payouts, and other money movement
/v3/financeBank accounts, transfers, cards, transactions, and tax withholding
/v3/onboardingIdentity verification, requirements, and reference data
/v3/complianceTax forms and filings, insurance, and vault files
/v3/platformAccounts, persons, sessions, webhooks, events, batches, custom fields, and async operations

/v3/search is a separate read-only surface for search rows. See Search.

A few more rules make paths predictable:

  • Path segments are kebab-case (/v3/compliance/vault-files) and path parameters are camelCase ({payeeId}).
  • Your own ID never appears in the URL. Wingspan knows who you are from your token. There is no /me endpoint.
  • To work inside a child or authorized Account, send X-Wingspan-Account. See Acting on behalf of Accounts.

Headers you'll use

HeaderWhen
Authorization: Bearer <token>Every authenticated request
Content-Type: application/jsonEvery request with a body
Idempotency-KeyCreates, money movement, and any POST or PATCH you might retry. Required on some operations. See Idempotency
If-MatchUpdates and state changes whose reference page lists it. See Concurrency and ETags
X-Wingspan-AccountActing in another Account. See Acting on behalf of Accounts

Successful responses are application/json. Errors are application/problem+json. See Errors.

IDs

Wingspan IDs are opaque strings. They have no type prefix and they are not UUIDs. Most are 22 characters, for example Qm7tR2vXk9LpZ4wNc8HsYa. Some records created before V3 use other shapes:

  • Older 22-character IDs can contain a ..
  • Some Accounts migrated from V1 retain a 24-character lowercase hexadecimal ID.
  • Some mandates have a 32-character hexadecimal ID.
  • Some records carried over from V1 and V2 have composite IDs of 22 to 80 characters.

Store IDs as strings that support up to 80 characters, and do not validate their shape.

Foreign keys are named after the resource they point to: payeeId, payerId, payeeEngagementId. References to vault files use fileId (or a name ending in FileId, such as attachmentFileId).

Casing

  • Field names are camelCase: externalId, dueDate, payeeAccountId.
  • Enum values are TitleCase: Activated, PaymentInTransit, PartiallyPaid.

Some values retain the spelling of an outside standard. Sort directions are asc and desc, OAuth values follow the OAuth specifications, and tax form types start with a digit (1099Nec).

The allowed expand values are case-sensitive and listed for each endpoint. See Filtering, sorting, and expanding.

Dates and times

  • Timestamps are ISO 8601 in UTC with a Z: "2026-03-27T10:30:00Z".
  • Calendar dates with no time are YYYY-MM-DD: "2026-04-15".

Money

Amounts are JSON numbers in major units, such as 1250.00 for $1,250. They are never strings or integer cents. Set the currency once on the document (the invoice, payable, or payroll run) in a currency field such as "USD". Every amount on that document uses that currency.

The API rejects an amount sent as a string, or with more decimal places than the currency allows, with 422 ValidationError.

Fields most resources share

Resources that you create and query carry a common set of fields:

FieldWhat it holds
idThe Wingspan ID.
statusThe lifecycle state, on resources that have one.
eventsTimestamps for each transition, each named {transition}At: createdAt, openedAt, paidAt. A missing key means the transition hasn't happened.
actorsWho made each transition, each named {transition}By: createdBy, paidBy. The value is the ID of the Person or ServiceAccount, or SYSTEM when Wingspan made the change itself.
metadataYour own key-value strings. Up to 50 keys, keys up to 40 characters, values up to 500 characters.
externalIdYour own ID for the record, on resources you'd reconcile with another system.

Here is a trimmed payable as an example:

// (trimmed)
{
  "id": "b3VhT6gJq1MzR8xKd5WnPe",
  "externalId": "INV-2026-0412",
  "status": "Opened",
  "currency": "USD",
  "amount": 1250.00,
  "dueDate": "2026-04-15",
  "events": {
    "createdAt": "2026-04-01T14:02:11Z",
    "openedAt": "2026-04-01T14:05:40Z"
  },
  "actors": {
    "createdBy": "Hn4dW8kPq2ZxM7vRt5LcJa",
    "openedBy": "Hn4dW8kPq2ZxM7vRt5LcJa"
  },
  "metadata": {
    "costCenter": "east-region"
  }
}

The events timestamps line up with webhook event names. For example, events.paidAt on an invoice corresponds to the Invoice.Paid event. Not every transition has a subscribable event yet. Event types lists the ones you can subscribe to.

externalId

Use externalId to store the ID your own system uses for a record. Every read returns it. List endpoints that support it accept filter[externalId][eq]=..., so you can look up a record by your ID.

externalId is unique per Account and resource type. Creating a second record of the same type with the same externalId returns 409 ResourceConflict. Create is never an upsert, so to recover, look the existing record up by externalId and update it instead.

externalId is a real field. Don't put your IDs in metadata when the resource has an externalId.

metadata

metadata holds strings only. It's for your own tags and references, not for data Wingspan acts on. A few list endpoints can filter on metadata keys, and their reference pages say so.

State changes use action endpoints

To move a resource through its lifecycle, call a verb on it: POST /v3/payments/payables/{payableId}/open, POST /v3/platform/batches/{batchId}/process. You never change status with a PATCH.

This keeps each transition explicit. The verb determines the resulting status, the events.*At timestamp, and the webhook event name. For example, open returns status: Opened and adds events.openedAt.

An action returns 200 with the updated resource. It returns 409 InvalidStateTransition when the resource is not in an allowed state.

Updating a resource

Send only the fields you want to change in a PATCH body. There is no update mask.

  • A field you send with a value replaces the stored value.
  • A field you leave out stays as it is.
  • To clear a field, send null if the reference marks the field nullable. A few string fields, such as a batch's name, clear with an empty string instead; the field description says so. If neither is documented, the field can't be cleared.
  • How metadata updates depends on the operation. Some merge the keys you send into the stored map, and others replace the whole map. The field description in the reference says which.

Some endpoints reject fields they don't recognize with 422 ValidationError. Others ignore them. Check field names against the reference: a misspelled field on an endpoint that ignores it leaves the stored value unchanged.

Status codes

CodeMeaning
200A read, an update, or an action that returns data
201A resource was created
202Long-running work was accepted. See Async operations
204A delete succeeded. There's no body.
302A file download redirect. See Files and documents

Error codes are listed in Errors.

Every GET also answers HEAD with the same status and headers and no body.

Related pages


Did this page help you?