You’re building a “yacht near a port” feature and need to answer: Which yachts are within X nautical miles of my marina? What’s their live position, heading, and ETA? Are they heading to this port, and what does port load look like right now? By the end of this post, you’ll know how to implement a reliable, production-grade workflow using vessels-api.com to detect nearby yachts, track a selected vessel, and layer in port intelligence for operations and guest services.
What you can build for yachts near a port
Whether you support marinas, harbor pilots, yacht agents, or premium guest services, you likely need a few core capabilities:
- Detect yachts within a configurable radius of a port or marina (e.g., 10–30 NM).
- Track a selected vessel’s live AIS position, course, and historical trail (up to 168 hours).
- Confirm route context, destination, and predicted ETA to your port.
- Check port conditions: how many vessels are in-port, and is congestion rising?
- Surface ETA feeds for arrivals to coordinate pilots, dock staff, fuel bunkering, and concierge services.
vessels-api.com provides 18 REST endpoints with a single API key and a single base URL. Every response uses a consistent JSON envelope: {status, success, message, data}. Below we’ll use four endpoints that map directly to the yacht-near-port use case:
- GET /vessels/nearby — find yachts within a radius of any lat/lon.
- GET /vessels/track — live position, route, predicted ETA, history, and last port calls.
- GET /ports/data — detailed info and live vessel counts for a port.
- GET /port/expected-arrivals — port arrival feed with ETAs and origin.
All examples use the base URL https://vessels-api.com/api/V1 and the X-API-Key header. For endpoint and schema details, see the Documentation. If you’re testing tools and workflows, check the MCP as well.
Detect yachts within a radius of your port
Use GET /vessels/nearby to return all vessels within a radius (nautical miles) of a given coordinate. You can filter by ship_type when you want only yachts, or leave it open to track all traffic near your harbor entrance.
cURL: yachts within 30 NM of Buenos Aires (example coordinates)
curl -H "X-API-Key: YOUR_API_KEY" "https://vessels-api.com/api/V1/vessels/track?mmsi=258785000&hours=48"
Key parameters:
- latitude, longitude: center point in decimal degrees.
- radius: nautical miles (default 50, max 200).
- ship_type: optional filter (e.g., Yacht) to keep your map uncluttered.
- limit: cap the returned list; defaults to 50.
JavaScript example: render nearby yachts on a map
async function fetchNearbyYachts(lat, lon, radiusNm = 20) {
const url = new URL("https://vessels-api.com/api/V1/vessels/nearby");
url.searchParams.set("latitude", lat);
url.searchParams.set("longitude", lon);
url.searchParams.set("radius", radiusNm);
url.searchParams.set("ship_type", "Yacht");
url.searchParams.set("limit", 100);
const res = await fetch(url.toString(), {
headers: { "X-API-Key": "YOUR_API_KEY" }
});
if (!res.ok) throw new Error("nearby request failed: " + res.status);
const payload = await res.json();
// The consistent envelope means we always read payload.data.vessels
const vessels = payload?.data?.vessels || [];
return vessels.map(v => ({
mmsi: v.mmsi,
imo: v.imo,
name: v.name,
type: v.ship_type,
lat: v.position?.latitude,
lon: v.position?.longitude,
speed: v.speed_knots,
course: v.course_degrees,
distanceNm: v.distance_nm,
timestampUtc: v.position?.timestamp_utc
}));
}
// Example usage:
fetchNearbyYachts(-34.60, -58.38, 30).then(yachts => {
// Plot markers with speed/course; pick a target yacht for detailed tracking
console.log("Nearby yachts:", yachts);
});
Response fields you will use immediately:
- vessels[].position.latitude and longitude: place markers on your chart.
- distance_nm: sort by proximity to your port.
- speed_knots and course_degrees: show motion trails and headings.
- navigational_status: gate alerts (e.g., anchored vs underway).
Track a selected yacht for live position, route, and ETA
Once a user clicks a nearby yacht, request GET /vessels/track with either MMSI or IMO to fetch current_position, optional position_history (up to 168 hours), the active route, predicted ETA, and last_port_visits. This context reduces false positives when a vessel passes near but isn’t actually inbound to your marina.
Official cURL and JSON sample (do not change)
The following is the official sample for a documented MMSI:
{
"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 for a yacht-near-port workflow:
- current_position.latitude/longitude, speed_knots, course_degrees: display live symbol and heading.
- current_position.timestamp_utc: all timestamps are UTC; display local time using the port’s timezone when needed.
- route.destination_port and predicted_eta: prioritize port calls truly headed for you.
- last_port_visits: enrich your operations panel with context about where the yacht has been.
Python example: poll track data and compute live ETA tiles
import requests
from datetime import datetime, timezone
BASE_URL = "https://vessels-api.com/api/V1"
API_KEY = "YOUR_API_KEY"
def get_track_by_mmsi(mmsi: str, hours=24):
url = f"{BASE_URL}/vessels/track"
params = {"mmsi": mmsi, "hours": hours}
r = requests.get(url, headers={"X-API-Key": API_KEY}, params=params, timeout=20)
r.raise_for_status()
payload = r.json()
if not payload.get("success"):
raise RuntimeError("API indicated failure: " + str(payload.get("message")))
return payload["data"]
def summarize_position(track):
pos = track.get("current_position", {}) or {}
lat, lon = pos.get("latitude"), pos.get("longitude")
speed, course = pos.get("speed_knots"), pos.get("course_degrees")
ts = pos.get("timestamp_utc")
return {
"lat": lat,
"lon": lon,
"speed_knots": speed,
"course_deg": course,
"timestamp_utc": ts
}
if __name__ == "__main__":
data = get_track_by_mmsi("258785000", hours=48)
current = summarize_position(data)
route = data.get("route") or {}
predicted_eta = data.get("predicted_eta") # may be null
print("Position:", current)
print("Route context:", route)
print("Predicted ETA:", predicted_eta)
Notes:
- Include hours up to 168 to get a full 7-day trail if you want to draw history polylines on your map.
- Handle nulls gracefully (e.g., predicted_eta may be null, as in the sample).
- Use the vessel object (imo, mmsi) as stable identifiers across sessions.
Port awareness: live port info and arrival feeds
Before you allocate a berth or dispatch a pilot boat, you may want to validate the port’s current load and near-term arrivals. Two endpoints help:
- GET /ports/data — returns port metadata plus live vessel counts in and expected.
- GET /port/expected-arrivals — structured list of arrivals with ETA and origin.
cURL: fetch a port’s basic live info (ARBUE as example)
Fields to use:
- vessels_in_port: helps you show load indicators in your UI.
- vessels_expected: pair this with expected-arrivals for a limited-horizon workload view.
- timezone: convert UTC timestamps from vessel tracking to local port time for operations staff.
cURL: expected arrivals for scheduling and concierge operations
The expected_arrivals array provides:
- mmsi/imo/name: vessel identity to cross-reference with your fleet or CRM.
- vessel_type: filter to yachts-only when building a guest-focused dashboard.
- eta and departure_port: timeline planning, especially when coordinating services.
Recommended workflow: “Yachts within 20 NM and actually inbound”
- Resolve your port’s center coordinate and timezone with GET /ports/data.
- Call GET /vessels/nearby with your radius, filter ship_type=Yacht, and collect core fields (mmsi, name, position, speed, course, distance_nm).
- Let the user pick a yacht (or automatically pick the closest top-N) and call GET /vessels/track for each selection, requesting hours=24 or 48 and include_predicted_eta if available.
- Use route.destination_port or predicted_eta when present to prioritize yachts actually headed to your port.
- Overlay GET /port/expected-arrivals to enrich your ops board with ETA context and reduce paging/phone calls.
Optional: batch tracking for multiple yachts
If you run a private fleet or a members-only marina list, use POST /vessels/fleet to fetch positions and routes in a single request, lowering API call overhead and simplifying synchronization.
cURL: batch positions for a small yacht list
Use cases:
- Draw a single synchronized update for all monitored yachts without per-vessel polling.
- Compute aggregate wait times and ETAs across a few berths.
- Keep your UI snappy with fewer network round-trips.
Units, pagination, envelopes, and errors
Implementation details that save time and prevent bugs:
- Units: distances in nautical miles (nm), speeds in knots, courses/headings in degrees, timestamps in UTC.
- Envelope: every response is {status, success, message, data}. Code defensively against missing/null fields inside data.
- Pagination: GET /vessels/search returns pagination in data.pagination. For nearby, use limit to cap results; ship_type filters reduce noise.
- Errors: check HTTP status and the envelope. Common codes: 400 (invalid parameter), 401 (bad/missing key), 404 (not found), 422 (out of range), 429 (rate limit), 500 (server).
- Auth: a single X-API-Key header; there is no per-endpoint OAuth complexity.
Port congestion snapshots
If you need queue and wait-time signals, GET /ports/congestion provides a snapshot and statistics. Request it with a valid port_id (e.g., ARBUE, SGSIN, NLRTM) and an optional period window:
Use the snapshot for real-time view (anchorage vs berth counts) and statistics for last-7d averages. This is ideal for on-call dashboards that warn marina ops when arrival peaks coincide with limited berths.
Search and enrich: identify a yacht by name or MMSI
When a guest provides only a vessel name, use GET /vessels/search to resolve the IMO/MMSI you’ll pass to tracking. Combine this with ship_type filters to avoid matching similarly named workboats.
cURL: fuzzy search by name plus type filter
The response includes vessels[] with identity (imo, mmsi) and particulars (flag, vessel_type, dimensions). Store the IMO/MMSI and use it consistently across track and fleet endpoints.
Practical implementation notes
- Caching: cache nearby lists briefly (e.g., 15–30 seconds) and track responses per vessel (e.g., 10–20 seconds) to balance freshness with cost.
- Refresh cadence: render a smooth UX by interpolating positions with speed/course between polls; reset to exact coordinate on each update.
- Geofencing: compute a simple circular geofence in nm around your port entrance; trigger webhooks when a yacht crosses inward and is inbound per route/ETA.
- Local time: use /ports/data.timezone to display ETAs and events in the port’s local timezone while keeping storage in UTC.
- Data model: store vessels keyed by MMSI and IMO; attach UI preferences (labels, icons) by MMSI; keep last known positions with update timestamps to show age.
FAQ
How do I limit results to yachts only?
Pass ship_type=Yacht to GET /vessels/nearby and GET /vessels/search. You can still track any vessel by MMSI/IMO with GET /vessels/track.
What’s the maximum position history window?
Use hours up to 168 on GET /vessels/track to retrieve a 7-day history. Skip or reduce this when you only need the current position for faster responses.
How do I display local time for ETA?
All timestamps are UTC. Query GET /ports/data to get the port’s timezone and convert display times client-side while storing UTC.
Can I poll multiple yachts efficiently?
Yes. Use POST /vessels/fleet with include_positions and include_routes to fetch a batch update for your monitored list in one round-trip.
How do errors surface in responses?
Check the HTTP status and the JSON envelope’s status/success/message fields. For example, 401 indicates an invalid or missing X-API-Key; 404 indicates vessel/port not found.
Build your yacht-near-port feature with a minimal surface area and consistent payloads. Start integrating now: use the Register link to get an API key, wire up the endpoints shown above using the Documentation, and explore test calls in the MCP.