Work order lifecycle
Work orders are the one resource three different audiences touch: staff manage them, tenants report and follow them, and vendors do the work. The visibility rules matter as much as the state machine.
States
Section titled “States”open ──> in_progress ──> completed │ │ └────────────┴────────> cancelledcompleted and cancelled are terminal. Any other transition returns 409:
{ "message": "Invalid status transition from completed to open" }Creating one
Section titled “Creating one”Staff must name a unit:
curl -X POST https://api.getvespy.com/api/work-orders \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "title": "Kitchen sink leaking under cabinet", "description": "Standing water in the base cabinet, tenant has shut off the supply valve.", "propertyId": "'"$PROPERTY_ID"'", "unitId": "'"$UNIT_ID"'", "priority": "high", "category": "plumbing" }'priority is low, normal, high, or emergency.
When a tenant calls the same endpoint, the API overrides most of the body: source
becomes tenant, and the property, unit, and tenant are taken from the caller’s active
lease. A tenant cannot open a work order against a unit they do not rent, and cannot
pre-assign a vendor.
If the unit has accessInstructions, they are copied onto the work order as
entryInstructions — so whoever is dispatched gets the lockbox code without anyone
looking it up.
Assigning it
Section titled “Assigning it”curl -X POST "https://api.getvespy.com/api/work-orders/$WORK_ORDER_ID/assign" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"assignedVendorId":"'"$VENDOR_ID"'"}'Assign a vendor, a staff user, or both. Pass null to clear one. Assignment does not
change status — move it to in_progress separately:
curl -X POST "https://api.getvespy.com/api/work-orders/$WORK_ORDER_ID/status" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"status":"in_progress"}'Assigning a vendor also lets them see the job in the vendor portal. If they do not have a login yet, send a vendor invite first.
Notes and who can read them
Section titled “Notes and who can read them”Notes carry the conversation, and every note declares its audience:
visibility |
Visible to |
|---|---|
organization_only |
Staff only |
tenant_shared |
Staff and the tenant |
vendor_shared |
Staff and the assigned vendor |
tenant_vendor_shared |
Everyone on the job |
curl -X POST "https://api.getvespy.com/api/work-orders/$WORK_ORDER_ID/notes" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"visibility":"tenant_shared","body":"Plumber scheduled for Thursday between 9 and 11am."}'A tenant posting a note may only choose a visibility that includes tenants; anything else
returns 403.
Expenses link to a work order through workOrderId, and
GET /api/work-orders/{id} returns them as linkedExpenses with the notes and
attachments. That detail response is the single call to make when rendering a work order
page.
Closing out
Section titled “Closing out”curl -X POST "https://api.getvespy.com/api/work-orders/$WORK_ORDER_ID/status" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"status":"completed"}'Completion stamps completedAt and emits an event that downstream notifications consume.
Because it is terminal, reopening means creating a new work order.