1. The service data model
Four top-level resources. Everything else hangs off these.
work_order— the unit of service. Has a type, a hull, a contact, a status, and rolls up total cost.labor_line— time entries by technician. Stored in minutes, billed at a rate resolved from the WO type and tech skill.parts_line— a SKU, quantity, cost, markup, and status (requested→ordered→received→installed).warranty_claim— OEM-facing claim referencing a work order, with a state machine independent of the WO.
Work orders belong to a location_id and optionally a service_bay_id. Time clock punches are separate (time_punch) and link to labor lines at commit.
2. Custom work order types
BoaterOS ships with standard types (PDI, commission, warranty, comeback, detail, storage, winterization, de-winterization, survey-repair). Add your own under Service → Types → New or through the API:
{
"code": "hurricane_haul_out",
"label": "Hurricane haul-out",
"default_bay": "bay_yard_3",
"default_labor_rate_code": "yard",
"default_flat_rate_minutes": 120,
"required_fields": ["hull_id", "length_ft", "beam_ft"],
"checklist_template_id": "chk_hurricane_v2",
"visible_to_customer_portal": true,
"invoice_tax_code": "tx_fl_service"
} Required fields block WO creation if absent; checklists attach a step-through list to the tech's iPad view. The new type is immediately available to scheduling, dispatch, and billing.
3. Service webhooks
curl https://api.boater.os/v1/service/work-orders \ -H "Authorization: Bearer $BOATEROS_KEY" \ -d '{ "type_code": "hurricane_haul_out", "hull_id": "hull_YDV48219", "contact_id": "cnt_YTG9210", "scheduled_for": "2026-06-01T13:00:00-04:00", "notes": "Customer requests storm-prep checklist." }'
4. Parts catalog extension
The parts catalog ships with Mercury, Yamaha, Volvo Penta, Suzuki, Honda, Garmin, and Raymarine pre-loaded (SKU, list price, supplier). Add custom SKUs with a supplier mapping:
{
"sku": "SHOREPWR-30A-50",
"description": "30A shore power cord, 50 ft, yellow",
"supplier_id": "sup_marinco",
"supplier_sku": "199119",
"cost_cents": 14900,
"list_cents": 21900,
"tax_class": "parts",
"min_stock": 4,
"reorder_point": 6
} Suppliers with EDI feeds (Land 'N' Sea, Payne's, Wholesale Marine, and most OEMs) accept POs automatically. For the rest, BoaterOS emails a PDF PO with a reply-tracking address.
5. Labor rate configuration
Labor rates live in a small table keyed by (rate_code, skill_tier, effective_from). Rate codes are free-form — we see dealers split by "retail," "warranty," "internal," "yard," "commission." Skill tiers (apprentice / tech / master) multiply the base rate. Rates are time-versioned so historical WOs always invoice at the rate in effect on the date of service.
6. External shop-management integration
If you run Mitchell 1 or Shop-Ware alongside BoaterOS, use the service webhooks as your source of truth and push state changes back via the REST API. A common pattern:
- Subscribe to
work_order.created. Mirror the WO in your shop tool with itsidstored as an external ref. - When a tech clocks out in the shop tool, POST
/v1/service/work-orders/{id}/labor. - When parts are installed, POST to
/laborwith theparts_line_id. - On close, POST
/completeto trigger invoicing and customer receipt.
We test this path against Mitchell 1 and Shop-Ware in CI; a reference adapter is open-sourced at github.com/boateros/shop-adapters.
7. Reporting extensions
The standard service reports (utilization, effective labor rate, warranty recovery, parts margin, comeback rate) live in the admin. For anything custom, three options: (1) the /v1/reports/service/* endpoints return the same aggregates as JSON, (2) the daily S3 export dumps the full normalized schema to Parquet, and (3) read-only Postgres replicas are available on the Fleet plan for direct BI tool connection.
Warning. Don't compute effective labor rate client-side from raw labor lines. We apply rate versioning, warranty splits, and internal-vs-retail allocation server-side; reproducing it is how people file tickets saying "your numbers are wrong" when their own numbers are wrong.