Acting on behalf of Accounts

Use the X-Wingspan-Account header to read and write in a child or authorized Wingspan Account, and learn which endpoints reject it.

Send a request in a child or authorized Wingspan Account by using the X-Wingspan-Account header instead of placing Account IDs in URLs or request bodies.

How it works

Every authenticated request runs in the context of one Account. By default, that's the Account your token is tied to. Add X-Wingspan-Account with another Account's ID and the request runs as if that Account made it:

curl -g "https://api.wingspan.app/v3/payments/payables?filter[status][eq]=Opened" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "X-Wingspan-Account: 7KfPz2QwLm9XcV4nRt1YsB"

With the header set:

  • Reads return the target Account's records.
  • Writes are saved under the target Account.
  • Webhook events go to the target Account's subscriptions.
  • Rate limits are charged to the target Account and its Organization, not to yours. See Rate limiting.
  • Idempotency-Key values are tracked per target Account, so the same key under two different Accounts refers to two different requests.

The header decides where the work happens; it does not authenticate you. Your bearer token identifies the caller, and actors.*By fields record that caller as the person who made the change.

Who can act on which Account

Wingspan checks every request that sets the header. You can target an Account when one of these is true:

Your credentialAccounts you can target
A Person's session or API keyAccounts you're the Principal of, and Accounts that have given you access through an Authorization (adding you as a Stakeholder or teammate creates one, and so does creating the Account). Accounts below those in the hierarchy only if the Authorization was granted to include them. Being the Principal or a Stakeholder of a parent Account doesn't by itself reach its children. A Person's session bound to an Account at login can still select any Account the Person can access.
A ServiceAccount API keyAccounts inside the ServiceAccount owner's boundary, or covered by an Authorization grant. ServiceAccount keys must send X-Wingspan-Account on every Account-scoped request.
A session minted for an Account by a ServiceAccountThat Account, plus the Accounts below it only if the session was created to include them.
An OAuth access tokenOnly the Account the user consented for.

If the check fails, Wingspan rejects the request with 403 AccountMismatch and makes no change. See Parent and child accounts and Team access and roles to learn how hierarchy and access grants work.

Accounts in the URL

Some /v3/platform/accounts/{accountId}/... endpoints name an Account in the path. The path Account must match the Account your request acts on. To work on a child Account, set X-Wingspan-Account to that child's ID too. If the path and header name different Accounts, the request returns 403 AccountMismatch.

Endpoints that reject the header

Some endpoints aren't about an Account, so the header has no meaning there. Instead of ignoring it, they reject the request with 400 AccountScopeNotApplicable. That way a mistake in your client shows up right away instead of reading or writing the wrong data.

Kind of endpointExamples
Person-only. These belong to the signed-in human, never to an Account.Your own identity verifications (/v3/onboarding/identity-verifications), MFA factors and challenges (/v3/platform/mfa-factors, /v3/platform/mfa-challenges), your notification inbox and channel settings (/v3/platform/persons/{personId}/notifications), and creating a new Account (POST /v3/platform/accounts)
Global. These work the same for every caller.Sign-in and sessions (/v3/platform/sessions), OAuth endpoints (/v3/platform/oauth/...)
Owner in the request. These take the owner as part of the request instead.Webhook subscriptions (/v3/platform/webhooks) and the event log (/v3/platform/events)

Person-only endpoints also reject ServiceAccount keys and Account-bound sessions made without a Person, with 400 AccountScopeNotApplicable, because there's no Person to act as.

Some endpoints can belong to either an Account or a Person, such as compliance entities at /v3/platform/compliance-entities. On those, sending X-Wingspan-Account selects the Account. Leaving it out, from a Person's session, selects the Person.

Caching responses safely

Important: Include X-Wingspan-Account and Authorization in the cache key for every shared cache, including a CDN, gateway, reverse proxy, application cache, or SDK cache. The same URL returns different data for different Accounts. A URL-only cache can serve one Account's payees, payables, or tax data to another Account.

Apply the same rule to data that your code memoizes by URL and to per-Account ETag stores for If-Match. Partition each store by the X-Wingspan-Account value.

What can go wrong

ResponseCauseFix
403 AccountMismatchYou don't have access to the target Account, or the Account in the path differs from the one you're acting asCheck the Account ID, your Authorization for that Account, and that path and header match
400 AccountScopeNotApplicableThe endpoint is Person-only, global, or takes its owner in the requestRemove the header for this call
422 ValidationError on X-Wingspan-Account, RequiredA ServiceAccount key called an Account-scoped endpoint without the headerSend X-Wingspan-Account
422 ValidationError on X-Wingspan-Account, InvalidFormatThe value isn't a well-formed Account IDSend the Account's id exactly as Wingspan returned it

From V1

V1 integrations used the X-WINGSPAN-USER header with a child user ID to act on a child account. In V3, send X-Wingspan-Account with the child's Account ID. V3 checks your access to the target Account on every request.

Related pages


Did this page help you?