Worksites
Record the physical locations where your employees work, mark a primary worksite, and assign worksites to Employee engagements in the V3 API.
A worksite is a physical work location your Account owns, such as an office, a clinic, or a warehouse. This page shows you how to create worksites, choose a primary one, and assign them to Employee engagements.
What a worksite is for
Worksites apply to Employee work. When an Employee engagement names a worksite, Wingspan uses that worksite for state income tax assignment on the employee's pay statements. An Employee engagement with no worksite is treated as remote work.
Contractor engagements don't need a worksite.
| Where you set it | Field | What it means |
|---|---|---|
| An Engagement template | worksiteId or isRemote | The default location for Employee work on that template. |
| A PayeeEngagement | worksiteId | Where this employee works. Leave it out, or set it to null, for remote work. |
Create a worksite
POST /v3/payments/worksites creates one. name and address are required. Each worksite name must be unique in your Account.
curl -X POST https://api.wingspan.app/v3/payments/worksites \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Northwind Sacramento clinic",
"externalId": "site-sac-01",
"address": {
"line1": "200 Example Way",
"city": "Sacramento",
"subdivision": "CA",
"postalCode": "95814",
"countryCode": "US"
},
"isPrimary": true
}'// 201 Created (trimmed)
{
"id": "Wk4sT9pLq2Xn7Rb3Vd8HcA",
"accountId": "z9uv0jPAxTqSLs5UKwv1DE",
"externalId": "site-sac-01",
"name": "Northwind Sacramento clinic",
"address": {
"line1": "200 Example Way",
"city": "Sacramento",
"subdivision": "CA",
"postalCode": "95814",
"countryCode": "US"
},
"isPrimary": true,
"events": { "createdAt": "2026-09-25T15:00:00Z" }
}| Field | What it's for |
|---|---|
name | Required. |
address | Required. line1, city, subdivision (the two-letter state code, such as CA), postalCode, and countryCode (ISO 3166-1 alpha-2) are all required. line2 is optional. The address can't be changed later. |
isPrimary | Makes this your Account's primary worksite. Defaults to false, except that your first worksite is always primary. |
externalId | Your own ID for the location. Wingspan doesn't check it for uniqueness on worksites, so use a value you keep unique. |
metadata | Up to 50 keys of your own data. |
The worksite belongs to the Account you're acting as. To create one for a child Account, send X-Wingspan-Account. See Acting on behalf of Accounts.
List and read worksites
GET /v3/payments/worksiteslists your worksites, paginated. Filter withfilter[isPrimary][eq]=trueorfilter[externalId][eq]=site-sac-01.GET /v3/payments/worksites/{worksiteId}returns one.
curl -G https://api.wingspan.app/v3/payments/worksites \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
--data-urlencode "filter[externalId][eq]=site-sac-01"Update a worksite
PATCH /v3/payments/worksites/{worksiteId} changes name, isPrimary, externalId, and metadata. Fields you leave out stay the same.
curl -X PATCH https://api.wingspan.app/v3/payments/worksites/Wk4sT9pLq2Xn7Rb3Vd8HcA \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Northwind Sacramento clinic (Midtown)" }'- Address. You can't change it. If a location moves, create a new worksite and point engagements at it.
- Primary. Send
"isPrimary": trueto make a worksite primary. The previous primary is demoted. You can't sendfalse; to move the primary flag, promote another worksite instead. externalId. You can set or replace it, but not clear it. An empty string returns422.metadata. Sending it replaces the stored metadata. You can't clear metadata through this endpoint.
Delete a worksite
DELETE /v3/payments/worksites/{worksiteId} returns 204. It returns 422 if the worksite is your primary worksite or if any engagement still references it. Move those engagements to another worksite, or promote another worksite to primary, then delete.
Assign a worksite to an employee
Set worksiteId when you create the PayeeEngagement, or change it later with PATCH:
curl -X PATCH https://api.wingspan.app/v3/payments/payee-engagements/ru9zEafz6i4WRlIOA10wAk \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "worksiteId": "Wk4sT9pLq2Xn7Rb3Vd8HcA" }'Send "worksiteId": null to switch the engagement to remote work. See Engagements and Payroll runs.
What can go wrong
Status and code | Cause | Fix |
|---|---|---|
422 ValidationError on create | A required field is missing, such as address.countryCode, the name is already used by another worksite, or you sent a field the endpoint doesn't accept. | Read errors[]. |
422 ValidationError on update | You sent isPrimary: false, an address, an empty externalId, or a name another worksite already uses. | Promote another worksite, or create a new one for a new address. |
422 ValidationError when you assign a worksite to an Employee PayeeEngagement | The worksite's country or state isn't supported for W-2 payroll (payments.WorksiteCountryUnsupported, payments.WorksiteJurisdictionUnsupported), or the state isn't enabled on your employer payroll profile (payments.WorksiteJurisdictionNotEnabled). | Choose a worksite in an enabled state, or enable the state on your payroll profile. |
422 ValidationError on delete | The worksite is primary, or an engagement still uses it. | Promote another worksite or reassign the engagements first. |
Webhooks
Worksites don't emit webhook events. Read the worksite after each change.
Related
Updated about 1 hour ago