Pay employees

Turn the hours your employees log into pay statements, and follow each Employee payroll run from draft to paid with the Wingspan V3 API.

Wingspan pays employees in Employee payroll runs. Your employee pay schedule creates a pay period and an on-cycle run for it, employees log their hours against that period, and each work log you approve becomes a pay statement in the run. Wingspan then pays the run on its pay date. This page shows you how each step works and what to do at each one.

How pay periods and runs work

Each pay period on your employee schedule gets one on-cycle payroll run. Wingspan creates the next pay period and its run as each period closes, so there's always a run open for the current period. Find it with GET /v3/payments/payroll-runs.

curl -G https://api.wingspan.app/v3/payments/payroll-runs \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  --data-urlencode "filter[type][eq]=Employee"
// 200 OK (trimmed)
{
  "data": [
    {
      "id": "TVDpIcgEQZ0E64NXJeC2Ps",
      "type": "Employee",
      "status": "Draft",
      "cycleType": "OnCycle",
      "payrollNumber": "K4TQZ-001-PR",
      "currency": "USD",
      "payPeriodId": "ha2GMh5tQiSpEyCNFbzd5D",
      "payPeriodStartDate": "2026-11-06T12:00:00.000Z",
      "payPeriodEndDate": "2026-11-12T12:00:00.000Z",
      "payDate": "2026-11-13T12:00:00.000Z",
      "approvalDeadline": "2026-11-12T12:00:00.000Z",
      "totalAmount": 0
    }
  ],
  "pagination": { "nextPageToken": "" }
}
FieldWhat it means
payPeriodIdThe pay period the run pays. Use it when you log hours and preview pay.
payDateThe day employees are paid.
approvalDeadlineThe last time pay statements can be added to or changed in the run. Approve hours before it.
totalAmountWhat Wingspan debits from your payroll funding source for the run: gross pay plus your employer taxes.

List pay periods themselves with GET /v3/payments/pay-periods.

Log hours

Hours go on a work log for one assignment and one pay period. Employees can log their own hours in Wingspan or in your app, then submit the work log for approval with POST /v3/payments/work-logs/{workLogId}/submit. When the pay period ends, Wingspan moves any work logs that are still being edited into review, so nothing is left out of the run.

To send hours from your own system, such as a scheduling or timekeeping tool, create the work log yourself with POST /v3/payments/work-logs. A work log you create is ready for your approval right away: it starts in InReview, so you don't submit it.

curl -X POST https://api.wingspan.app/v3/payments/work-logs \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "payeeEngagementId": "j88P41LYjxNCLZ6CmGNHqH",
    "payPeriodId": "ha2GMh5tQiSpEyCNFbzd5D",
    "externalId": "timesheet-1042-2026-11-12"
  }'
// 201 Created (trimmed)
{
  "id": "mvG1MkSioJyHDrW5vRGlMf",
  "workLogNumber": "Q2NVR-001L",
  "status": "InReview",
  "payeeEngagementId": "j88P41LYjxNCLZ6CmGNHqH",
  "payPeriodId": "ha2GMh5tQiSpEyCNFbzd5D",
  "totalAmount": 0,
  "currency": "USD"
}

Then add each entry as a work item with POST /v3/payments/work-items. The attributes follow the engagement's work definition. The default hourly work definition takes hours and overtimeHours.

curl -X POST https://api.wingspan.app/v3/payments/work-items \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "workLogId": "mvG1MkSioJyHDrW5vRGlMf",
    "workDefinitionId": "RG230mb8G9wPIKxfsUR3lu",
    "timestamp": "2026-11-10T16:00:00Z",
    "attributes": { "hours": 8, "overtimeHours": 2 }
  }'
// 201 Created (trimmed)
{
  "id": "aixxMYORNYvz7kcS0CjYUK",
  "workLogId": "mvG1MkSioJyHDrW5vRGlMf",
  "status": "Active",
  "attributes": { "hours": 8, "overtimeHours": 2 },
  "calculations": {
    "result": 220,
    "items": [
      { "earningType": "Regular", "hoursAttribute": "hours", "interpolatedFormula": "8 * 20", "result": 160 },
      { "earningType": "Overtime", "hoursAttribute": "overtimeHours", "interpolatedFormula": "2 * 30", "result": 60 }
    ]
  }
}

Wingspan prices each entry from the assignment's rate card and labels each part with the earning type it becomes on the pay statement. The work log's totalHours and totalAmount update as you add entries. Work logs and rate cards covers work definitions, rate cards, and requesting edits.

Approve hours

Approve a work log with POST /v3/payments/work-logs/{workLogId}/approve. Approving it creates a pay statement for the employee in that pay period's run, and the run's totalAmount goes up by the statement's gross pay plus your employer taxes. For the work log above, that's $220.00 of gross pay plus $25.85 of employer taxes, so the run's totalAmount becomes 245.85.

curl -X POST https://api.wingspan.app/v3/payments/work-logs/mvG1MkSioJyHDrW5vRGlMf/approve \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
// 200 OK (trimmed)
{
  "id": "mvG1MkSioJyHDrW5vRGlMf",
  "payeeEngagementId": "j88P41LYjxNCLZ6CmGNHqH",
  "payPeriodId": "ha2GMh5tQiSpEyCNFbzd5D",
  "status": "Approved",
  "totalAmount": 220.00,
  "currency": "USD",
  "convertedResourceType": "PayStatement",
  "convertedResourceId": "GUAJztqobp9aoa1fCRJ92f"
}

Approve every work log before the run's approvalDeadline. To have Wingspan approve submitted work logs for you, set autoApproveWorkLogs.enabled to true with PATCH /v3/payments/work-logging-configs. See Work logs and rate cards.

Before you approve, you can see what the employee will take home with a gross-to-net preview. See Pay statements.

To keep hours from being paid, reject the work log with POST /v3/payments/work-logs/{workLogId}/reject and a reason. A rejected work log is final.

Employee run statuses

stateDiagram-v2
  [*] --> Draft
  Draft --> PendingApprovals: pay period ends
  PendingApprovals --> Finalized: approval deadline, or finalize
  Finalized --> Processing: funding debit starts
  Processing --> Paid
  Processing --> PartiallyPaid
  Draft --> Cancelled
  PendingApprovals --> Cancelled
  Finalized --> Cancelled
  Processing --> Failed
StatusWhat it means
DraftThe pay period is in progress. Approving a work log adds its pay statement to the run.
PendingApprovalsThe run is open for approval. Approve the period's work logs before the approvalDeadline.
FinalizedApprovals are closed and the amounts are locked. No money has moved yet.
ProcessingWingspan has debited your funding source and is paying employees. Deposits land on the pay date.
PaidEmployees have been paid. Each pay statement shows Paid.
PartiallyPaidSome of the run's pay statements were paid. Check each statement's status.
CancelledThe run was cancelled and won't be paid.
FailedFunding failed. failureReason says why.

At the approval deadline, Wingspan finalizes the run for you. To close approvals early, call POST /v3/payments/payroll-runs/{payrollRunId}/finalize. If you call with a person's login session, finalizing needs a recent MFA step-up: without one it returns 403 StepUpMfaRequired. Create and verify an MFA challenge with requiredFor: "HighRiskWriteAction", then retry. A backend calling with an API key has no session to step up. Your account can have one Employee run Finalized or Processing at a time.

Read a run's totals with ?expand=Breakdown on GET /v3/payments/payroll-runs/{payrollRunId}.

curl https://api.wingspan.app/v3/payments/payroll-runs/TVDpIcgEQZ0E64NXJeC2Ps?expand=Breakdown \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
// 200 OK (trimmed)
{
  "id": "TVDpIcgEQZ0E64NXJeC2Ps",
  "type": "Employee",
  "status": "Draft",
  "totalAmount": 245.85,
  "breakdown": {
    "payStatementsCount": 1,
    "grossPay": 220,
    "netPay": 200.31,
    "grossLiabilityAmount": 245.85,
    "payeeTaxes": [
      { "taxCodeId": "OhbVrpoiVgRV5IfLBcbfno", "amount": 13.64, "ytdAmount": 13.64, "qtdAmount": 13.64, "mtdAmount": 13.64 }
    ],
    "payerTaxes": [
      { "taxCodeId": "kIUpkDyr7OSJoRu1XXdo0c", "amount": 13.64, "ytdAmount": 13.64, "qtdAmount": 13.64, "mtdAmount": 13.64 }
    ]
  }
}

grossLiabilityAmount is what the run costs you: gross pay plus your employer taxes. Each tax entry carries the run's amount and the year-, quarter-, and month-to-date totals.

Funding and failures

Wingspan debits the run's totalAmount from your payroll funding source. If the debit can't go through, the run moves to Failed with one of these failureReason codes.

failureReason codeWhat happenedWhat to do
FundingSourceMissingThe account has no payroll funding source.Set one. See Payroll settings and funding.
FundingAccountNotReadyThe funding account isn't verified or active yet.Finish setting up the funding account, then run payroll again.
InsufficientFundsThe funding account didn't have enough money.Add funds, then run payroll again.
FundingSubmissionFailedWingspan couldn't submit the debit.Run payroll again.
FundingDebitReturnedThe debit went through and your bank later returned it.Contact support to pay the run again.

For every code except FundingDebitReturned, no money left your account.

Off-cycle runs

To pay outside the on-cycle run for a pay period, create an off-cycle run with POST /v3/payments/payroll-runs. It starts in Draft.

curl -X POST https://api.wingspan.app/v3/payments/payroll-runs \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "type": "Employee", "payPeriodId": "ha2GMh5tQiSpEyCNFbzd5D" }'
// 201 Created (trimmed)
{
  "id": "4IWrXPvhsBkDa9U4UqGWlG",
  "type": "Employee",
  "status": "Draft",
  "cycleType": "OffCycle",
  "payrollNumber": "K4TQZ-002-PR",
  "payPeriodId": "ha2GMh5tQiSpEyCNFbzd5D",
  "payDate": "2026-11-13T12:00:00.000Z",
  "approvalDeadline": "2026-11-12T12:00:00.000Z",
  "totalAmount": 0
}

A pay period can have one open off-cycle Employee run at a time. Creating a second returns 409 ResourceConflict. Delete an off-cycle run while it's still a draft with DELETE /v3/payments/payroll-runs/{payrollRunId}.

Cancel a run

Cancel a run that hasn't started paying with POST /v3/payments/payroll-runs/{payrollRunId}/cancel. To take one employee out of a run instead, cancel their pay statement. See Pay statements.

Track a run

To follow a run, read it. Its events object records when it reached each status: draftAt, pendingApprovalsAt, finalizedAt, processingAt, paidAt, cancelledAt, and failedAt. Once the run is paid, each pay statement's own events records when the employee's deposit was sent (paidAt) and when it settled (depositedAt).

What can go wrong

Status and codeCauseFix
422 payments.WorkLogPayrollProfileMissing on approveThe employee's account isn't linked, or there's no worksite on the assignment and no home address or primary worksite to tax them from.Link the employee's account, or add a worksite. The work log stays InReview until you approve it again.
403 StepUpMfaRequired on finalizeThe login session has no recent MFA step-up.Complete an MFA challenge with requiredFor: "HighRiskWriteAction", then retry.
409 on finalizeAnother Employee run is Finalized or Processing, or this run isn't in a status that can be finalized.Wait for the other run to be paid, or check this run's status.
409 ResourceConflict on an off-cycle runThe pay period already has an open off-cycle Employee run.Use the open run, or wait until it's paid.
409 on cancelThe run is already Processing or Paid.Contact support.
403 payments.WorkLogDeleteForbiddenYou tried to delete a work log as the employer.Reject it with a reason instead.
422 on submitThe work log is already InReview. Work logs you create start there.Approve it. You only submit work logs the employee is still editing.

Related pages


Did this page help you?