Compliance
ISM compliance records — drill cadences, permits to work, MARPOL regulatory record entries.
/compliance/vessel/{vesselId}/drills/cadencesList a vessel's ISM drill cadences
Returns EVERY active `DrillCadence` row for the vessel (typically fewer than 15 — one per ISM-required drill type), plus pre-computed `overdue` and `dueSoon` sub-lists. Not a flat list at the top level and not cursor-paginated — this is a bounded ISM registry, not a growing log (see `docs/api.md` "Known gaps").
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| vesselId | path | string (uuid) | required | — |
| Field | Type | Required | Description |
|---|---|---|---|
| data | object | required | — |
| data.cadences | array<object> | optional | — |
| data.cadences[].id | string (uuid) | optional | — |
| data.cadences[].drillType | string | optional | DrillType enum, e.g. fire, abandon_ship, man_overboard, lifeboat_launch, steering_gear_test. |
| data.cadences[].frequency | string | optional | RecurringFrequency enum, e.g. monthly, quarterly, annually. |
| data.cadences[].isActive | boolean | optional | — |
| data.cadences[].lastCompletedAt | string (date-time) | null | optional | — |
| data.cadences[].nextDueAt | string (date-time) | null | optional | — |
| data.cadences[].alertDaysBeforeDue | integer | optional | — |
| data.cadences[].checklistTemplateId | string (uuid) | null | optional | — |
| data.cadences[].cadenceOverrideNote | string | null | optional | — |
| data.cadences[].createdAt | string (date-time) | optional | — |
| data.cadences[].updatedAt | string (date-time) | optional | — |
| data.overdue | array<any> | optional | — |
| data.dueSoon | array<object> | optional | — |
| data.dueSoon[].id | string (uuid) | optional | — |
| data.dueSoon[].drillType | string | optional | DrillType enum, e.g. fire, abandon_ship, man_overboard, lifeboat_launch, steering_gear_test. |
| data.dueSoon[].frequency | string | optional | RecurringFrequency enum, e.g. monthly, quarterly, annually. |
| data.dueSoon[].isActive | boolean | optional | — |
| data.dueSoon[].lastCompletedAt | string (date-time) | null | optional | — |
| data.dueSoon[].nextDueAt | string (date-time) | null | optional | — |
| data.dueSoon[].alertDaysBeforeDue | integer | optional | — |
| data.dueSoon[].checklistTemplateId | string (uuid) | null | optional | — |
| data.dueSoon[].cadenceOverrideNote | string | null | optional | — |
| data.dueSoon[].createdAt | string (date-time) | optional | — |
| data.dueSoon[].updatedAt | string (date-time) | optional | — |
/compliance/vessel/{vesselId}/ptwList a vessel's permits to work
Supports `?cursor=` pagination. Returns a trimmed summary projection (not every `PermitToWork` column) — see `Permit` schema.
| 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[].permitType | string | optional | PtwType enum: hot_work, enclosed_space, working_aloft, working_overside, diving. |
| data[].title | string | optional | — |
| data[].status | string | optional | PtwStatus enum: pending_approval, issued, active, suspended, closed, cancelled. |
| data[].issuedAt | string (date-time) | optional | — |
| data[].validFrom | string (date-time) | optional | — |
| data[].validUntil | string (date-time) | optional | — |
| data[].checklistInstanceId | string (uuid) | null | optional | — |
| data[].holderName | string | null | optional | — |
| data[].issuerName | string | null | optional | — |
| data[].isExpired | boolean | 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). |
/compliance/vessel/{vesselId}/regulatory-recordsList a vessel's MARPOL regulatory record entries
ORB Annex I (oily-water), GRB Annex V (garbage), and BWM (ballast water) log entries. Supports `?cursor=` pagination — cursor mode always sorts `(createdAt DESC, id DESC)`, NOT `operationDate` (the default page-mode sort field), because `operationDate` isn't guaranteed unique/monotonic.
| 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[].entryType | enum(orb_annex_i | grb_annex_v | bwm_record) | optional | — |
| data[].operationDate | string (date-time) | optional | — |
| data[].operationTime | string | null | optional | "HH:MM" UTC. |
| data[].positionLat | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].positionLon | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].positionText | string | null | optional | — |
| data[].officerUserId | string (uuid) | null | optional | — |
| data[].officer | object | null | optional | — |
| data[].officer.id | string (uuid) | optional | — |
| data[].officer.firstName | string | null | optional | — |
| data[].officer.lastName | string | null | optional | — |
| data[].officer.email | string | null | optional | — |
| data[].officer.phone | string | null | optional | — |
| data[].officer.profilePhoto | string | null | optional | — |
| data[].signatureUrl | string | null | optional | — |
| data[].operationCode | string | null | optional | ORB Annex I code A-H. |
| data[].operationDescription | string | null | optional | — |
| data[].quantityLitres | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].disposalMethod | string | null | optional | — |
| data[].retentionTankId | string | null | optional | — |
| data[].grbCategory | string | null | optional | GRB Annex V category A-H. |
| data[].grbDisposalLocation | string | null | optional | — |
| data[].grbMassKg | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].bwmOperationType | string | null | optional | — |
| data[].bwmSourceWater | string | null | optional | — |
| data[].bwmTreatmentSystem | string | null | optional | — |
| data[].bwmVolumeM3 | string | null | optional | Serialized as string to preserve Decimal precision. |
| data[].receiptUrl | 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). |
