Maintenance
Maintenance event history and scheduling.
/maintenanceCreate a maintenance event
Requires `vesselId`, `scheduledDate`, `description`, and `eventType`. `scope` defaults to `equipment` and determines which FK is required: `equipment` requires `equipmentId`, `system` requires `vesselSystemId`, `vessel` requires neither. On ISM-onboarded vessels, `responsibleUserId` is also required. Supports the `Idempotency-Key` request header (see `IdempotencyKeyHeader` parameter) — a repeat POST with the same key and the same request body within 24h returns the original response unchanged with `Idempotent-Replayed: true`; the same key with a *different* body returns `409 CONFLICT`.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| Idempotency-Key | header | string | optional | Client-generated unique token (e.g. a UUID) scoping this write to be safely retried. Replaying the same key with the same request body within 24h returns the original cached response unchanged (with `Idempotent-Replayed: true`); replaying with a different body returns `409 CONFLICT`. Opt-in — omit for normal (non-idempotent) behavior. |
| Field | Type | Required | Description |
|---|---|---|---|
| vesselId | string (uuid) | required | — |
| scope | enum(equipment | system | vessel) | optional | — |
| equipmentId | string (uuid) | optional | Required when scope=equipment. |
| vesselSystemId | string (uuid) | optional | Required when scope=system. |
| eventType | enum(routine | repair | inspection | upgrade | refit | warranty) | required | — |
| scheduledDate | string (date-time) | required | — |
| description | string | required | — |
| priority | enum(low | medium | high | critical) | optional | — |
| serviceProviderId | string (uuid) | optional | — |
| performedBy | string | optional | — |
| currency | string | optional | — |
| responsibleUserId | string (uuid) | optional | Required on ISM-onboarded vessels. |
| laborHours | number | optional | — |
| laborCost | string | optional | Serialized as string to preserve Decimal precision. |
| notes | string | optional | — |
| partsUsed | array<object> | optional | — |
| partsUsed[].inventoryItemId | string (uuid) | required | — |
| partsUsed[].quantity | number | required | — |
| workflowId | string (uuid) | optional | — |
| Field | Type | Required | Description |
|---|---|---|---|
| data | object | required | — |
| data.id | string (uuid) | optional | — |
| data.vesselId | string (uuid) | optional | — |
| data.equipmentId | string (uuid) | null | optional | — |
| data.vesselSystemId | string (uuid) | null | optional | — |
| data.scope | string | optional | MaintenanceScope enum: equipment, system, or vessel. |
| data.eventType | string | optional | MaintenanceType enum: routine, repair, inspection, upgrade, refit, warranty. |
| data.status | string | optional | Derived workflow-bucket status (not a stored column) — computed from `workflowExecution.currentState`. |
| data.priority | string | optional | MaintenancePriority enum: low, medium, high, critical. |
| data.scheduledDate | string (date-time) | null | optional | — |
| data.completedDate | string (date-time) | null | optional | — |
| data.description | string | null | optional | — |
| data.performedBy | string | null | optional | — |
| data.serviceProviderId | string (uuid) | null | optional | — |
| data.serviceProvider | object | null | optional | — |
| data.serviceProvider.id | string (uuid) | optional | — |
| data.serviceProvider.businessName | string | optional | — |
| data.serviceProvider.phone | string | null | optional | — |
| data.partsUsed | array<object> | optional | — |
| data.partsUsed[].inventoryItemId | string (uuid) | optional | — |
| data.partsUsed[].quantity | number | optional | — |
| data.laborHours | number | null | optional | — |
| data.laborCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.partsCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.totalCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.currency | string | optional | — |
| data.hoursAtService | integer | null | optional | — |
| data.beforePhotos | array<string> | optional | — |
| data.afterPhotos | array<string> | optional | — |
| data.invoiceAttachments | array<string> | optional | — |
| data.notes | string | null | optional | — |
| data.technicianSignature | string | null | optional | — |
| data.technicianName | string | null | optional | — |
| data.technicianSignedAt | string (date-time) | null | optional | — |
| data.requestedById | string (uuid) | null | optional | — |
| data.responsibleUserId | string (uuid) | null | optional | — |
| data.linkedPermitId | string (uuid) | null | optional | — |
| data.equipment | object | null | optional | — |
| data.equipment.id | string (uuid) | optional | — |
| data.equipment.equipmentType | string | optional | — |
| data.equipment.make | string | null | optional | — |
| data.equipment.model | string | null | optional | — |
| data.createdAt | string (date-time) | optional | — |
| data.updatedAt | string (date-time) | optional | — |
curl -sS -X POST "https://api.owlmar.com/v1/maintenance" \-H "Authorization: Bearer $OWLMAR_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: $(uuidgen)" \-d '{"vesselId": "'"$VESSEL_ID"'","equipmentId": "'"$EQUIPMENT_ID"'","eventType": "routine","scheduledDate": "2026-09-01T09:00:00.000Z","description": "500-hour service — port main engine"}'
import { randomUUID } from 'node:crypto';// Generate the Idempotency-Key ONCE per logical create attempt, not per HTTP// call — reuse the SAME key across retries of the SAME create so a network// timeout-and-retry doesn't double-create the maintenance event.const idempotencyKey = randomUUID();async function createWithRetry(body, attempts = 3) {for (let i = 0; i < attempts; i++) {try {const res = await fetch('https://api.owlmar.com/v1/maintenance', {method: 'POST',headers: {Authorization: `Bearer ${process.env.OWLMAR_API_KEY}`,'Content-Type': 'application/json','Idempotency-Key': idempotencyKey,},body: JSON.stringify(body),});return await res.json();} catch (networkErr) {if (i === attempts - 1) throw networkErr;}}}const { data: event } = await createWithRetry({vesselId,equipmentId,eventType: 'routine',scheduledDate: '2026-09-01T09:00:00.000Z',description: '500-hour service — port main engine',});
import uuididempotency_key = str(uuid.uuid4()) # reuse across retries of the same attemptresp = requests.post("https://api.owlmar.com/v1/maintenance",headers={"Authorization": f"Bearer {OWLMAR_API_KEY}","Idempotency-Key": idempotency_key,},json={"vesselId": vessel_id,"equipmentId": equipment_id,"eventType": "routine","scheduledDate": "2026-09-01T09:00:00.000Z","description": "500-hour service — port main engine",},)event = resp.json()["data"]
/maintenance/{id}Get a maintenance event
Returns the full maintenance event with vessel, linked permit summary, recurring-schedule state, and current workflow state/transitions.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | required | — |
| Field | Type | Required | Description |
|---|---|---|---|
| data | any | required | — |
/maintenance/{id}Update a maintenance event
Partial update. Supports the `Idempotency-Key` header on the same terms as create.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | required | — |
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| Idempotency-Key | header | string | optional | Client-generated unique token (e.g. a UUID) scoping this write to be safely retried. Replaying the same key with the same request body within 24h returns the original cached response unchanged (with `Idempotent-Replayed: true`); replaying with a different body returns `409 CONFLICT`. Opt-in — omit for normal (non-idempotent) behavior. |
| Field | Type | Required | Description |
|---|---|---|---|
| eventType | enum(routine | repair | inspection | upgrade | refit | warranty) | optional | — |
| priority | enum(low | medium | high | critical) | optional | — |
| scheduledDate | string (date-time) | null | optional | — |
| completedDate | string (date-time) | null | optional | — |
| description | string | null | optional | — |
| notes | string | null | optional | — |
| laborHours | number | null | optional | — |
| laborCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| partsUsed | array<object> | optional | — |
| partsUsed[].inventoryItemId | string (uuid) | optional | — |
| partsUsed[].quantity | number | optional | — |
| Field | Type | Required | Description |
|---|---|---|---|
| data | object | required | — |
| data.id | string (uuid) | optional | — |
| data.vesselId | string (uuid) | optional | — |
| data.equipmentId | string (uuid) | null | optional | — |
| data.vesselSystemId | string (uuid) | null | optional | — |
| data.scope | string | optional | MaintenanceScope enum: equipment, system, or vessel. |
| data.eventType | string | optional | MaintenanceType enum: routine, repair, inspection, upgrade, refit, warranty. |
| data.status | string | optional | Derived workflow-bucket status (not a stored column) — computed from `workflowExecution.currentState`. |
| data.priority | string | optional | MaintenancePriority enum: low, medium, high, critical. |
| data.scheduledDate | string (date-time) | null | optional | — |
| data.completedDate | string (date-time) | null | optional | — |
| data.description | string | null | optional | — |
| data.performedBy | string | null | optional | — |
| data.serviceProviderId | string (uuid) | null | optional | — |
| data.serviceProvider | object | null | optional | — |
| data.serviceProvider.id | string (uuid) | optional | — |
| data.serviceProvider.businessName | string | optional | — |
| data.serviceProvider.phone | string | null | optional | — |
| data.partsUsed | array<object> | optional | — |
| data.partsUsed[].inventoryItemId | string (uuid) | optional | — |
| data.partsUsed[].quantity | number | optional | — |
| data.laborHours | number | null | optional | — |
| data.laborCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.partsCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.totalCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.currency | string | optional | — |
| data.hoursAtService | integer | null | optional | — |
| data.beforePhotos | array<string> | optional | — |
| data.afterPhotos | array<string> | optional | — |
| data.invoiceAttachments | array<string> | optional | — |
| data.notes | string | null | optional | — |
| data.technicianSignature | string | null | optional | — |
| data.technicianName | string | null | optional | — |
| data.technicianSignedAt | string (date-time) | null | optional | — |
| data.requestedById | string (uuid) | null | optional | — |
| data.responsibleUserId | string (uuid) | null | optional | — |
| data.linkedPermitId | string (uuid) | null | optional | — |
| data.equipment | object | null | optional | — |
| data.equipment.id | string (uuid) | optional | — |
| data.equipment.equipmentType | string | optional | — |
| data.equipment.make | string | null | optional | — |
| data.equipment.model | string | null | optional | — |
| data.createdAt | string (date-time) | optional | — |
| data.updatedAt | string (date-time) | optional | — |
/maintenance/{id}/completeMark a maintenance event complete
Stamps `completedDate: now()`, optionally deducts `partsUsed` from inventory (if not already deducted), and accepts the same body fields as a PATCH for any final details captured at completion time (labor hours, cost, notes, technician signature, etc.).
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | required | — |
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| Idempotency-Key | header | string | optional | Client-generated unique token (e.g. a UUID) scoping this write to be safely retried. Replaying the same key with the same request body within 24h returns the original cached response unchanged (with `Idempotent-Replayed: true`); replaying with a different body returns `409 CONFLICT`. Opt-in — omit for normal (non-idempotent) behavior. |
| Field | Type | Required | Description |
|---|---|---|---|
| partsUsed | array<object> | optional | — |
| partsUsed[].inventoryItemId | string (uuid) | required | — |
| partsUsed[].quantity | number | required | — |
| laborHours | number | null | optional | — |
| laborCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| notes | string | null | optional | — |
| technicianSignature | string | null | optional | — |
| technicianName | string | null | optional | — |
| Field | Type | Required | Description |
|---|---|---|---|
| data | object | required | — |
| data.id | string (uuid) | optional | — |
| data.vesselId | string (uuid) | optional | — |
| data.equipmentId | string (uuid) | null | optional | — |
| data.vesselSystemId | string (uuid) | null | optional | — |
| data.scope | string | optional | MaintenanceScope enum: equipment, system, or vessel. |
| data.eventType | string | optional | MaintenanceType enum: routine, repair, inspection, upgrade, refit, warranty. |
| data.status | string | optional | Derived workflow-bucket status (not a stored column) — computed from `workflowExecution.currentState`. |
| data.priority | string | optional | MaintenancePriority enum: low, medium, high, critical. |
| data.scheduledDate | string (date-time) | null | optional | — |
| data.completedDate | string (date-time) | null | optional | — |
| data.description | string | null | optional | — |
| data.performedBy | string | null | optional | — |
| data.serviceProviderId | string (uuid) | null | optional | — |
| data.serviceProvider | object | null | optional | — |
| data.serviceProvider.id | string (uuid) | optional | — |
| data.serviceProvider.businessName | string | optional | — |
| data.serviceProvider.phone | string | null | optional | — |
| data.partsUsed | array<object> | optional | — |
| data.partsUsed[].inventoryItemId | string (uuid) | optional | — |
| data.partsUsed[].quantity | number | optional | — |
| data.laborHours | number | null | optional | — |
| data.laborCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.partsCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.totalCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data.currency | string | optional | — |
| data.hoursAtService | integer | null | optional | — |
| data.beforePhotos | array<string> | optional | — |
| data.afterPhotos | array<string> | optional | — |
| data.invoiceAttachments | array<string> | optional | — |
| data.notes | string | null | optional | — |
| data.technicianSignature | string | null | optional | — |
| data.technicianName | string | null | optional | — |
| data.technicianSignedAt | string (date-time) | null | optional | — |
| data.requestedById | string (uuid) | null | optional | — |
| data.responsibleUserId | string (uuid) | null | optional | — |
| data.linkedPermitId | string (uuid) | null | optional | — |
| data.equipment | object | null | optional | — |
| data.equipment.id | string (uuid) | optional | — |
| data.equipment.equipmentType | string | optional | — |
| data.equipment.make | string | null | optional | — |
| data.equipment.model | string | null | optional | — |
| data.createdAt | string (date-time) | optional | — |
| data.updatedAt | string (date-time) | optional | — |
/maintenance/vessel/{vesselId}List a vessel's maintenance events
Returns maintenance events for a vessel with derived `status`, linked equipment, service provider, permit, schedule, and workflow-state info. Supports `?cursor=` pagination on the default (status-priority) sort path only — the `status=hours_due` filter branch (JS-side post-filter, no materialized sort column) does not support cursor and silently falls back to `?page=` semantics if combined with `?cursor=`.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| vesselId | path | string (uuid) | required | — |
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| cursor | query | string | optional | Opaque pagination cursor from a previous response's `pagination.cursor`. Omit this param entirely for the first page of cursor mode; pass `cursor=` (empty) is also accepted as "start from the beginning in cursor mode." Omitting `cursor` altogether (not even as an empty string) falls back to legacy `?page=` semantics on endpoints that still support it. |
| limit | query | integer | optional | — |
| Field | Type | Required | Description |
|---|---|---|---|
| data | array<object> | required | — |
| data[].id | string (uuid) | optional | — |
| data[].vesselId | string (uuid) | optional | — |
| data[].equipmentId | string (uuid) | null | optional | — |
| data[].vesselSystemId | string (uuid) | null | optional | — |
| data[].scope | string | optional | MaintenanceScope enum: equipment, system, or vessel. |
| data[].eventType | string | optional | MaintenanceType enum: routine, repair, inspection, upgrade, refit, warranty. |
| data[].status | string | optional | Derived workflow-bucket status (not a stored column) — computed from `workflowExecution.currentState`. |
| data[].priority | string | optional | MaintenancePriority enum: low, medium, high, critical. |
| data[].scheduledDate | string (date-time) | null | optional | — |
| data[].completedDate | string (date-time) | null | optional | — |
| data[].description | string | null | optional | — |
| data[].performedBy | string | null | optional | — |
| data[].serviceProviderId | string (uuid) | null | optional | — |
| data[].serviceProvider | object | null | optional | — |
| data[].serviceProvider.id | string (uuid) | optional | — |
| data[].serviceProvider.businessName | string | optional | — |
| data[].serviceProvider.phone | string | null | optional | — |
| data[].partsUsed | array<object> | optional | — |
| data[].partsUsed[].inventoryItemId | string (uuid) | optional | — |
| data[].partsUsed[].quantity | number | optional | — |
| data[].laborHours | number | null | optional | — |
| data[].laborCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].partsCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].totalCost | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].currency | string | optional | — |
| data[].hoursAtService | integer | null | optional | — |
| data[].beforePhotos | array<string> | optional | — |
| data[].afterPhotos | array<string> | optional | — |
| data[].invoiceAttachments | array<string> | optional | — |
| data[].notes | string | null | optional | — |
| data[].technicianSignature | string | null | optional | — |
| data[].technicianName | string | null | optional | — |
| data[].technicianSignedAt | string (date-time) | null | optional | — |
| data[].requestedById | string (uuid) | null | optional | — |
| data[].responsibleUserId | string (uuid) | null | optional | — |
| data[].linkedPermitId | string (uuid) | null | optional | — |
| data[].equipment | object | null | optional | — |
| data[].equipment.id | string (uuid) | optional | — |
| data[].equipment.equipmentType | string | optional | — |
| data[].equipment.make | string | null | optional | — |
| data[].equipment.model | string | null | optional | — |
| data[].createdAt | string (date-time) | optional | — |
| data[].updatedAt | string (date-time) | optional | — |
| pagination | object | optional | Canonical cursor-pagination block. On endpoints not yet retrofitted for true cursor support, `cursor` is a best-effort display-only value (not decodable, no `?cursor=` branch accepts it back) — see each endpoint's description for whether it has full cursor support. |
| pagination.cursor | string | null | optional | Opaque token for the next page, or `null` when `hasMore` is `false`. |
| pagination.limit | integer | required | — |
| pagination.hasMore | boolean | required | — |
| pagination.total | integer | required | Total row count matching the filter (not just this page). |
