Parent and child Accounts
Create child Accounts under a parent, list and navigate the hierarchy, and act on a child Account with one credential in the Wingspan V3 API.
Use this page to create a child Account under a parent Account, find Accounts in your hierarchy, and send requests on behalf of a child. In V1 these were "organization child accounts" created with POST /users/organization/user and then associated; in V3 it's one call.
How the hierarchy works
Every Account can have one parent (parentAccountId). A caller with access to a child Account can act on it by sending X-Wingspan-Account: {childAccountId}. Access to the parent doesn't reach the child by itself: a Person reaches a child when they created it, are its Principal, or hold an Authorization on it or on an ancestor granted with isAccountScopeInclusiveOfDescendants: true. See Team access and roles. The request then runs as if the child made it: resources are read from and written to the child, rate limits are charged to the child (and its Organization), and webhooks are routed to the child.
Two rules shape how you build the tree:
- Parent changes go through
moveandassociate.POST /v3/platform/accounts/{accountId}/movere-parents an Account, andPOST /v3/platform/accounts/{accountId}/associateattaches a standalone Account to a parent. A move that would create a cycle or cross an organization boundary returns409. - Inheritance is set per link. The link between a child and its parent holds an
inheritancemode for each kind of setting. See Inheritance between parent and child.
Prerequisites
- Either a Person session (a user login) or a ServiceAccount. See Team access and roles.
- Write access on the parent Account.
- A Person must use an unbound session (not tied to a specific Account; an Account-bound session gets
403 AccountMismatch) and must not sendX-Wingspan-Account. The Account you're creating doesn't exist yet. - A ServiceAccount can create child Accounts only, so
parentAccountIdis required. It needs theusers.account:writescope and Write authority on the parent that reaches new child Accounts. With an API key, sendX-Wingspan-Account. An Account session minted by a ServiceAccount must include descendants, and its parent must be the bound Account or one of its descendants. A ServiceAccount can't create a root Account or link a Person to an Account.
1. Create the child Account
Call Create an Account with parentAccountId. The child joins the parent's Organization automatically. You can also pass inheritance here (see Inheritance between parent and child). An organizationId is accepted only if it matches the parent's Organization.
curl -X POST https://api.wingspan.app/v3/platform/accounts \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"parentAccountId": "Nw7kQ2pLx9RtVb3mHc5dZa",
"externalId": "entity-alpha",
"profile": {
"supportEmail": "[email protected]",
"timezone": "America/Los_Angeles"
},
"metadata": { "region": "west" }
}'// 201 Created (trimmed)
{
"id": "Ap4tYs8KqW2nLm6xRb1cVe",
"status": "Active",
"parentAccountId": "Nw7kQ2pLx9RtVb3mHc5dZa",
"organizationId": "Og3hTq8VnK1pXw6cMd4bRs",
"externalId": "entity-alpha",
"profile": {
"supportEmail": "[email protected]",
"timezone": "America/Los_Angeles"
},
"metadata": { "region": "west" }
}profile is display information: logoUrl, brandColor, supportEmail, supportPhone, locale, and timezone (see Branding and customization). The legal and tax identity of record (legal name, EIN, entity type) lives on the Account's compliance entity, not here. When you update an Account later, sending profile replaces the whole profile, so include every field you want to keep. See Tax information and Identity verification.
A child Account has no principal (the person who represents it) until you add one. The creator is not assumed to be the child's principal. To designate one, create a Stakeholder with isPrincipal: true. See Team access and roles.
What can go wrong
| Status | code | Why |
|---|---|---|
403 | AccountMismatch | A Person session is bound to an Account, or you lack Write access on the parent. |
403 | Forbidden | A ServiceAccount has no parentAccountId, or its access doesn't reach new child Accounts. |
409 | ResourceConflict | The externalId is already used. Create is never an upsert. Look up the existing Account with filter[externalId][eq]. |
422 | ValidationError | A field is invalid, for example an organizationId that doesn't match the parent's Organization. |
2. Find Accounts in the hierarchy
List a parent's direct children with List child Accounts:
curl https://api.wingspan.app/v3/platform/accounts/Nw7kQ2pLx9RtVb3mHc5dZa/children \
-H "Authorization: Bearer $WINGSPAN_TOKEN"Other lookups:
| To | Call |
|---|---|
| Walk up from a child to the root (breadcrumbs) | GET /v3/platform/accounts/{accountId}/ancestors |
| Find a child by your own ID | GET /v3/platform/accounts?filter[externalId][eq]=entity-alpha |
| List all children of a parent, filtered | GET /v3/platform/accounts?filter[parentAccountId][eq]={parentId}&filter[status][eq]=Active |
| Read one Account | GET /v3/platform/accounts/{accountId} |
| Read the whole descendant tree | GET /v3/platform/accounts/{accountId}/hierarchy |
Closed Accounts are never returned. To walk the tree one level at a time, use children.
3. Act on a child Account
Send the child's ID in X-Wingspan-Account. Creating a child Account gives you Admin access to it, so the Person who created it can do this right away. Here that Person creates a payee owned by the child:
curl -X POST https://api.wingspan.app/v3/payments/payees \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "X-Wingspan-Account: Ap4tYs8KqW2nLm6xRb1cVe" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "profile": { "email": "[email protected]" }, "context": "Contractor", "externalId": "alpha-c-1042" }'The payee belongs to Alpha LLC, not to the parent. A header that points outside the caller's reach returns 403 AccountMismatch. Person-only endpoints (under /v3/platform/persons/{personId}/...) reject the header with 400 AccountScopeNotApplicable. Full rules are in Acting on behalf of Accounts.
Important: If you cache API responses in a shared layer, include
X-Wingspan-Accountin the cache key. Leaving it out can serve one Account's data to another.
Bind a session to an Account instead of sending the header
If a user works inside one Account for a whole session, replace their session with one bound to that Account. Calls without the header then default to the bound Account. Use Update a session on the caller's own session:
curl -X PATCH https://api.wingspan.app/v3/platform/sessions/Ss1dKw6tQm3pXn8vLr2bHc \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accountScope": {
"accountId": "Nw7kQ2pLx9RtVb3mHc5dZa",
"shouldIncludeDescendants": true
}
}'The response carries a replacement token. Use it for every later call; the old session is revoked. Send "accountScope": null to go back to an unbound session.
For a backend with no human in the loop, a ServiceAccount can mint an Account-scoped session instead. See Embed Wingspan in your app.
4. Close an Account
DELETE /v3/platform/accounts/{accountId} closes an Account and returns 204. Closed is terminal: the Account can't be used as an access context afterward. It returns 409 PrincipalLockedByActiveEngagement while the Account has an active Employee or EmployeeOfRecord engagement.
Inheritance between parent and child
Each child has a parent link that says which settings it takes from its parent. Read it with GET /v3/platform/accounts/{accountId}/parent-link and change it with PATCH /v3/platform/accounts/{accountId}/parent-link. A root Account has no link and returns 404. The link is never created by a write. To change the parent, use move.
inheritance has one mode for each of branding, payerSettings, paymentMethods, internalAccounts, customFields, payeeRequirements, rateCards, and complianceEntity:
| Mode | Effect |
|---|---|
Overridable | The child gets the parent's value only where the child hasn't set its own. This is the default for every kind you omit. |
Enforced | The parent's populated values take precedence over the child's. |
Disabled | Nothing of this kind is inherited over this link. |
The PATCH needs Write authority on the parent Account. The child's own admin can't change the link without it. A ServiceAccount's authority must reach descendants. metadata on the link merges by key, and null removes a key.
Account statuses
| Status | Meaning |
|---|---|
Active | The initial and normal state. |
Suspended | A recoverable hold. The Account stays readable so an authorized person can resolve it. |
Closed | Terminal. Not returned by list calls and not usable as an access context. |
An Active Account can still be blocked from moving money until its verification requirements are complete. See Requirements and eligibility.
Events
Account.Updated and Account.Closed are available as webhooks. To receive events for a parent and all its children on one endpoint, subscribe with an Account scope and shouldIncludeDescendants: true:
{
"url": "https://hooks.example.com/wingspan",
"subscribedEvents": ["Payable.*", "Invoice.*", "Account.*"],
"scope": { "type": "Account", "id": "Nw7kQ2pLx9RtVb3mHc5dZa", "shouldIncludeDescendants": true }
}Each event carries routingSubject, the Account it's about, so you can route it. Record the ID from the 201 response when you create an Account. See Create a subscription.
Next steps
- Organizations for when to split your business into Accounts
- Team access and roles to give people and systems access to each Account
- Embed Wingspan in your app if the child Accounts are your customers
Updated 24 minutes ago