Payers Submitting Work Logs/Converting to Payables

Payers Submitting Work Logs/Converting to Payables

Table of Contents

  1. Overview
  2. How Payment Amounts Are Calculated
  3. Key Concepts
  4. Entity Relationships
  5. Prerequisites
  6. Authentication
  7. Step-by-Step Flow
  8. API Reference
  9. Supporting Operations
  10. Work Definitions & Attributes
  11. Rate Cards
  12. Rate Calculation Examples
  13. Error Handling
  14. FAQ
  15. Complete Example Flow

Overview

The Work Logging API allows payers to programmatically submit work performed by contractors and convert it into invoices (payables) for payment. When you submit work items, the system automatically calculates payment amounts based on pre-configured rate cards and formulas.

Base URLs:

EnvironmentURL
Productionhttps://api.wingspan.app
Staginghttps://stagingapi.wingspan.app

Key Flow:

┌─────────────┐      ┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│  Create     │      │  Add Work   │      │  Convert    │      │  Invoice    │
│  Work Log   │ ───► │    Items    │ ───► │ to Invoice  │ ───► │  Created    │
└─────────────┘      └─────────────┘      └─────────────┘      └─────────────┘
     (per payee)      (with attributes)    (closes log)         (status: Open)

What This API Enables:

  • Open a work log (batch container) for a contractor
  • Add work items with service details — amounts are auto-calculated
  • Convert to invoice when ready for payment

How Payment Amounts Are Calculated

When you submit a work item, the system automatically calculates the payment amount using three components configured during onboarding:

ComponentWhat It IsExample
Work DefinitionTemplate defining what data to captureserviceCategory, units, isLocalityPay
Rate CardStored pricing valuesstandardUnitRate: $25, localityBonus: $5
Rate CalculationFormula logic that computes payment(units × standardUnitRate) + (isLocalityPay ? localityBonus : 0)

These are linked together in an Engagement, which is then assigned to contractors via a PayerPayeeEngagement (PPE).

Calculation Process:

  1. You submit a work item with attributes (e.g., units: 4, isLocalityPay: true)
  2. System validates attributes against the work definition schema
  3. Rate calculation formulas are evaluated using work item attributes + rate card values
  4. All matching formulas are summed together (Sum selection strategy)
  5. Final calculated amount is stored on the work item

Example Calculation:

Work Item Attributes: { units: 2, isLocalityPay: true, isFirstEncounter: true }
Rate Card Values: { unitRate: 25, localityBonus: 5, retainer: 200 }

Formula 1: units × unitRate = 2 × 25 = $50
Formula 2: units × localityBonus = 2 × 5 = $10  (matches because isLocalityPay=true)
Formula 3: retainer = $200  (matches because isFirstEncounter=true)

Total: $50 + $10 + $200 = $260

Key Concepts

Entities

EntityDescription
EngagementTemplate defining work types, rate calculations, and rate cards for a category of work
Payer-Payee-Engagement (PPE)Links a payer to a specific payee (contractor) under an engagement
Work DefinitionSchema defining what attributes to capture for a type of work
Rate CardPricing table with named rate values
Rate CalculationFormula logic that computes payment amounts from attributes + rate card
Work LogContainer for work items, tied to one PPE (one contractor)
Work ItemSingle service entry with attributes (date, category, units, etc.)
InvoicePayment document generated from a work log

Important Constraints

  • One Work Log = One Payee: Each work log is tied to a single PPE (contractor)
  • Multiple Work Items per Log: A work log can contain many work items
  • Multiple Work Definitions per Log: Work items can use different work definitions within the same engagement
  • No Bulk Multi-Payee: To submit work for multiple contractors, create separate work logs
  • One Open Log per PPE: Only one work log can be open per contractor engagement at a time

Entity Relationships

Here's how the entities fit together:

Payer (Your Organization)
│
├── Engagement: "Service Type A"
│   │
│   ├── Work Definition: "Standard Services"
│   │   └── Rate Calculation: (formulas for standard services)
│   │
│   ├── Work Definition: "Specialty Services"
│   │   └── Rate Calculation: (formulas for specialty services)
│   │
│   └── Rate Card: (pricing values)
│
├── PPE: Contractor A (payerPayeeEngagementId: "ppe_abc123")
│   └── Work Log → Work Items → Invoice
│
├── PPE: Contractor B (payerPayeeEngagementId: "ppe_def456")
│   └── Work Log → Work Items → Invoice
│
└── PPE: Contractor C (payerPayeeEngagementId: "ppe_ghi789")
    └── Work Log → Work Items → Invoice

Key Insight: The payerPayeeEngagementId uniquely identifies the combination of:

  • Your organization (payer)
  • A specific contractor (payee)
  • The engagement they're assigned to

This is the ID you'll use when creating work logs for each contractor.


Prerequisites

Before submitting work items, you need:

RequirementDescriptionHow to Obtain
API TokenBearer token for authenticationProvided by Wingspan
payerPayeeEngagementIdContractor's assignment to the engagementSee "Getting PPE IDs" below
workDefinitionIdTemplate defining what data to captureProvided by Wingspan during setup

Note: Work definitions, rate cards, rate calculations, and engagements are configured by Wingspan during onboarding. You only need the IDs to submit work.

Getting PPE IDs

To get a contractor's PPE ID, use this endpoint:

GET /payments/payee/{payeeId}/engagement

This returns all engagements for that payee, including the payerPayeeEngagementId:

[
  {
    "payerPayeeEngagementId": "ppe_abc123",
    "engagementId": "eng_xyz789",
    "engagementName": "Service Type A",
    "status": "Active"
  }
]

Important: A contractor can have multiple PPEs if they're under multiple engagements. Filter by engagementName or engagementId to get the correct one.


Authentication

All API calls require a Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json

Contact Wingspan to obtain API credentials for your payer account.


Step-by-Step Flow

Step 1: Identify the Payer-Payee-Engagement (PPE)

Before creating work, you need the PPE ID for the contractor. This links your organization to that contractor under a specific engagement.

Option A — Look up by payee:

GET /payments/payee/{payeeId}/engagement

Option B — Use a known PPE ID (provided during onboarding or stored from previous calls)

Step 2: Create a Work Log

Create a work log for the contractor. This is the container that holds all work items.

POST /payments/work-log

Step 3: Add Work Items

Add one or more work items to the work log. Each work item represents a service performed. Amounts are automatically calculated.

POST /payments/work-item

Step 4: Convert to Invoice

When ready, convert the work log into an invoice. This closes the work log and creates an Open invoice ready for payment.

POST /payments/work-log/{workLogId}/convert

API Reference

1. Create Work Log

Creates a new work log for a specific payer-payee-engagement.

Endpoint: POST /payments/work-log

Request Body:

FieldTypeRequiredDescription
payerPayeeEngagementIdstringYesThe PPE ID linking payer to payee under engagement
startDateDateTimeNoPeriod start date (defaults to now)

Example Request:

curl -X POST "https://api.wingspan.app/payments/work-log" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payerPayeeEngagementId": "ppe_abc123"
  }'

Example Response (200 OK):

{
  "workLogId": "wl_xyz789",
  "workLogNumber": "ABC-001L",
  "payerPayeeEngagementId": "ppe_abc123",
  "engagementId": "eng_xyz789",
  "payerId": "payer_123",
  "payeeId": "payee_456",
  "amount": 0,
  "status": "Draft",
  "startDate": "2026-02-13T20:29:08.568Z",
  "createdAt": "2026-02-13T20:29:08.578Z",
  "updatedAt": "2026-02-13T20:29:08.578Z"
}

Key Response Fields:

FieldDescription
workLogIdUnique ID — use this for adding work items
workLogNumberHuman-readable reference number
status"Draft" initially
amountTotal amount (0 until work items added)

2. Create Work Item

Adds a work item (service entry) to a work log. The payment amount is automatically calculated.

Endpoint: POST /payments/work-item

Request Body:

FieldTypeRequiredDescription
workLogIdstringYesThe work log to add this item to
workDefinitionIdstringYesTemplate that defines required attributes
attributesobjectYesService data (varies by work definition)
timestampDateTimeNoWhen work was performed (defaults to now)

Example Request:

curl -X POST "https://api.wingspan.app/payments/work-item" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workLogId": "wl_xyz789",
    "workDefinitionId": "wd_def456",
    "attributes": {
      "serviceDate": "2026-02-14T12:00:00Z",
      "serviceCategory": "standard",
      "units": 4,
      "isLocalityPay": false,
      "isFirstEncounter": true
    }
  }'

Example Response (201 Created):

{
  "workItemId": "wi_ghi789",
  "workLogId": "wl_xyz789",
  "workDefinitionId": "wd_def456",
  "payerId": "payer_123",
  "payeeId": "payee_456",
  "attributes": {
    "serviceDate": "2026-02-14T12:00:00Z",
    "serviceCategory": "standard",
    "units": 4,
    "isLocalityPay": false,
    "isFirstEncounter": true
  },
  "calculations": {
    "items": [
      {
        "result": 100,
        "formula": "${workItem.units} * ${rateCard.unitRate}",
        "interpolatedFormula": "4 * 25"
      },
      {
        "result": 200,
        "formula": "${rateCard.retainer}",
        "interpolatedFormula": "200"
      }
    ],
    "result": 300,
    "selectionStrategy": "Sum"
  },
  "rateCalculationId": "rc_abc123",
  "createdAt": "2026-02-13T20:29:18.039Z"
}

Key Response Fields:

FieldDescription
workItemIdUnique ID for this work item
calculations.resultTotal calculated amount
calculations.itemsBreakdown of each formula applied
calculations.selectionStrategyHow formulas are combined (typically "Sum")

3. Convert Work Log to Invoice

Converts a work log to an invoice, closing the work log and creating an Open invoice.

Endpoint: POST /payments/work-log/{workLogId}/convert

Parameters:

ParameterValuesDefaultDescription
invoiceStatusDraft, OpenDraftStatus of created invoice

Example Request:

curl -X POST "https://api.wingspan.app/payments/work-log/wl_xyz789/convert" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json"

Example Response (200 OK):

{
  "invoiceId": "inv_jkl012",
  "invoiceNumber": "ABC-001",
  "workLogId": "wl_xyz789",
  "memberId": "payee_456",
  "clientId": "payer_123",
  "amount": 300,
  "currency": "USD",
  "status": "Open",
  "dueDate": "2026-02-28T12:00:00.000Z",
  "lineItems": [
    {
      "description": "Standard Service - 2026-02-14",
      "detail": "Service Date: 2026-02-14\nUnits: 4\nFirst Encounter: true",
      "totalCost": 300,
      "labels": {
        "workItemId": "wi_ghi789"
      }
    }
  ],
  "acceptedPaymentMethods": ["Credit", "ACH", "Manual"],
  "attachments": {
    "invoiceLink": "https://my.wingspan.app/invoice-payment/inv_jkl012"
  },
  "createdAt": "2026-02-13T20:31:29.882Z"
}

Key Response Fields:

FieldDescription
invoiceIdUnique invoice ID
invoiceNumberHuman-readable invoice number
status"Open" — ready for payment
amountTotal invoice amount
lineItemsEach work item becomes a line item
attachments.invoiceLinkURL for the payee to view/pay

Supporting Operations

List Work Logs

Retrieve work logs for your account.

GET /payments/work-log
GET /payments/work-log?filter[payerId]=YOUR_ID&filter[status]=Open

Get Work Log Details

Retrieve a specific work log.

GET /payments/work-log/{workLogId}

List Work Items

Retrieve work items, optionally filtered by work log.

GET /payments/work-item
GET /payments/work-item?filter[workLogId]=wl_xyz789

Update a Work Item

Modify a work item while the log is still in Draft status.

PATCH /payments/work-item/{workItemId}

Delete a Work Item

Remove a work item from a work log.

DELETE /payments/work-item/{workItemId}

Get Engagements

List all engagements configured for your account.

GET /payments/engagement

Get Work Definition Details

Retrieve the schema for a work definition.

GET /payments/work-definition/{workDefinitionId}

Work Definitions & Attributes

Work definitions are configured during onboarding and define the schema for each type of work. Here's an example of what a work definition looks like:

Example Work Definition

Work Definition ID: wd_standard_services

Attribute Definitions:

KeyNameTypeRequiredValid ValuesDescription
serviceDateService DateDatetimeYesISO 8601When service was performed
serviceCategoryService CategoryValueSetYesstandard, specialty, consultationType of service
unitsUnitsNumberNo*0-10Number of units (*Required for certain categories)
serviceLocationService LocationValueSetYesvirtual, inPersonWhere service was performed
isLocalityPayLocality PayBooleanYestrue, falseWhether locality bonus applies
isFirstEncounterFirst EncounterBooleanYestrue, falseFirst encounter with client
notesNotesStringNoAny stringOptional notes

Attribute Types

TypeDescriptionExample Values
DatetimeISO 8601 formatted date/time"2026-02-15T14:00:00Z"
ValueSetEnumerated set of valid values"standard", "specialty"
NumberNumeric value (may have min/max)4, 6.5
BooleanTrue or falsetrue, false
StringFree-form text"Additional notes here"

Example Payloads

Standard Service:

{
  "workLogId": "<workLogId>",
  "workDefinitionId": "wd_standard_services",
  "attributes": {
    "serviceDate": "2026-02-15T14:00:00Z",
    "serviceCategory": "standard",
    "units": 4,
    "serviceLocation": "inPerson",
    "isLocalityPay": true,
    "isFirstEncounter": false
  }
}

Specialty Service:

{
  "workLogId": "<workLogId>",
  "workDefinitionId": "wd_specialty_services",
  "attributes": {
    "serviceDate": "2026-02-15T08:00:00Z",
    "serviceCategory": "daytime",
    "totalHours": 6,
    "isLocalityPay": false,
    "isFirstEncounter": true
  }
}

Rate Cards

Rate cards store the pricing values used in rate calculations. They're configured during onboarding and referenced by rate calculation formulas.

Example Rate Card

Rate Card ID: rc_standard_rates Name: Standard Service Rate Card

KeyNameValueDescription
unitRateVirtualVirtual Unit Rate$20/unitPer unit, virtual services
unitRateInPersonIn-Person Unit Rate$25/unitPer unit, in-person services
flatRateVirtualFlat Rate Virtual$600Flat rate for virtual
flatRateInPersonFlat Rate In-Person$700Flat rate for in-person
hourlyRateHourly Rate$40/hrPer hour
retainerFirst Encounter Bonus$200Added on first encounter
localityBonusLocality Bonus$100Locality bonus (flat)
localityBonusPerUnitLocality Bonus Per Unit$5/unitLocality bonus per unit

Accessing Rate Card Values in Formulas

Rate card values are referenced in formulas using the syntax ${rateCard.keyName}:

${rateCard.unitRateInPerson}  →  25
${rateCard.retainer}          →  200

Rate Calculation Examples

Rate calculations combine work item attributes with rate card values using formulas. Multiple formulas can match and are summed together.

Example: Unit-Based Service

Attributes: { units: 4, serviceLocation: "inPerson", isLocalityPay: true, isFirstEncounter: true }

FormulaConditionCalculationResult
Base rateAlways4 × $25$100
Locality bonusisLocalityPay && inPerson4 × $5$20
RetainerisFirstEncounter$200$200
Total$320

Example: Hourly Service

Attributes: { totalHours: 6, isFirstEncounter: false }

FormulaConditionCalculationResult
Hourly rateAlways6 × $40$240
Total$240

Example: Flat Rate Service

Attributes: { serviceCategory: "flatRate", serviceLocation: "inPerson", isLocalityPay: true, isFirstEncounter: true }

FormulaConditionCalculationResult
Flat rateinPerson$700$700
Locality bonusisLocalityPay && inPerson$100$100
RetainerisFirstEncounter$200$200
Total$1000

Selection Strategy

The system uses a Sum selection strategy — all matching formulas are applied and their results are summed together. The calculations response shows each formula that matched:

{
  "calculations": {
    "items": [
      { "result": 700, "interpolatedFormula": "700" },
      { "result": 100, "interpolatedFormula": "100" },
      { "result": 200, "interpolatedFormula": "200" }
    ],
    "result": 1000,
    "selectionStrategy": "Sum"
  }
}

Error Handling

Error Response Format

All errors follow this format:

{
  "error": "Description of what went wrong"
}

Common Errors

HTTP StatusErrorCauseSolution
400Attribute validation failed: Missing required attribute: XRequired attribute not providedInclude all required attributes for the work definition
400Attribute validation failed: X must be one of: [values]Invalid enum valueUse only valid values from the work definition
400Attribute validation failed: X must be at most NValue out of rangeEnsure values are within allowed range
400Attribute validation failed: Invalid type for attribute X. Expected BooleanWrong data typeUse correct types (booleans must be true/false, not strings)
400Cannot create work item for a closed work logWork log already convertedCreate a new work log
400Work log is already converted to an invoiceDuplicate conversion attemptCheck work log status before converting
400Invalid workDefinitionIdWork definition not found or not in engagementVerify work definition belongs to the engagement
400Cannot create work log for an inactive engagementEngagement not activeContact Wingspan to activate
400Active log existsOnly one open log per PPEConvert or close existing log first
401Auth token was not providedMissing or invalid tokenInclude valid Bearer token
404WorkLog not foundInvalid work log IDVerify work log ID is correct
404Rate calc not foundMissing rate configurationContact Wingspan support

Example Error Responses

Missing Required Attributes:

{
  "error": "Attribute validation failed: Missing required attribute: Service Category (serviceCategory), Missing required attribute: Service Location (serviceLocation)"
}

Invalid Enum Value:

{
  "error": "Attribute validation failed: Service Category (serviceCategory) must be one of: standard, specialty, consultation"
}

Value Out of Range:

{
  "error": "Attribute validation failed: Units (units) must be at most 10"
}

FAQ

General Questions

Q: Can I create work for multiple contractors in a single API call?

A: No. Each work log is tied to a single PPE (one contractor). To submit work for multiple contractors, create separate work logs for each.


Q: Can I add work items using different work definitions to the same work log?

A: Yes! As long as the work definitions are part of the same engagement, you can mix them in one work log.


Q: What happens when I convert a work log to an invoice?

A: The work log status changes to "Approved", an invoice is created with status "Open", and the work log is closed (cannot add more items). Each work item becomes a line item on the invoice.


Q: Can I edit or delete work items after adding them?

A: Yes, while the work log is in "Draft" status. Once converted to an invoice, work items cannot be modified.


Q: How do I know what PPE ID to use for a contractor?

A: PPE IDs are assigned when a contractor is onboarded to an engagement. You can:

  1. Query GET /payments/payee/{payeeId}/engagement to get engagements for a payee
  2. Store PPE IDs during onboarding
  3. Contact Wingspan for a list

Q: What timezone should I use for dates?

A: Use ISO 8601 format with UTC (e.g., 2026-02-15T14:00:00Z). The system handles timezone conversions for display.


Rate Calculation Questions

Q: How is the total calculated when multiple formulas match?

A: The system uses a "Sum" selection strategy — all matching formulas are applied and their results are summed.


Q: Can I see the calculation breakdown?

A: Yes. The work item response includes a calculations object with:

  • result: Total amount
  • items: Array of each formula applied with individual results
  • selectionStrategy: How formulas are combined

Q: How do I handle rate card changes?

A: Rate card changes apply to new work items only. Existing work items retain their calculated amounts. To recalculate, delete the work item and create a new one.


Troubleshooting

Q: My API call returns "Auth token was not provided" even though I included it.

A: Check that:

  1. The header is Authorization: Bearer <token> (with space after "Bearer")
  2. The token is not expired
  3. There are no extra characters or line breaks

Q: Work log amount shows $0 even after adding work items.

A: The work log amount updates asynchronously. Query the work log again after a short delay, or check the individual work items' calculations.result fields.


Q: I'm getting "Invalid workDefinitionId" but I know the ID is correct.

A: Verify that:

  1. The work definition belongs to the engagement associated with the PPE
  2. The work definition is active
  3. You're using the correct engagement

Integration Best Practices

Q: Should I create one work log per day or per pay period?

A: Common patterns:

  • Per pay period: One work log per contractor per pay period, converted at period end
  • Per batch: One work log per batch submission from your system
  • Per service: One work log per service (simplest but most API calls)

Q: How should I handle failures during batch submission?

A: Recommended approach:

  1. Create work log (save workLogId)
  2. Add work items one by one, tracking which succeed
  3. If a work item fails, log the error and continue with others
  4. Only convert to invoice if all required items succeeded
  5. Implement idempotency by checking if work items already exist

Q: What's the rate limit for the API?

A: Contact Wingspan for specific rate limits. General guidance:

  • Implement exponential backoff on 429 errors
  • Batch work items into single work logs where possible
  • Avoid parallel requests for the same work log

Complete Example Flow

Here's a complete end-to-end example using shell commands:

# Set your token
TOKEN="your_api_token_here"
BASE_URL="https://api.wingspan.app"

# 1. Create work log for contractor
echo "Creating work log..."
WORK_LOG=$(curl -s -X POST "$BASE_URL/payments/work-log" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payerPayeeEngagementId": "ppe_abc123"
  }')

WORK_LOG_ID=$(echo $WORK_LOG | jq -r '.workLogId')
echo "Created work log: $WORK_LOG_ID"

# 2. Add first work item
echo "Adding work item 1..."
curl -s -X POST "$BASE_URL/payments/work-item" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workLogId": "'$WORK_LOG_ID'",
    "workDefinitionId": "wd_standard_services",
    "attributes": {
      "serviceDate": "2026-02-10T10:00:00Z",
      "serviceCategory": "standard",
      "units": 4,
      "serviceLocation": "inPerson",
      "isLocalityPay": false,
      "isFirstEncounter": true
    }
  }'

# 3. Add second work item
echo "Adding work item 2..."
curl -s -X POST "$BASE_URL/payments/work-item" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workLogId": "'$WORK_LOG_ID'",
    "workDefinitionId": "wd_standard_services",
    "attributes": {
      "serviceDate": "2026-02-12T14:00:00Z",
      "serviceCategory": "standard",
      "units": 2,
      "serviceLocation": "virtual",
      "isLocalityPay": false,
      "isFirstEncounter": false
    }
  }'

# 4. Convert to invoice
echo "Converting to invoice..."
INVOICE=$(curl -s -X POST "$BASE_URL/payments/work-log/$WORK_LOG_ID/convert" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json")

INVOICE_ID=$(echo $INVOICE | jq -r '.invoiceId')
INVOICE_AMOUNT=$(echo $INVOICE | jq -r '.amount')
echo "Created invoice: $INVOICE_ID for \$$INVOICE_AMOUNT"



Last Updated: February 2026