API Specification¶
Overview¶
This document describes the backend API exposed by apps/backend/app/main.py and the router modules under apps/backend/app/routers/.
API Architecture¶
flowchart LR
Client[Client] --> API[REST API /api/v1/*]
API --> Services[Services]
Services --> DB[(DB/Redis)]
- API title:
Ulsan Port 3D Monitoring API - Current version:
0.1.0 - REST base path:
/api/v1 - Health path:
/health - WebSocket path:
/api/v1/ws/events
Unless otherwise noted, endpoints return JSON.
Common Conventions¶
Error Model¶
Several routers explicitly document ProblemDetail for error responses.
{
"type": "about:blank",
"title": "Error title",
"status": 500,
"detail": "Human-readable detail",
"instance": "/api/v1/example"
}
Validation Behavior¶
- Missing required path parameters result in framework-level validation failure.
- Query parameter constraints are enforced where declared.
- Endpoints with path or query validation may return
422 Unprocessable Entity.
Authentication¶
No authentication or authorization layer is defined in the current router set.
API Domains¶
flowchart TD
API[API] --> Port[Port]
API --> Vessels[Vessels]
API --> Berths[Berths]
API --> Weather[Weather]
API --> Stats[Stats]
API --> Docs[Docs]
API --> Scenarios[Scenarios]
API --> Graph[Graph]
API --> WebSocket[WebSocket]
API --> Insights[Insights]
Port¶
GET /api/v1/port/overview¶
Returns top-level port summary metrics.
- Query params: none
- Request body: none
Response 200 OK¶
{
"name": "string",
"zone_count": 0,
"berth_count": 0,
"active_vessel_count": 0,
"alert_count": 0,
"last_updated": "2026-04-23T00:00:00Z"
}
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/zones¶
Returns the list of port zones.
- Query params: none
- Request body: none
Response 200 OK¶
Array of zone objects:
[
{
"zone_id": "string",
"name": "string",
"zone_type": "string",
"description": "string",
"berth_count": 0,
"buoy_count": 0,
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
Vessels¶
Vessel Data Lifecycle¶
stateDiagram-v2
[*] --> Collected: ETL ingestion
Collected --> Normalized: Normalizer
Normalized --> Stored: DB upsert
Stored --> Live: GET /vessels/live
Stored --> Detail: GET /vessels/{id}
Live --> Streamed: WS /ws/events
Detail --> GraphExplored: GET /graph/Vessel/{id}
GET /api/v1/vessels¶
Returns all known vessels (same schema as /vessels/live).
- Query params:
zone: string | nullship_type: string | null- Request body: none
Response 200 OK¶
Same response format as GET /api/v1/vessels/live.
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/vessels/live¶
Returns the latest live vessel positions.
- Query params:
zone: string | nullship_type: string | null- Request body: none
Response 200 OK¶
[
{
"vessel_id": "string",
"name": "string",
"call_sign": "string",
"imo": "string",
"ship_type": "string",
"gross_tonnage": 0,
"lat": 0,
"lon": 0,
"speed": 0,
"course": 0,
"heading": 0,
"draft": 0,
"observed_at": "2026-04-23T00:00:00Z",
"updated_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/vessels/{vessel_id}¶
Returns a vessel detail payload with latest position and event history.
- Path params:
vessel_id: string— canonical vessel identifier- Query params: none
- Request body: none
Response 200 OK¶
{
"vessel_id": "string",
"latest_position": {
"vessel_id": "string",
"name": "string",
"call_sign": "string",
"imo": "string",
"ship_type": "string",
"gross_tonnage": 0,
"lat": 0,
"lon": 0,
"speed": 0,
"course": 0,
"heading": 0,
"draft": 0,
"observed_at": "2026-04-23T00:00:00Z",
"updated_at": "2026-04-23T00:00:00Z"
},
"events": [
{
"event_id": "string",
"vessel_id": "string",
"call_sign": "string",
"event_type": "string",
"berth_facility_code": "string",
"event_time": "2026-04-23T00:00:00Z",
"detail": "string",
"raw_data": "string",
"created_at": "2026-04-23T00:00:00Z"
}
]
}
Status Codes¶
200 OK404 Not Found(ProblemDetail)500 Internal Server Error(ProblemDetail)
Berths¶
GET /api/v1/berths¶
Returns berth inventory and latest berth state.
- Query params:
zone: string | nullstatus: string | nulloperator: string | null- Request body: none
Response 200 OK¶
[
{
"berth_id": "string",
"facility_code": "string",
"name": "string",
"zone_id": "string",
"zone_name": "string",
"operator_id": "string",
"operator_name": "string",
"length": 0,
"depth": 0,
"latitude": 0,
"longitude": 0,
"latest_status": "string",
"latest_status_detail": "string",
"latest_status_updated_at": "2026-04-23T00:00:00Z",
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/berths/{berth_id}¶
Returns a single berth detail (same schema as items in GET /api/v1/berths).
- Path params:
berth_id: string— UUID or facility code- Query params: none
- Request body: none
Response 200 OK¶
Same object shape as a single item in the GET /api/v1/berths array response.
Status Codes¶
200 OK404 Not Found(ProblemDetail)500 Internal Server Error(ProblemDetail)
GET /api/v1/berth-status/live¶
Returns live berth status records.
- Query params: none
- Request body: none
Response 200 OK¶
[
{
"berth_facility_code": "string",
"berth_name": "string",
"zone_name": "string",
"status": "string",
"status_detail": "string",
"updated_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
Weather¶
GET /api/v1/weather/current¶
Returns the latest weather observation and latest tide observation.
- Query params: none
- Request body: none
Response 200 OK¶
{
"observation": {
"zone_name": "string",
"wind_speed": 0,
"wind_dir": 0,
"temperature": 0,
"humidity": 0,
"pressure": 0,
"precipitation": 0,
"visibility": 0,
"wave_height": 0,
"observed_at": "2026-04-23T00:00:00Z"
},
"tide": {
"station_name": "string",
"tide_level": 0,
"observed_at": "2026-04-23T00:00:00Z"
}
}
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/weather/forecast¶
Returns weather forecast series, optionally scoped to a zone.
- Query params:
zone: string | null- Request body: none
Response 200 OK¶
[
{
"zone_name": "string",
"observations": [
{
"zone_name": "string",
"wind_speed": 0,
"wind_dir": 0,
"temperature": 0,
"humidity": 0,
"pressure": 0,
"precipitation": 0,
"visibility": 0,
"wave_height": 0,
"observed_at": "2026-04-23T00:00:00Z"
}
]
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
Statistics¶
Data Freshness Timeline¶
gantt
title Data Freshness Timeline
dateFormat HH:mm
axisFormat %H:%M
section Vessel Position
AIS collection :active, vp, 00:00, 5m
DB available :va, after vp, 1m
section Berth Status
Status poll :active, bs, 00:00, 10m
DB available :ba, after bs, 2m
section Weather
Observation poll :active, wo, 00:00, 15m
DB available :wa, after wo, 2m
section Statistics
Monthly batch :ms, 00:00, 60m
GET /api/v1/stats/arrivals¶
Returns monthly arrival statistics.
- Query params:
from_date: string | nullto_date: string | nullzone: string | null- Request body: none
Response 200 OK¶
[
{
"year_month": "2026-04",
"zone_name": "string",
"berth_name": "string",
"vessel_count": 0,
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/stats/liquid-cargo¶
Returns monthly liquid cargo statistics.
- Query params:
from_date: string | nullto_date: string | nullzone: string | null- Request body: none
Response 200 OK¶
[
{
"year_month": "2026-04",
"zone_name": "string",
"berth_name": "string",
"cargo_type": "string",
"volume_ton": 0,
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/stats/congestion¶
Returns congestion statistics.
- Query params:
from_date: string | nullto_date: string | null- Request body: none
Response 200 OK¶
[
{
"stat_date": "2026-04-23",
"waiting_count": 0,
"avg_wait_hours": 0,
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
Documents¶
GET /api/v1/docs/hazard¶
Returns hazard document records.
- Query params: none
- Request body: none
Response 200 OK¶
[
{
"doc_id": "string",
"title": "string",
"source_page": "string",
"published_date": "2026-04-23",
"file_url": "string",
"related_cargo_type": "string",
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/docs/msds¶
Returns MSDS document records.
- Query params: none
- Request body: none
Response 200 OK¶
[
{
"doc_id": "string",
"title": "string",
"cargo_type": "string",
"source_page": "string",
"file_url": "string",
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
Scenarios¶
GET /api/v1/scenarios¶
Returns available scenario summaries.
- Query params: none
- Request body: none
Response 200 OK¶
[
{
"scenario_id": "string",
"frame_count": 0,
"first_frame_index": 0,
"last_frame_index": 0,
"first_timestamp": "2026-04-23T00:00:00Z",
"last_timestamp": "2026-04-23T00:00:00Z",
"is_simulated": false
}
]
Status Codes¶
200 OK500 Internal Server Error(ProblemDetail)
GET /api/v1/scenarios/{scenario_id}/frames¶
Returns the ordered frames for a scenario.
- Path params:
scenario_id: string- Query params: none
- Request body: none
Response 200 OK¶
[
{
"frame_id": "string",
"scenario_id": "string",
"frame_index": 0,
"timestamp": "2026-04-23T00:00:00Z",
"vessel_positions": {},
"berth_statuses": {},
"weather": {},
"alerts": {},
"ai_summary": "string",
"is_simulated": false,
"created_at": "2026-04-23T00:00:00Z"
}
]
Status Codes¶
200 OK404 Not Found(ProblemDetail)500 Internal Server Error(ProblemDetail)
Graph¶
Graph Entity Relationships¶
erDiagram
PORT ||--o{ ZONE : hasZone
ZONE ||--o{ BERTH : hasBerth
ZONE ||--o{ BUOY : hasBuoy
ZONE ||--o{ ROUTE_SEGMENT : hasRouteSegment
OPERATOR ||--o{ BERTH : operates
OPERATOR ||--o{ TANK_TERMINAL : operates
VESSEL ||--o{ VOYAGE_CALL : hasVoyageCall
VOYAGE_CALL }o--|| BERTH : usesFacility
VESSEL ||--o{ VESSEL_POSITION : hasPosition
BERTH ||--o{ BERTH_STATUS : hasStatus
BERTH ||--o{ CARGO_TYPE : handlesCargo
CARGO_TYPE ||--o{ MSDS_DOC : hasMsds
GET /api/v1/graph/{entity_type}/{entity_id}¶
Returns graph context centered on one entity.
- Path params:
entity_type: stringentity_id: string- Query params:
depth: integer— allowed range1..3, default1- Request body: none
Response 200 OK¶
{
"center": {
"type": "string",
"id": "string",
"label": "string",
"data": {}
},
"relations": [
{
"predicate": "string",
"direction": "incoming",
"node": {
"type": "string",
"id": "string",
"label": "string",
"data": {}
}
}
]
}
Status Codes¶
200 OK404 Not Found(ProblemDetail)422 Unprocessable Entityfor invaliddepth500 Internal Server Error(ProblemDetail)
GET /api/v1/graph/explore¶
Explores graph neighborhoods around an optional entity anchor.
- Query params:
entity_type: string | nullentity_id: string | nulldepth: integer— allowed range1..3, default1direction: string— allowed valuesincoming,outgoing,both; defaultboth- Request body: none
Response 200 OK¶
{
"nodes": [
{
"type": "string",
"id": "string",
"label": "string",
"data": {}
}
],
"edges": [
{
"source_type": "string",
"source_id": "string",
"target_type": "string",
"target_id": "string",
"predicate": "string"
}
]
}
Status Codes¶
200 OK404 Not Found(ProblemDetail)422 Unprocessable Entityfor invaliddepthordirection500 Internal Server Error(ProblemDetail)
Event Flow¶
sequenceDiagram
participant Client
participant Server
participant Redis
Client->>Server: WS upgrade
Server->>Redis: subscribe
Redis-->>Server: event
Server-->>Client: JSON message
WebSocket¶
WS /api/v1/ws/events¶
Streams pub/sub event messages to connected clients.
- Handshake: WebSocket upgrade request
- Request body: not applicable
- Query params: none
Message Format¶
Server sends JSON messages forwarded from the Redis pub/sub channel:
Behavior¶
- Connection is accepted immediately.
- Server subscribes to the configured Redis channel.
- Incoming pub/sub messages are forwarded as JSON.
- Client messages are read and discarded to keep the socket open.
Status / Close Codes¶
101 Switching Protocolson successful handshake1011 Internal Errorwhen pub/sub subscription fails during setup
Insights¶
GET /api/v1/insights/current¶
Returns current rule-based insights with optional LLM summary.
- Query params: none
- Request body: none
Response 200 OK¶
{
"rule_based_insights": [
{
"insight_id": "string",
"type": "string",
"severity": "string",
"message": "string",
"related_entity_type": "string",
"source_data": {}
}
],
"llm_summary": "string",
"insight_count": 0
}
Status Codes¶
200 OK500 Internal Server Errorif insight generation fails
GET /api/v1/alerts¶
Returns the latest active alerts.
- Query params: none
- Request body: none
Response 200 OK¶
{
"alerts": [
{
"alert_id": "string",
"type": "string",
"severity": "string",
"message": "string",
"related_entity_type": "string",
"related_entity_id": "string",
"created_at": "2026-04-23T00:00:00Z"
}
],
"total": 0
}
Status Codes¶
200 OK500 Internal Server Errorif alert query fails
POST /api/v1/alerts/evaluate¶
Triggers alert evaluation and persists any newly generated alerts.
- Query params: none
- Request body: none
Response 200 OK¶
{
"new_alerts": [
{
"alert_type": "string",
"severity": "string",
"message": "string",
"related_entity_type": "string",
"related_entity_id": "string"
}
],
"count": 0
}
Status Codes¶
200 OK500 Internal Server Errorif evaluation fails
Health¶
GET /health¶
Simple service health probe.
- Query params: none
- Request body: none
Response 200 OK¶
Status Codes¶
200 OK
Router Inventory Summary¶
The current router set defines the following backend entry points:
GET /healthGET /api/v1/port/overviewGET /api/v1/zonesGET /api/v1/vesselsGET /api/v1/vessels/liveGET /api/v1/vessels/{vessel_id}GET /api/v1/berthsGET /api/v1/berths/{berth_id}GET /api/v1/berth-status/liveGET /api/v1/weather/currentGET /api/v1/weather/forecastGET /api/v1/stats/arrivalsGET /api/v1/stats/liquid-cargoGET /api/v1/stats/congestionGET /api/v1/docs/hazardGET /api/v1/docs/msdsGET /api/v1/scenariosGET /api/v1/scenarios/{scenario_id}/framesGET /api/v1/graph/{entity_type}/{entity_id}GET /api/v1/graph/exploreWS /api/v1/ws/eventsGET /api/v1/insights/currentGET /api/v1/alertsPOST /api/v1/alerts/evaluate
Request Lifecycle¶
graph LR
Client[Client] --> FastAPI[FastAPI]
FastAPI --> Router[Router]
Router --> Service[Service]
Service --> DB[(DB/Redis)]
DB --> Service
Service --> Response[Response]
This specification should be updated whenever router signatures, schema models, or version prefixes change.