Payers Submitting Work Logs/Converting to Payables
Payers Submitting Work Logs/Converting to Payables
Table of Contents
- Overview
- How Payment Amounts Are Calculated
- Key Concepts
- Entity Relationships
- Prerequisites
- Authentication
- Step-by-Step Flow
- API Reference
- Supporting Operations
- Work Definitions & Attributes
- Rate Cards
- Rate Calculation Examples
- Error Handling
- FAQ
- 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:
| Environment | URL |
|---|---|
| Production | https://api.wingspan.app |
| Staging | https://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:
| Component | What It Is | Example |
|---|---|---|
| Work Definition | Template defining what data to capture | serviceCategory, units, isLocalityPay |
| Rate Card | Stored pricing values | standardUnitRate: $25, localityBonus: $5 |
| Rate Calculation | Formula 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:
- You submit a work item with attributes (e.g.,
units: 4,isLocalityPay: true) - System validates attributes against the work definition schema
- Rate calculation formulas are evaluated using work item attributes + rate card values
- All matching formulas are summed together (Sum selection strategy)
- 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
| Entity | Description |
|---|---|
| Engagement | Template 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 Definition | Schema defining what attributes to capture for a type of work |
| Rate Card | Pricing table with named rate values |
| Rate Calculation | Formula logic that computes payment amounts from attributes + rate card |
| Work Log | Container for work items, tied to one PPE (one contractor) |
| Work Item | Single service entry with attributes (date, category, units, etc.) |
| Invoice | Payment 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:
| Requirement | Description | How to Obtain |
|---|---|---|
| API Token | Bearer token for authentication | Provided by Wingspan |
| payerPayeeEngagementId | Contractor's assignment to the engagement | See "Getting PPE IDs" below |
| workDefinitionId | Template defining what data to capture | Provided 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}/engagementOption 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-logStep 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-itemStep 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}/convertAPI Reference
1. Create Work Log
Creates a new work log for a specific payer-payee-engagement.
Endpoint: POST /payments/work-log
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
payerPayeeEngagementId | string | Yes | The PPE ID linking payer to payee under engagement |
startDate | DateTime | No | Period 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:
| Field | Description |
|---|---|
workLogId | Unique ID — use this for adding work items |
workLogNumber | Human-readable reference number |
status | "Draft" initially |
amount | Total 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:
| Field | Type | Required | Description |
|---|---|---|---|
workLogId | string | Yes | The work log to add this item to |
workDefinitionId | string | Yes | Template that defines required attributes |
attributes | object | Yes | Service data (varies by work definition) |
timestamp | DateTime | No | When 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:
| Field | Description |
|---|---|
workItemId | Unique ID for this work item |
calculations.result | Total calculated amount |
calculations.items | Breakdown of each formula applied |
calculations.selectionStrategy | How 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:
| Parameter | Values | Default | Description |
|---|---|---|---|
invoiceStatus | Draft, Open | Draft | Status 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:
| Field | Description |
|---|---|
invoiceId | Unique invoice ID |
invoiceNumber | Human-readable invoice number |
status | "Open" — ready for payment |
amount | Total invoice amount |
lineItems | Each work item becomes a line item |
attachments.invoiceLink | URL 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]=OpenGet 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_xyz789Update 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/engagementGet 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:
| Key | Name | Type | Required | Valid Values | Description |
|---|---|---|---|---|---|
serviceDate | Service Date | Datetime | Yes | ISO 8601 | When service was performed |
serviceCategory | Service Category | ValueSet | Yes | standard, specialty, consultation | Type of service |
units | Units | Number | No* | 0-10 | Number of units (*Required for certain categories) |
serviceLocation | Service Location | ValueSet | Yes | virtual, inPerson | Where service was performed |
isLocalityPay | Locality Pay | Boolean | Yes | true, false | Whether locality bonus applies |
isFirstEncounter | First Encounter | Boolean | Yes | true, false | First encounter with client |
notes | Notes | String | No | Any string | Optional notes |
Attribute Types
| Type | Description | Example Values |
|---|---|---|
Datetime | ISO 8601 formatted date/time | "2026-02-15T14:00:00Z" |
ValueSet | Enumerated set of valid values | "standard", "specialty" |
Number | Numeric value (may have min/max) | 4, 6.5 |
Boolean | True or false | true, false |
String | Free-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
| Key | Name | Value | Description |
|---|---|---|---|
unitRateVirtual | Virtual Unit Rate | $20/unit | Per unit, virtual services |
unitRateInPerson | In-Person Unit Rate | $25/unit | Per unit, in-person services |
flatRateVirtual | Flat Rate Virtual | $600 | Flat rate for virtual |
flatRateInPerson | Flat Rate In-Person | $700 | Flat rate for in-person |
hourlyRate | Hourly Rate | $40/hr | Per hour |
retainer | First Encounter Bonus | $200 | Added on first encounter |
localityBonus | Locality Bonus | $100 | Locality bonus (flat) |
localityBonusPerUnit | Locality Bonus Per Unit | $5/unit | Locality 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 }
| Formula | Condition | Calculation | Result |
|---|---|---|---|
| Base rate | Always | 4 × $25 | $100 |
| Locality bonus | isLocalityPay && inPerson | 4 × $5 | $20 |
| Retainer | isFirstEncounter | $200 | $200 |
| Total | $320 |
Example: Hourly Service
Attributes: { totalHours: 6, isFirstEncounter: false }
| Formula | Condition | Calculation | Result |
|---|---|---|---|
| Hourly rate | Always | 6 × $40 | $240 |
| Total | $240 |
Example: Flat Rate Service
Attributes: { serviceCategory: "flatRate", serviceLocation: "inPerson", isLocalityPay: true, isFirstEncounter: true }
| Formula | Condition | Calculation | Result |
|---|---|---|---|
| Flat rate | inPerson | $700 | $700 |
| Locality bonus | isLocalityPay && inPerson | $100 | $100 |
| Retainer | isFirstEncounter | $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 Status | Error | Cause | Solution |
|---|---|---|---|
| 400 | Attribute validation failed: Missing required attribute: X | Required attribute not provided | Include all required attributes for the work definition |
| 400 | Attribute validation failed: X must be one of: [values] | Invalid enum value | Use only valid values from the work definition |
| 400 | Attribute validation failed: X must be at most N | Value out of range | Ensure values are within allowed range |
| 400 | Attribute validation failed: Invalid type for attribute X. Expected Boolean | Wrong data type | Use correct types (booleans must be true/false, not strings) |
| 400 | Cannot create work item for a closed work log | Work log already converted | Create a new work log |
| 400 | Work log is already converted to an invoice | Duplicate conversion attempt | Check work log status before converting |
| 400 | Invalid workDefinitionId | Work definition not found or not in engagement | Verify work definition belongs to the engagement |
| 400 | Cannot create work log for an inactive engagement | Engagement not active | Contact Wingspan to activate |
| 400 | Active log exists | Only one open log per PPE | Convert or close existing log first |
| 401 | Auth token was not provided | Missing or invalid token | Include valid Bearer token |
| 404 | WorkLog not found | Invalid work log ID | Verify work log ID is correct |
| 404 | Rate calc not found | Missing rate configuration | Contact 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:
- Query
GET /payments/payee/{payeeId}/engagementto get engagements for a payee - Store PPE IDs during onboarding
- 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 amountitems: Array of each formula applied with individual resultsselectionStrategy: 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:
- The header is
Authorization: Bearer <token>(with space after "Bearer") - The token is not expired
- 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:
- The work definition belongs to the engagement associated with the PPE
- The work definition is active
- 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:
- Create work log (save workLogId)
- Add work items one by one, tracking which succeed
- If a work item fails, log the error and continue with others
- Only convert to invoice if all required items succeeded
- 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
Updated 3 days ago