Skip to main content

REST API Reference

All REST endpoints are mounted under /v1. Responses use JSON and include HATEOAS _links where applicable.

Base URL: http://localhost:3000/v1


GPS Routes

GET /v1/peplink/gps

Returns the current GPS position from the Peplink router.

Response 200 OK

{
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 100.0,
"timestamp": 1717934400,
"speedKmh": 45.2,
"speedMps": 12.56,
"speedMph": 28.09,
"speedKnots": 24.41,
"_links": {
"self": "/v1/peplink/gps"
}
}
FieldTypeDescription
latitudef64Latitude in degrees
longitudef64Longitude in degrees
altitudef64Altitude in meters
timestampi64Unix timestamp (seconds)
speedKmhf64Speed in km/h
speedMpsf64Speed in meters per second
speedMphf64Speed in miles per hour
speedKnotsf64Speed in knots

Status Codes: 200, 500


GET /v1/peplink/gps/speed

Returns only speed data from the current GPS position.

Response 200 OK

{
"speedKmh": 45.2,
"speedMps": 12.56,
"speedMph": 28.09,
"speedKnots": 24.41,
"timestamp": 1717934400,
"_links": {
"self": "/v1/peplink/gps/speed"
}
}

Status Codes: 200, 500


GET /v1/peplink/gps/history

Returns GPS location history within a time range. Defaults to the last 24 hours.

Query Parameters

ParameterTypeRequiredDefaultDescription
starti64No24h agoStart of range as unix timestamp (seconds)
endi64NonowEnd of range as unix timestamp (seconds)

Validation:

  • start must be a valid unix timestamp
  • end must be a valid unix timestamp
  • start must be before end

Response 200 OK

{
"locations": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-06-09T12:00:00Z",
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 100.0,
"speed_kmh": 45.2,
"speed_mps": 12.56,
"speed_mph": 28.09,
"speed_knots": 24.41,
"trip_id": null
}
],
"_links": {
"self": "/v1/peplink/gps/history"
}
}

Status Codes: 200, 400, 500


Trip Routes

POST /v1/trips

Create a new trip. Validates coordinates and checks that the referenced category, sender, and receiver exist.

Request Body (JSON, camelCase)

FieldTypeRequiredDefaultDescription
startLatitudef64Yes--Start latitude (-90 to 90)
startLongitudef64Yes--Start longitude (-180 to 180)
cargostringYes--Cargo description (must not be empty)
categoryIdUUIDYes--Cargo category ID (must exist)
weightf64Yes--Cargo weight
senderIdUUIDYes--Sender client ID (must exist)
receiverIdUUIDYes--Receiver client ID (must exist)
logGpsboolNofalseWhether to log GPS locations for this trip

Example

{
"startLatitude": 51.5074,
"startLongitude": -0.1278,
"cargo": "Electronics",
"categoryId": "550e8400-e29b-41d4-a716-446655440001",
"weight": 250.5,
"senderId": "550e8400-e29b-41d4-a716-446655440002",
"receiverId": "550e8400-e29b-41d4-a716-446655440003",
"logGps": true
}

Response 201 Created

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"start_time": "2026-06-09T12:00:00Z",
"end_time": null,
"start_latitude": 51.5074,
"start_longitude": -0.1278,
"end_latitude": null,
"end_longitude": null,
"cargo": "Electronics",
"category_id": "550e8400-e29b-41d4-a716-446655440001",
"weight": 250.5,
"sender_id": "550e8400-e29b-41d4-a716-446655440002",
"receiver_id": "550e8400-e29b-41d4-a716-446655440003",
"status": "Active",
"log_gps": true,
"_links": {
"self": "/v1/trips/550e8400-e29b-41d4-a716-446655440000"
}
}

Validation Rules:

  • Latitude must be between -90 and 90
  • Longitude must be between -180 and 180
  • cargo must not be empty (after trim)
  • categoryId must reference an existing category
  • senderId must reference an existing client
  • receiverId must reference an existing client

Status Codes: 201, 400, 500


GET /v1/trips

List all trips ordered by start time descending.

Response 200 OK

{
"trips": [
{
"id": "...",
"start_time": "2026-06-09T12:00:00Z",
"end_time": null,
"start_latitude": 51.5074,
"start_longitude": -0.1278,
"end_latitude": null,
"end_longitude": null,
"cargo": "Electronics",
"category_id": "...",
"weight": 250.5,
"sender_id": "...",
"receiver_id": "...",
"status": "Active",
"log_gps": true
}
],
"_links": {
"self": "/v1/trips"
}
}

Status Codes: 200, 500


GET /v1/trips/:id

Get a single trip by ID. Includes GPS locations recorded for this trip, ordered by timestamp ascending.

Path Parameters

ParameterTypeDescription
idUUIDTrip ID

Response 200 OK

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"start_time": "2026-06-09T12:00:00Z",
"end_time": null,
"start_latitude": 51.5074,
"start_longitude": -0.1278,
"end_latitude": null,
"end_longitude": null,
"cargo": "Electronics",
"category_id": "...",
"weight": 250.5,
"sender_id": "...",
"receiver_id": "...",
"status": "Active",
"log_gps": true,
"locations": [
{
"id": "...",
"timestamp": "2026-06-09T12:01:00Z",
"latitude": 51.5080,
"longitude": -0.1270,
"altitude": 98.5,
"speed_kmh": 35.0,
"speed_mps": 9.72,
"speed_mph": 21.75,
"speed_knots": 18.90,
"trip_id": "550e8400-e29b-41d4-a716-446655440000"
}
],
"_links": {
"self": "/v1/trips/550e8400-e29b-41d4-a716-446655440000"
}
}

Status Codes: 200, 404, 500


PUT /v1/trips/:id

Update a trip. Only provided fields are changed (partial update). Validates status transitions and foreign key existence for any changed FK fields.

Path Parameters

ParameterTypeDescription
idUUIDTrip ID

Request Body (JSON, camelCase -- all fields optional)

FieldTypeDescription
cargostringCargo description
categoryIdUUIDCargo category ID
weightf64Cargo weight
senderIdUUIDSender client ID
receiverIdUUIDReceiver client ID
statusstringTrip status: ACTIVE, COMPLETED, or CANCELLED
logGpsboolWhether to log GPS locations

Status Transition Rules:

  • ACTIVE -> COMPLETED: Allowed
  • ACTIVE -> CANCELLED: Allowed
  • COMPLETED -> ACTIVE: Not allowed (returns 400)
  • CANCELLED -> ACTIVE: Not allowed (returns 400)
  • COMPLETEDCANCELLED: Allowed

Response 200 OK -- Returns the full updated trip object with _links.

Status Codes: 200, 400, 404, 500


DELETE /v1/trips/:id

Delete a trip and its associated GPS locations (transactional).

Path Parameters

ParameterTypeDescription
idUUIDTrip ID

Response 204 No Content (empty body)

Status Codes: 204, 404, 500


POST /v1/trips/:id/complete

Mark an active trip as completed with end coordinates.

Path Parameters

ParameterTypeDescription
idUUIDTrip ID

Request Body (JSON, camelCase)

FieldTypeRequiredDescription
endLatitudef64YesEnd latitude (-90 to 90)
endLongitudef64YesEnd longitude (-180 to 180)

Example

{
"endLatitude": 52.5200,
"endLongitude": 13.4050
}

Validation:

  • Trip must be in ACTIVE status (returns 400 if COMPLETED or CANCELLED)
  • Coordinates must be valid ranges

Response 200 OK -- Returns the completed trip with status: "Completed", end_time set, and _links.

Status Codes: 200, 400, 404, 500


Client Routes

POST /v1/clients

Create a new client with optional contact persons. If contacts are provided and none has isPrimary set, the first contact is automatically promoted to primary.

Request Body (JSON, camelCase)

FieldTypeRequiredDefaultDescription
namestringYes--Client name (must not be empty)
emailstringYes--Email address
phonestringYes--Phone number
streetstringYes--Street name
houseNumberstringYes--House number
postalCodestringYes--Postal code
citystringYes--City
countrystringYes--Country
additionalInfostringNonullAdditional address info
contactsarrayNo[]Array of contact person objects

Contact Person Object

FieldTypeRequiredDefaultDescription
namestringYes--Contact name
emailstringYes--Contact email
phonestringYes--Contact phone
positionstringYes--Job position/title
isPrimaryboolNofalseWhether this is the primary contact

Example

{
"name": "ACME Logistics",
"email": "info@acme.example.com",
"phone": "+49 30 12345678",
"street": "Hauptstrasse",
"houseNumber": "42",
"postalCode": "10115",
"city": "Berlin",
"country": "DE",
"additionalInfo": "Building B, 3rd floor",
"contacts": [
{
"name": "John Doe",
"email": "john@acme.example.com",
"phone": "+49 30 12345679",
"position": "Logistics Manager",
"isPrimary": true
}
]
}

Response 201 Created

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ACME Logistics",
"email": "info@acme.example.com",
"phone": "+49 30 12345678",
"street": "Hauptstrasse",
"house_number": "42",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"additional_info": "Building B, 3rd floor",
"contacts": [
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"email": "john@acme.example.com",
"phone": "+49 30 12345679",
"position": "Logistics Manager",
"is_primary": true,
"client_id": "550e8400-e29b-41d4-a716-446655440000"
}
],
"_links": {
"self": "/v1/clients/550e8400-e29b-41d4-a716-446655440000"
}
}

Status Codes: 201, 400, 500


GET /v1/clients

List all clients with their contacts, ordered by name ascending.

Response 200 OK

{
"clients": [
{
"id": "...",
"name": "ACME Logistics",
"email": "info@acme.example.com",
"phone": "+49 30 12345678",
"street": "Hauptstrasse",
"house_number": "42",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"additional_info": null,
"contacts": [ ... ]
}
],
"_links": {
"self": "/v1/clients"
}
}

Status Codes: 200, 500


GET /v1/clients/:id

Get a single client by ID with contacts and associated trips (where the client is sender or receiver).

Path Parameters

ParameterTypeDescription
idUUIDClient ID

Response 200 OK

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ACME Logistics",
"email": "info@acme.example.com",
"phone": "+49 30 12345678",
"street": "Hauptstrasse",
"house_number": "42",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"additional_info": null,
"contacts": [ ... ],
"trips": [ ... ],
"_links": {
"self": "/v1/clients/550e8400-e29b-41d4-a716-446655440000"
}
}

The trips array contains all trips where this client is either the sender or receiver, ordered by start time descending.

Status Codes: 200, 404, 500


PUT /v1/clients/:id

Update client fields. Only provided fields are changed (partial update).

Path Parameters

ParameterTypeDescription
idUUIDClient ID

Request Body (JSON, camelCase -- all fields optional)

FieldTypeDescription
namestringClient name
emailstringEmail address
phonestringPhone number
streetstringStreet name
houseNumberstringHouse number
postalCodestringPostal code
citystringCity
countrystringCountry
additionalInfostringAdditional address info

Response 200 OK -- Returns the updated client (without contacts) with _links.

Status Codes: 200, 404, 500


POST /v1/clients/:id/contacts

Add a contact person to an existing client. If isPrimary is true, existing contacts are demoted (handled by the DB layer).

Path Parameters

ParameterTypeDescription
idUUIDClient ID

Request Body (JSON, camelCase)

FieldTypeRequiredDefaultDescription
namestringYes--Contact name
emailstringYes--Contact email
phonestringYes--Contact phone
positionstringYes--Job position/title
isPrimaryboolNofalseWhether this is the primary contact

Response 201 Created

{
"id": "660e8400-e29b-41d4-a716-446655440000",
"name": "Jane Smith",
"email": "jane@acme.example.com",
"phone": "+49 30 12345680",
"position": "Fleet Manager",
"is_primary": false,
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"_links": {
"self": "/v1/clients/550e8400-.../contacts/660e8400-..."
}
}

Status Codes: 201, 404, 500


PUT /v1/clients/:client_id/contacts/:contact_id

Update a contact person. Only provided fields are changed.

Path Parameters

ParameterTypeDescription
client_idUUIDClient ID
contact_idUUIDContact ID

Request Body (JSON, camelCase -- all fields optional)

FieldTypeDescription
namestringContact name
emailstringContact email
phonestringContact phone
positionstringJob position/title
isPrimaryboolWhether this is the primary contact

Response 200 OK -- Returns the updated contact with _links.

Status Codes: 200, 404, 500


DELETE /v1/clients/:client_id/contacts/:contact_id

Delete a contact person. If the deleted contact was primary, the DB layer auto-promotes the next contact alphabetically.

Path Parameters

ParameterTypeDescription
client_idUUIDClient ID
contact_idUUIDContact ID

Response 204 No Content (empty body)

Status Codes: 204, 404, 500


Category Routes

POST /v1/categories

Create a new cargo category. The name is trimmed before insertion.

Request Body (JSON)

FieldTypeRequiredDescription
namestringYesCategory name (must not be empty, must be unique)
descriptionstringNoCategory description

Example

{
"name": "Fragile Goods",
"description": "Items requiring careful handling"
}

Response 201 Created

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Fragile Goods",
"description": "Items requiring careful handling",
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z",
"_links": {
"self": "/v1/categories/550e8400-e29b-41d4-a716-446655440000"
}
}

Validation:

  • name must not be empty (after trim)
  • name must be unique (returns 400 if duplicate: "A category with this name already exists")

Status Codes: 201, 400, 500


GET /v1/categories

List all categories ordered by name ascending.

Response 200 OK

{
"categories": [
{
"id": "...",
"name": "Fragile Goods",
"description": "Items requiring careful handling",
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z"
}
],
"_links": {
"self": "/v1/categories"
}
}

Status Codes: 200, 500


GET /v1/categories/:id

Get a single category by ID with associated trips.

Path Parameters

ParameterTypeDescription
idUUIDCategory ID

Response 200 OK

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Fragile Goods",
"description": "Items requiring careful handling",
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z",
"trips": [
{
"id": "...",
"cargo": "Electronics",
"status": "Active",
"..."
}
],
"_links": {
"self": "/v1/categories/550e8400-e29b-41d4-a716-446655440000"
}
}

Status Codes: 200, 404, 500


PUT /v1/categories/:id

Update a category. Only provided fields are changed. The name is trimmed.

Path Parameters

ParameterTypeDescription
idUUIDCategory ID

Request Body (JSON -- all fields optional)

FieldTypeDescription
namestringCategory name (must be unique)
descriptionstringCategory description

Validation:

  • If name is provided and conflicts with an existing category, returns 400

Response 200 OK -- Returns the updated category with _links.

Status Codes: 200, 400, 404, 500


DELETE /v1/categories/:id

Delete a category. Returns 400 if trips still reference this category.

Path Parameters

ParameterTypeDescription
idUUIDCategory ID

Validation:

  • Cannot delete a category that is still referenced by trips (returns 400: "Cannot delete category: trips still reference it")

Response 204 No Content (empty body)

Status Codes: 204, 400, 404, 500


Pegel Routes

GET /v1/pegel/nearest

Returns the nearest water gauge station from Pegelonline WSV with current water level measurement. The server uses its own GPS position (from Peplink) to determine the nearest station.

Two modes:

  • Whitelist mode (recommended): stations configured — searches within 200 km, filters by whitelist, returns the nearest match regardless of radius_km.
  • Radius mode: stations empty — searches by GPS proximity within radius_km.

Query Parameters:

ParameterTypeRequiredDescription
waterstringNoFilter by water body name (e.g. RHEIN, MOSEL)
radiusnumberNoSearch radius in km (radius mode only, capped at server config)

Response (200):

{
"stationName": "OTTENHEIM",
"waterName": "RHEIN",
"km": 270.6,
"latitude": 48.3544,
"longitude": 7.7282,
"value": 324,
"unit": "cm",
"timestamp": "2026-06-15T14:00:00+02:00",
"state": "normal"
}

state values: low (≤ MNW), normal (MNW–MHW), high (≥ MHW), unknown, commented, out-dated

Response (404): Pegelonline disabled in config, no station found, or no data yet (first poll pending).

Status Codes: 200, 404, 500

Configuration:

[pegelonline]
enabled = true
base_url = "https://pegelonline.wsv.de/webservices/rest-api/v2"
radius_km = 30 # Only used when stations is empty
stations = ["OTTENHEIM", "IFFEZHEIM", "MAXAU"] # Exact PegelOnline API shortnames

Data is polled every 5 minutes and cached server-side. The endpoint reads from cache — no external API call per request.


Watchlist Routes

GET /v1/watchlist/entries

List all watchlist entries, ordered by name ascending.

Response 200 OK

{
"entries": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"mmsi": "211234567",
"name": "MS Harmonie",
"category_id": "...",
"alert_sound": "sonar",
"danger_sound": "alarm",
"star_color": "#EF4444",
"range_thresholds": [10.0, 7.0, 5.0, 2.0, 1.0],
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z"
}
],
"_links": {
"self": "/v1/watchlist/entries"
}
}

Status Codes: 200, 500


POST /v1/watchlist/entries

Create a new watchlist entry.

Request Body (JSON, camelCase)

FieldTypeRequiredDefaultDescription
mmsistringYes--MMSI number of the vessel
namestringYes--Display name
categoryIdUUIDNonullWatchlist category ID
alertSoundstringNonullSound key for alert threshold
dangerSoundstringNonullSound key for danger threshold
starColorstringNonullHex color for stars (inherits from category/settings)
rangeThresholdsnumber[]Nonull5 distance thresholds in km (inherits from category/settings)

Response 201 Created -- Returns the created entry with _links.

Status Codes: 201, 400, 500


GET /v1/watchlist/entries/:id

Get a single watchlist entry by ID.

Path Parameters

ParameterTypeDescription
idUUIDEntry ID

Response 200 OK

Status Codes: 200, 404, 500


PUT /v1/watchlist/entries/:id

Update a watchlist entry. Only provided fields are changed.

Path Parameters

ParameterTypeDescription
idUUIDEntry ID

Request Body (JSON, camelCase -- all fields optional)

FieldTypeDescription
mmsistringMMSI number
namestringDisplay name
categoryIdUUIDCategory ID
alertSoundstringAlert sound key
dangerSoundstringDanger sound key
starColorstringHex star color
rangeThresholdsnumber[]Distance thresholds

Response 200 OK -- Returns the updated entry with _links.

Status Codes: 200, 404, 500


DELETE /v1/watchlist/entries/:id

Delete a watchlist entry.

Path Parameters

ParameterTypeDescription
idUUIDEntry ID

Response 204 No Content (empty body)

Status Codes: 204, 404, 500


GET /v1/watchlist/categories

List all watchlist categories, ordered by name ascending.

Response 200 OK

{
"categories": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Freighter",
"color": "#EF4444",
"display_type": "stars",
"sound_enabled": true,
"overlay_enabled": true,
"overlay_icon": null,
"alert_sound": "sonar",
"danger_sound": "alarm",
"range_thresholds": [10.0, 7.0, 5.0, 2.0, 1.0],
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z"
}
],
"_links": {
"self": "/v1/watchlist/categories"
}
}

Status Codes: 200, 500


POST /v1/watchlist/categories

Create a new watchlist category.

Request Body (JSON, camelCase)

FieldTypeRequiredDefaultDescription
namestringYes--Category name
colorstringYes--Hex color
displayTypestringNo"stars"Display mode: stars or custom
soundEnabledboolNotrueWhether sound alerts are enabled
overlayEnabledboolNotrueWhether overlay display is enabled
overlayIconstringNonullLucide icon name for custom display
alertSoundstringNonullSound key for alert threshold
dangerSoundstringNonullSound key for danger threshold
rangeThresholdsnumber[]Nonull5 distance thresholds in km

Response 201 Created -- Returns the created category with _links.

Status Codes: 201, 400, 500


GET /v1/watchlist/categories/:id

Get a single watchlist category by ID.

Path Parameters

ParameterTypeDescription
idUUIDCategory ID

Status Codes: 200, 404, 500


PUT /v1/watchlist/categories/:id

Update a watchlist category. Only provided fields are changed.

Path Parameters

ParameterTypeDescription
idUUIDCategory ID

Request Body (JSON, camelCase -- all fields optional)

FieldTypeDescription
namestringCategory name
colorstringHex color
displayTypestringDisplay mode
soundEnabledboolSound alerts enabled
overlayEnabledboolOverlay display enabled
overlayIconstringLucide icon name
alertSoundstringAlert sound key
dangerSoundstringDanger sound key
rangeThresholdsnumber[]Distance thresholds

Response 200 OK -- Returns the updated category with _links.

Status Codes: 200, 400, 404, 500


DELETE /v1/watchlist/categories/:id

Delete a watchlist category.

Path Parameters

ParameterTypeDescription
idUUIDCategory ID

Response 204 No Content (empty body)

Status Codes: 204, 404, 500


GET /v1/watchlist/settings

Get global watchlist settings (singleton).

Response 200 OK

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"range_thresholds": [10.0, 7.0, 5.0, 2.0, 1.0],
"default_alert_sound": "sonar",
"default_danger_sound": "alarm",
"default_star_color": "#EF4444",
"updated_at": "2026-06-09T12:00:00Z",
"_links": {
"self": "/v1/watchlist/settings"
}
}

Status Codes: 200, 404, 500


PUT /v1/watchlist/settings

Update global watchlist settings (upserts). Only provided fields are changed.

Request Body (JSON, camelCase -- all fields optional)

FieldTypeDescription
rangeThresholdsnumber[]5 distance thresholds in km
defaultAlertSoundstringDefault alert sound key
defaultDangerSoundstringDefault danger sound key
defaultStarColorstringDefault hex star color

Response 200 OK -- Returns the updated settings with _links.

Status Codes: 200, 500


GET /v1/watchlist/sounds

List all available watchlist sounds (built-in + custom).

Response 200 OK

{
"sounds": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"key": "sonar",
"name": "Sonar",
"volume": 80,
"file_id": "...",
"builtin": true,
"created_at": "2026-06-09T12:00:00Z"
}
],
"_links": {
"self": "/v1/watchlist/sounds"
}
}

Status Codes: 200, 500


POST /v1/watchlist/sounds/upload

Upload a custom sound file. Accepts multipart/form-data.

Form Fields

FieldTypeRequiredDescription
filefileYesAudio file (MP3, WAV, OGG)
namestringYesDisplay name for the sound

Response 201 Created -- Returns the created sound object.

Status Codes: 201, 400, 500


DELETE /v1/watchlist/sounds/:id

Delete a custom sound. Built-in sounds cannot be deleted. Returns 400 if the sound is still referenced by entries, categories, or settings.

Path Parameters

ParameterTypeDescription
idUUIDSound ID

Response 204 No Content (empty body)

Status Codes: 204, 400, 404, 500


PUT /v1/watchlist/sounds/:id/volume

Update the volume of a watchlist sound (0-100).

Path Parameters

ParameterTypeDescription
idUUIDSound ID

Request Body (JSON)

FieldTypeRequiredDescription
volumeintegerYesVolume level (0-100)

Response 200 OK -- Returns the updated sound object.

Status Codes: 200, 404, 500


POST /v1/watchlist/test-alert

Send a test alert through the WebSocket watchlist channel. Useful for testing sound and overlay configuration.

Response 200 OK

Status Codes: 200, 500


Vessel Routes

GET /v1/vessels/active

Returns AIS vessels seen within a look-back window, ordered by lastSeen descending (newest first). Records come from the persistent vessels registry, so this includes vessels that are no longer on the live map. Requires the api_key header (see Authentication).

Query Parameters

ParameterTypeRequiredDefaultDescription
withinSecsi64No3600Look-back window in seconds — only vessels whose lastSeen falls within it are returned

Response 200 OK

[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"mmsi": 211234567,
"name": "MS Harmonie",
"shipType": 70,
"length": 110,
"beam": 11,
"callsign": "DA1234",
"firstSeen": "2026-07-20T08:00:00Z",
"lastSeen": "2026-07-27T12:34:56Z",
"updatedAt": "2026-07-27T12:34:56Z",
"source": "local"
}
]
FieldTypeDescription
idUUIDVessel registry record ID
mmsii64Maritime Mobile Service Identity
namestring|nullVessel name (null if never received)
shipTypeinteger|nullRaw AIS ship type code (null if unknown)
lengthinteger|nullLength in meters (null if unknown)
beaminteger|nullBeam in meters (null if unknown)
callsignstring|nullRadio call sign (null if unknown)
firstSeenstringRFC 3339 timestamp of first AIS contact
lastSeenstringRFC 3339 timestamp of most recent AIS contact
updatedAtstringRFC 3339 timestamp of the last registry write
sourcestring|nullLast effective tracking source: local | aisstream (null until first activity flush)

Status Codes: 200, 500


GET /v1/vessels

Returns all vessels in the persistent vessels registry, ordered by lastSeen descending (newest first). Requires the api_key header (see Authentication).

Response 200 OK

[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"mmsi": 211234567,
"name": "MS Harmonie",
"shipType": 70,
"length": 110,
"beam": 11,
"callsign": "DA1234",
"firstSeen": "2026-07-20T08:00:00Z",
"lastSeen": "2026-07-27T12:34:56Z",
"updatedAt": "2026-07-27T12:34:56Z"
}
]

Field shapes match GET /v1/vessels/active.

Status Codes: 200, 500


GET /v1/vessels/{mmsi}

Returns a single registry vessel by MMSI. Requires the api_key header.

Path Parameters

ParameterTypeDescription
mmsii64Maritime Mobile Service Identity

Response 200 OK -- Single vessel record (same shape as GET /v1/vessels/active entries).

Status Codes: 200, 404 (no vessel with that MMSI), 500


PUT /v1/vessels/{mmsi}

Manually overwrite a vessel's static fields. Full-representation update — the body always carries all 5 editable fields; an omitted field is written as null (no partial merge). Requires the api_key header.

Path Parameters

ParameterTypeDescription
mmsii64Maritime Mobile Service Identity

Request Body (JSON, camelCase -- all fields nullable, full representation)

FieldTypeDescription
namestring|nullVessel name
shipTypeinteger|nullRaw AIS ship type code
lengthinteger|nullLength in meters
beaminteger|nullBeam in meters
callsignstring|nullRadio call sign

Example

{
"name": "MS Harmonie",
"shipType": 70,
"length": 110,
"beam": 11,
"callsign": "DA1234"
}

Response 200 OK -- Returns the updated vessel (same shape as GET /v1/vessels/active entries).

Status Codes: 200, 404 (no vessel with that MMSI), 500


DELETE /v1/vessels/{mmsi}

Hard-delete a vessel from the registry. Requires the api_key header.

Path Parameters

ParameterTypeDescription
mmsii64Maritime Mobile Service Identity

Query Parameters

ParameterTypeRequiredDefaultDescription
ignoreboolNofalseAlso add the MMSI to the persistent ignore list (union'd with [ais] ignore_mmsi config) so it stays blacklisted after deletion

Response 204 No Content (empty body)

Status Codes: 204, 404 (no vessel with that MMSI), 500


GET /v1/vessels/history

Returns the static-field change audit log from vessels_history, ordered by changedAt descending (newest first). Each entry records one static field change. Requires the api_key header.

Query Parameters

ParameterTypeRequiredDefaultDescription
mmsii64No--Filter to a single vessel; omit for all vessels
limiti64No200Max entries to return (max 1000)

Response 200 OK

[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"mmsi": 211234567,
"field": "name",
"oldValue": null,
"newValue": "MS Harmonie",
"changedAt": "2026-07-27T12:34:56Z"
}
]
FieldTypeDescription
idUUIDHistory entry ID
mmsii64Maritime Mobile Service Identity
fieldstringChanged static field (name, shipType, length, beam, callsign)
oldValuestring|nullPrevious value
newValuestring|nullNew value
changedAtstringRFC 3339 timestamp of the change

Status Codes: 200, 500


Storage Routes

GET /v1/storage/{prefix}/files/{key}

Serve a file from storage. Used to serve uploaded sound files and other assets.

Path Parameters

ParameterTypeDescription
prefixstringStorage prefix (e.g. sounds)
keystringFile key

Response 200 OK -- Returns the file with appropriate Content-Type header.

Status Codes: 200, 404, 500


System Routes

GET /health

System health check. Returns database, Peplink, forwarding, and WebSocket status plus server uptime.

Response 200 OK (healthy) or 503 Service Unavailable (unhealthy)

{
"status": "healthy",
"uptime": 3600,
"services": {
"database": {
"status": "connected"
},
"peplink": {
"status": "connected"
},
"forwarding": {
"enabled": true,
"healthy": true,
"queueLength": 0,
"consecutiveFailures": 0
},
"websocket": {
"status": "running",
"connectedClients": 2
}
},
"timestamp": "2026-06-09T12:00:00+00:00"
}
FieldDescription
uptimeServer uptime in seconds
services.websocket.status"running" or "closing" (during graceful shutdown)
services.websocket.connectedClientsNumber of active WebSocket connections

The overall status is "healthy" only when the database is reachable. Returns 503 when the database check fails.

Status Codes: 200, 503


GET /v1

Returns API version, server info, and client info.

Response 200 OK

{
"version": "2026.6.1",
"server": {
"platform": "linux",
"arch": "x86_64"
},
"client": {
"ip": "127.0.0.1",
"userAgent": "curl/8.1.0"
},
"timestamp": "2026-06-09T12:00:00+00:00"
}

Status Codes: 200


GET /

Redirects to /v1 with 301 Moved Permanently.


GET /v1/openapi.json

Returns the OpenAPI 3.0 specification as JSON. Returns 404 if disabled in configuration (docs.openapi_json = false).

Status Codes: 200, 404


GET /v1/swagger-ui/

Serves the Swagger UI for interactive API exploration. Only available when docs.swagger_ui = true in configuration.