/v1/buyer/work_order/: creating work orders, querying them, the buyer-side status actions, the note thread, labels, and problem types.
All examples assume these shell variables:
The work order object
A work order returned by the API carries, among other fields:Create a work order
POST /v1/buyer/work_order/work_orders requires more than the obvious fields. Unlike most buyer endpoints, buyerFacilityId, buyerCompanyId, and createdBy must be supplied in the body; they are not derived from your API key on this endpoint. Use GET /v1/buyer/me to look up your company and facility ids once and cache them.
Required fields: title, locationId, problemTypeId, buyerFacilityId, buyerCompanyId, and createdBy (the email of the creating contact).
- Initial status. If
statusis omitted, the initial status is computed from your company’s configuration. Service requests typically start inPendingApproval; work orders default toUnassigned. If you pass astatus, it is still resolved against company configuration, so the effective status may differ from what you sent. - Dispatching on create. Passing
supplierFacilityIdassigns the supplier immediately. An unknownsupplierFacilityId,assetId, orproblemTypeIdis rejected with400. - Sub-assets.
subAssetIdsis an optional array of sub-asset ids attached alongside the mainassetId. Sub-assets are assets created with aparentId; see Assets. - Approval flag. The field is
needApproval, notneedsApproval. The response object usesneedsApproval; the create request does not. - Walkthrough linkage.
walkThroughIdandsiteSurveyTaskTitleIdmust be provided together or not at all. See Site survey walkthroughs. - Upserts. Passing an
idupdates that existing work order instead of creating a new one.
List, filter, and count
offset, limit (default 10, max 25), sort_by, and order. Every other query parameter is treated as a field filter; comma-separate a value to match any of several (status=Unassigned,PendingApproval). Filters always combine with the tenant scoping derived from your key, so you only ever see your company’s work orders.
The list and count resolve which work orders match in OpenWrench’s search index, then load the full records from the database. That changes a few behaviors:
- Full-text search.
search=matches words (with prefix and stem matching) across the title, description, location name, notes, service call check-in and check-out notes, and problem type name. It also matches reference numbers such as the work order number, PO number, and asset serial number. Quote the value (search="walk-in cooler") to require the exact phrase. - Asset filters include sub-assets.
assetId=andassetIds=match work orders whose main asset or any sub-asset is the given id. - Sorting.
sort_bysupportscreatedAt,locationName,woPriority(by the priority’s expected resolution time), andlastServiceCallServiceScheduledAt. Any other value, or nosort_by, sorts bycreatedAtdescending. - Unindexed filters are ignored.
updatedAtStartDate,updatedAtEndDate,buyerFacilityId, andwalkthroughIdare not in the search index and no longer narrow the results. Filter onstatusChangedAtwindows or other indexed columns instead. - Freshness. Search index and replica updates lag writes by a moment, so a work order created milliseconds ago may be briefly absent from list results and counts.
- Pagination depth.
offset + limitcannot exceed the search result window of 10,000. Narrow the filter rather than paging that deep.
GET /v1/buyer/work_order/work_orders/{id}.
Reassign a supplier
PATCH /v1/buyer/work_order/work_orders/{id} is deliberately narrow: only supplierFacilityId and supplierPrimaryContactEmail are read from the body. Every other field you send is silently ignored rather than rejected.
supplierPrimaryContactEmailonly applies as part of a reassignment. Sending it without a supplier facility change makes the whole patch a no-op: nothing persists and the current work order is returned.- A nonexistent id and a permission denial both return the same
400unauthorized response, so do not use this endpoint to probe whether a work order exists.
Buyer status actions
Four dedicated endpoints move a work order through the buyer-owned parts of the lifecycle. Each takes the same body: the work orderid and an optional note that is appended to the thread along with the status change.
Cancellation lock while a technician is checked in
Companies can opt in to a cancellation lock that keeps work orders open while a technician is on site. With the lock enabled, OpenWrench rejects any cancellation with409 Conflict if a technician is actively checked in on any of the work order’s service calls. This covers visits on the work order itself and visits on its sub-contracted work orders. OpenWrench blocks canceling a sub-contract whose status propagates to the root work order in the same way, and commits nothing on either work order.
The lock is configured per buyer company with two settings:
The
409 error message identifies the checked-in technician by email when the visit recorded one:
409, wait for the technician to check out (or for the check-in to go stale) and retry, or have the supplier end the visit first. The lock is part of your buyer company’s work order configuration in OpenWrench.
The status model
status values you will see and set through the API:
The supplier-owned transitions arrive through the supplier’s own integration or the OpenWrench apps; your side observes them through webhooks or by polling (
statusChangedAt, statusChanges) and acts on the buyer-owned ones.
Notes
Work orders carry a shared buyer–supplier note thread. Append withPATCH /v1/buyer/work_order/work_orders/append_notes. The body is { "id": <woId>, "note": { ... } } where the note needs text and noteAddedBy. The server overwrites noteAddedAt with its own clock, and the author’s email is added to the work order’s buyer subscriber list so they receive subsequent notifications.
taggedUsers beside the note: a list of contact email addresses to @mention. OpenWrench folds each email into note.elements as a user element, the same shape the apps write for an @mention, so you don’t need to build the elements structure yourself. Tagged contacts are added to the work order’s subscriber list if they aren’t subscribed already.
taggedUsers:
- Emails are trimmed and lowercased before matching. A note can mention at most 25 users.
- Duplicate emails in the list, and users already tagged in
note.elements, are skipped rather than mentioned twice. - An entry that is not a valid email address, or a list of more than 25 emails, is rejected with a
400of typeinvalidInputDataException. Nothing is persisted. - An empty list is ignored. Sending
taggedUserswithout anoteobject is rejected with400.
PATCH /v1/buyer/work_order/work_orders/append_notes/bulk. Use it when you have a batch of photos to post (a technician’s before/after set, for example): subscribers receive a single grouped notification instead of one notification per photo. The body is { "id": <woId>, "notes": [ ... ] } with 1–25 notes, and each note must carry a photo URL and no other content: text must be empty and video, audio, otherFile, and elements must be absent. Repeating the same photo URL within a request is rejected with 400. The server stamps every note in the batch with the same noteAddedAt and preserves the order you sent them in.
GET /v1/buyer/work_order/work_order_notes/{woId}. This returns the root buyer–supplier thread only (contractor and internal threads are separate), and reading marks the notes as read for your contact. A denied read returns an empty list rather than an error.
Labels
Labels are lightweight tags your team defines in the OpenWrench app (a name plus an optional color). Through the API you can read the catalog and replace the labels applied to a work order. Creating or editing the labels themselves stays in the app. Browse the catalog withGET /v1/buyer/work_order/work_order_labels, or fetch one with GET /v1/buyer/work_order/work_order_labels/{id}. The list is scoped to your company’s labels and paginated 10 per page by default. It accepts search and label filters on the label text. Pass no_pagination=true to pull the whole catalog in one call (sorting still applies).
PUT /v1/buyer/work_order/work_orders/{woId}/labels. The body is { "ids": [...] } and it is a full replacement: labels not listed are removed, and an empty array clears them all. The response lists the label mappings now active on the work order.
- A missing or non-array
ids, or an id that is not a whole number, is rejected with400. - Every id must be a live label from your own catalog. Unknown or cross-tenant ids answer
400with the offending ids listed. - The write requires work order write permission. An unknown, deleted, or foreign work order id answers the same
400as a denied write, not a404.
Problem types
GET /v1/buyer/work_order/problem_types lists the problem types configured for your company. Cache this: you need a valid problemTypeId for every work order you create, and the ranked supplier endpoint takes one too.
Putting it together
A typical dispatch integration:- Cache
me, problem types, and locations at startup. - Create the work order with
supplierFacilityIdset (or create unassigned, then rank suppliers andPATCHthe assignment). - Track supplier progress through webhooks (
workorder.status_update), fetching each work order by id when an event arrives. KeepGET /work_orders?statusChangedAt=...as an infrequent reconciliation pass, not the primary signal. - When the supplier reaches
WaitingForReview, verify the work (see Service calls for visit evidence) and postwork_reviewed_and_completed, orwork_unsatisfactorywith a note. - Reconcile the money side through Quotes and proposals and Invoices.