You need to know when a naval vessel is near a port, whether it’s inside anchorage, holding outside the breakwater, or already berthed—plus how fast it’s moving, which way it’s pointing, whether congestion on the pier will delay entry, and how to push those signals into a dashboard or alerting bot. By the end of this post you’ll be able to query live AIS positions, list all vessels within a radius of a port, read port congestion snapshots, and combine multiple vessels into a single fleet call using vessels-api.com.
What matters for “naval vessel near a port” scenarios
When a naval asset approaches a port, the operational questions usually reduce to four API calls:
- Get the live track and current position of a specific vessel: GET /vessels/track
- Find all vessels within X NM of a port’s coordinates: GET /vessels/nearby
- Check whether the port has room or is backed up: GET /ports/congestion and GET /ports/data
- Aggregate multiple vessels into a single operational snapshot: POST /vessels/fleet
All endpoints share the same base URL and headers:
- Base URL: https://vessels-api.com/api/V1
- Auth: X-API-Key: YOUR_API_KEY
- Consistent JSON envelope: {"status","success","message","data"}
Quick start: auth, envelopes, and units
Every request sets the X-API-Key header; there’s no OAuth or endpoint-specific auth. Distances are in nautical miles (nm). Speeds are in knots. Timestamps are UTC with timezone offset in ISO 8601 (e.g., 2026-07-30T13:00:10+00:00). When you paginate (e.g., search), respect the per_page max 100. Cache duration is your choice; for live targeting near a port, set short cache TTLs or disable caching to keep position age low.
Documentation and MCP tools:
Track a specific vessel approaching a port
Use GET /vessels/track to retrieve the current position, optional position history, active route, predicted ETA, and recent port calls. This is your “single-vessel truth” when a ship is in approach, holding pattern, or already at berth. The endpoint accepts either imo or mmsi and offers optional flags to include route, predicted ETA, and weather.
Official cURL example (live fixture)
curl -H "X-API-Key: YOUR_API_KEY" "https://vessels-api.com/api/V1/vessels/track?mmsi=258785000&hours=48"
Official JSON (copy-paste as-is)
{
"status": 200,
"success": true,
"message": "OK",
"data": {
"vessel": {
"imo": "9122556",
"mmsi": "258785000",
"name": null
},
"current_position": {
"latitude": 53.33708,
"longitude": 7.17993,
"position_precision": "precise",
"position_accuracy_nm": null,
"speed_knots": 0,
"course_degrees": 212,
"heading_degrees": null,
"navigational_status": 5,
"timestamp_utc": "2017-01-24T04:07:31+00:00",
"age_minutes": 5155160,
"destination": null,
"eta": null
},
"predicted_eta": null,
"position_history": [],
"route": {
"departure_port": "HERACLIO",
"departure_time": "2026-04-27T21:35:36+00:00",
"destination_port": "HERACLIO",
"eta": null,
"distance_nm": null,
"avg_speed_knots": 17.1
},
"last_port_visits": [
{
"port_id": "156",
"port_name": "EMDEN",
"arrival_time": "2026-07-30T13:00:10+00:00",
"departure_time": null,
"duration_hours": null
},
{
"port_id": "10",
"port_name": "HERACLIO",
"arrival_time": "2026-04-20T12:00:00+00:00",
"departure_time": "2026-04-23T08:00:00+00:00",
"duration_hours": 68
}
],
"weather": null
}
}
What to read from this payload:
- data.current_position.latitude/longitude: the live AIS fix (WGS84).
- speed_knots, course_degrees, heading_degrees: movement and bow direction—useful to detect approach versus drift.
- navigational_status: integer AIS nav status code; combine with speed_knots to infer anchorage or underway.
- timestamp_utc and age_minutes: how fresh the signal is—use for recency thresholds and UI badges.
- route and last_port_visits: context for the current voyage and latest calls.
Python: read a single vessel’s approach state
import requests
from datetime import datetime, timezone
API_KEY = "YOUR_API_KEY"
BASE = "https://vessels-api.com/api/V1"
def track_vessel(mmsi: str, hours: int = 24):
url = f"{BASE}/vessels/track"
params = {"mmsi": mmsi, "hours": hours}
resp = requests.get(url, headers={"X-API-Key": API_KEY}, params=params, timeout=15)
resp.raise_for_status()
payload = resp.json()["data"]
pos = payload["current_position"]
ts = datetime.fromisoformat(pos["timestamp_utc"].replace("Z","+00:00"))
fresh = (datetime.now(timezone.utc) - ts).total_seconds() / 60.0
state = {
"lat": pos["latitude"],
"lon": pos["longitude"],
"speed_kn": pos["speed_knots"],
"course_deg": pos["course_degrees"],
"nav_status": pos["navigational_status"],
"age_min": fresh
}
return state, payload.get("route"), payload.get("last_port_visits", [])
if __name__ == "__main__":
state, route, visits = track_vessel("258785000", hours=48)
print("Approach state:", state)
if route:
print("Route:", route)
if visits:
last = visits[0]
print("Last port call:", last["port_name"], "arrived", last["arrival_time"])
Tip: if your geofence is a port anchorage circle, compute distance from (lat, lon) to the anchorage centroid and set alert thresholds (e.g., entering within 3 nm, abeam within 1 nm, inside breakwater at 0.3 nm).
List all vessels near a port
When the question is “Which naval vessels are within 15–30 nm of this port?” use GET /vessels/nearby with the port’s coordinates and an appropriate radius. You can filter by ship_type if your workflow targets only a subset of naval hulls or auxiliaries.
cURL: vessels within 30 nm of Buenos Aires
Key response fields to use in your logic:
- data.center and data.radius_nm: confirms the query window.
- vessels[].position.latitude/longitude/timestamp_utc: latest fixes to map and to compute recency.
- vessels[].distance_nm: range to center—use this to sort a “closest to port” list.
- vessels[].navigational_status and speed_knots: underway vs. anchored at outer roads.
Implementation detail: set limit to constrain payload size on busy coasts, and apply your own type filter if needed. The max radius is 200 nm.
Port intelligence: congestion and live port data
Even if a naval vessel is inside the 12 nm zone, pier availability and current berths influence when it will actually dock. Use the port intelligence endpoints for this:
- GET /ports/congestion: required port_id is a UNLOCODE (examples supported here: ARBUE for Buenos Aires, SGSIN for Singapore, NLRTM for Rotterdam). Choose a period of 24h, 3d, or 7d.
- GET /ports/data: returns a single port’s details, including live vessel counts and expected vessels.
cURL: congestion snapshot for Buenos Aires (ARBUE)
cURL: live port data for ARBUE
What to wire into your UI:
- snapshot.vessels_in_anchorage vs. vessels_at_berth: a quick availability signal.
- statistics.avg_wait_time_hours_last_7d: an operational predictor for likely holding time outside the port.
- vessels_in_port and vessels_expected: combine with nearby detections to estimate crowding vs. actual berths.
Aggregate multiple naval assets in one call
For a task force or a patrol group nearing the same port, POST /vessels/fleet returns positions and routes for multiple vessels in one request. This avoids per-vessel polling and simplifies dashboard updates.
cURL: batch positions for two vessels
Use data.fleet.vessels_at_sea and vessels_in_port to render operational summaries, then plot each vessels[].position. For UI cadence, polling every 30–60 seconds is typical for near-port awareness; adjust based on your refresh needs.
Optional: emissions and ESG signals
If your organization tracks environmental indicators for port entry or compliance contexts, GET /vessels/green provides an IMO CII score for a selected period. Ratings map to A (best) through E (worst), based on IMO MEPC.339(76).
cURL: 30-day emissions snapshot by MMSI
Important fields: data.estimated_emissions.co2_tons, co2_per_nm, and cii.rating. If your workflow is purely operational (approach/berth/ETB/ETD) you can skip this, but it’s straightforward to surface in a sidebar pane.
Search, typing, and filtering
If you don’t have an MMSI/IMO on hand, GET /vessels/search supports fuzzy name queries along with filters by ship type, flag, deadweight, TEU, and build year. Combine it with a confirmation step that displays basic particulars before activating a track or fleet watch.
cURL: fuzzy search by name and flag
Use the pagination object from the response to page through results. The per_page max is 100.
Error handling and guardrails
- 401: invalid/missing X-API-Key — verify your header and avoid placing keys in client-side code.
- 404: vessel/port not found — double-check identifiers; for ports, use UNLOCODEs as required by each endpoint.
- 422: parameter out of range — watch radius (max 200 nm) and per_page (max 100).
- 429: rate limit exceeded — implement exponential backoff and jitter; consolidate with /vessels/fleet when you can.
- 500: server error — retry with backoff; log the response message for diagnostics.
Putting it together: a near-port watch pipeline
- Resolve targets: either you already know MMSI/IMO, or you use /vessels/search by name and confirm particulars.
- Set port context: pull /ports/data for the target port (e.g., ARBUE) to frame live capacity and expected traffic.
- Near-port scan: call /vessels/nearby for a 15–30 nm geofence to find additional vessels of interest, sort by distance_nm.
- Focused track: poll /vessels/track for each naval vessel of interest; trigger alerts on nav status or distance thresholds.
- Fleet efficiency: when tracking multiple hulls, consolidate with /vessels/fleet to minimize requests and simplify UI updates.
- Operational ETA: compare track ETA (if present) with port congestion statistics to estimate earliest practical berthing.
Developer notes that cut time
- Always check data.current_position.timestamp_utc; don’t assume freshness from your polling interval alone.
- Use distance_nm from /vessels/nearby directly for ranking; recalculate only if you change the geofence center.
- When overlaying routes, route.distance_nm may be null if the source AIS lacks the current voyage declaration.
- If you support both MMSI and IMO inputs, prefer MMSI for live tracking when available, as it’s the AIS transmitter identity.
- For UI, map navigational_status codes to human-readable states and combine with speed_knots for anchorage detection.
FAQ
Q: What refresh interval should I use for a near-port dashboard?
A: 30–60 seconds per poll is typical for approach monitoring. If you’re tracking 5+ vessels, switch to /vessels/fleet to reduce request count and keep latencies consistent.
Q: How do I filter the “nearby” list to naval vessels only?
A: Pass ship_type if you maintain a controlled list for naval hulls, or post-filter by vessel_type in the response. If you need strict control, maintain an allowlist of MMSIs/IMOs and use /vessels/fleet.
Q: What units and timezones are used?
A: Distances are nautical miles (nm), speeds are knots, and timestamps are UTC ISO 8601. This avoids local-time edge cases around DST or port timezones.
Q: Can I get predicted ETAs directly from the track?
A: Yes. Use GET /vessels/track with include_predicted_eta if you need ETA predictions. If predicted_eta is null, fall back to current_position.eta (if present) or compute an estimate from distance and average speed.
Q: How do I handle missing names or null fields?
A: Rely on MMSI/IMO as primary keys. Names can be null or change. Guard your parsing against nulls in route, predicted_eta, weather, and destination fields.
Build your near-port watch in a single afternoon: start with a focused track, add a nearby sweep, and surface port congestion right next to the map. Get your key and ship an integration with the Register link, scan the Documentation, and explore live calls in the MCP.