DEV Community

VesselAPI
VesselAPI

Posted on • Originally published at vesselapi.com

What 'Real-Time' Actually Means for a Vessel Tracking API

Open any consumer ship-tracking website, zoom in on the Strait of Dover, and you'll see hundreds of little arrowheads gliding across the channel as if you were watching a live camera feed. It feels continuous. It feels like now.

Container ship at sea with suggested radio signals

It isn't. Not really. And the gap between what "real-time" feels like and what it actually is turns out to be the most important thing to understand before you start building anything on top of a vessel tracking API.

So let's look at it properly.

The thing you're actually receiving

A ship doesn't broadcast its position to a satellite that beams it instantly into your laptop. It broadcasts an AIS message — a short burst on one of two VHF channels, about 256 bits long, containing its identity, position, course, speed, and heading. AIS stands for Automatic Identification System, and it was designed in the 1990s primarily so that ships could avoid hitting each other, not so that you could draw nice dots on a Mapbox tile.

This origin matters. AIS is a collision-avoidance protocol that the rest of the world has retrofitted into a global tracking network. The clue is in how often a ship transmits: a vessel moving fast and turning sharply sends a position every 2 seconds. A vessel anchored or moored might send one every 3 minutes. The system spends its bandwidth where danger lives, not where dashboards want it.

Those VHF bursts then have to be heard by something. Two somethings, really:

  • Terrestrial receivers — antennas on coastlines, harbours, and ships, with a range of roughly 40 nautical miles. Coverage is excellent near land, nonexistent in the middle of the Pacific.
  • Satellites — low-Earth-orbit constellations that pick up AIS signals from open ocean. Coverage is global, but a given patch of ocean might only be overflown every 10 to 90 minutes depending on the constellation. And satellites struggle in busy areas, because hundreds of ships transmitting on the same frequency at the same time produce a sort of radio shouting match.

By the time a position reaches an API, it has been: broadcast by the vessel, received by a terrestrial antenna or a satellite, relayed to a ground station, decoded, deduplicated against other receivers that heard the same burst, and written to a database. The latency budget for that pipeline ranges from a couple of seconds (a tug in Rotterdam harbour) to tens of minutes (a bulk carrier two-thirds of the way across the Indian Ocean).

So when an API says "real-time vessel tracking," what it can honestly promise is: the most recent position we have, with the timestamp of when the vessel actually transmitted it. The honesty lives in that second clause.

Ship VHF antenna with distant coastal receiver

The latency budget, written down

Let's make this concrete. Here's a rough decomposition of the lag between a ship being somewhere and your code knowing it:

  1. Reporting interval — 2 seconds to 3 minutes, depending on the vessel's state.
  2. Reception delay — near-zero for terrestrial, anywhere from seconds to an hour for satellite, depending on overflight schedule and message collisions.
  3. Aggregation and ingestion — a few seconds to tens of seconds inside the data provider's pipeline.
  4. Your polling interval — entirely up to you, and usually the biggest contributor to your perceived latency.

Numbers 1 and 2 are physics and orbital mechanics. You cannot beat them. Number 3 is engineering, and a good provider keeps it small. Number 4 is yours to design.

This is why every position you fetch from a vessel API comes with a timestamp — and why that timestamp matters more than the moment you received the response. The data isn't lying when it's 12 minutes old. It's telling you the truth: this is where the ship was 12 minutes ago, and we haven't heard a peep since.

Pulling positions, properly

Let's actually do it. The VesselAPI Python SDK is installed with pip install vessel-api-python, and the client looks like this:

from vessel_api_python import VesselClient

client = VesselClient(api_key="your_key_here")
Enter fullscreen mode Exit fullscreen mode

There are two distinct concepts in the API, and confusing them is the first mistake everyone makes. A vessel is identity: name, IMO, MMSI, type, flag, tonnage. It doesn't move. A position is a snapshot: latitude, longitude, speed, course, and the timestamp of the AIS burst that produced it. These live behind different endpoints.

# Identity — who is this ship?
vessel = client.vessels.get("9811000").vessel
print(vessel.name, vessel.vessel_type, vessel.imo, vessel.mmsi)

# Position — where was it last seen?
pos = client.vessels.position("9811000").vessel_position
print(pos.latitude, pos.longitude, pos.sog, pos.heading, pos.timestamp)
Enter fullscreen mode Exit fullscreen mode

A few things to notice. The ID is positional and defaults to IMO; pass filter_id_type="mmsi" if you want to look up by MMSI instead. The speed field is sog — speed over ground, in knots — not speed. And timestamp is an ISO-8601 string, which means if you want to compute "how stale is this?", you have to parse it first:

from datetime import datetime, timezone

reported = datetime.fromisoformat(pos.timestamp.replace("Z", "+00:00"))
age = datetime.now(timezone.utc) - reported
print(f"Position is {age.total_seconds():.0f} seconds old")
Enter fullscreen mode Exit fullscreen mode

This little calculation is the most honest line of code you'll write all day. Use it. Surface that age in your UI. A dashboard that displays a 47-minute-old position as if it were live is lying to its users; a dashboard that says "last seen 47 minutes ago" is telling them something true and useful.

Map of Rotterdam with vessel positions on a developer screen

Tracking many ships at once

Single-vessel polling is fine for, say, tracking your one chartered tanker. But most real applications care about an area — a port approach, a strait, a fishing ground. For that, the API exposes location queries:

# Everything currently in a rectangle around Rotterdam
box = client.location.vessels_bounding_box(
    lat_min=51.85, lat_max=52.05,
    lon_min=3.80,  lon_max=4.55,
)

for v in box.vessels or []:
    print(v.vessel_name, v.mmsi, v.latitude, v.longitude, v.timestamp)

# Or everything within 10 km of a point
near = client.location.vessels_radius(
    latitude=51.92, longitude=4.48, radius=10000,
)
Enter fullscreen mode Exit fullscreen mode

These return the latest known position for each vessel in the area. Each row has its own timestamp, and those timestamps will not be the same. The tug in the inner harbour might be 4 seconds old; the cargo ship anchored offshore might be 6 minutes old. That's not a bug — it's the protocol working as designed.

How often should you actually poll?

People reach for WebSockets and "live" streams because polling feels primitive. But for AIS data, the rhythm of your polling should match the rhythm of the underlying transmissions, not exceed it. There's no benefit to polling a moored ship every second when it only transmits every three minutes — you'll just see the same row, over and over, while burning your rate limit.

A reasonable heuristic:

  • Harbour and approach traffic (lots of movement, terrestrial coverage): every 10 to 30 seconds.
  • Coastal traffic at cruising speed: every minute or two.
  • Open-ocean voyages (satellite-dependent): every 5 to 15 minutes is plenty.

If a position's timestamp hasn't advanced since your last poll, nothing new has happened. The vessel hasn't moved into a new reporting state, or no receiver has heard from it since. Either way, polling harder won't help.

The thing under the thing

So: real-time vessel tracking is not a continuous video stream of the oceans. It is a sparse, opportunistic, physics-bounded sampling of millions of ships, glued together by a global network of antennas and satellites, and exposed to you as the freshest snapshot the system was able to assemble. The word "real-time" is doing some work it can't quite hold up.

But once you see the seams — once you treat that timestamp field as the first-class citizen it is — the whole thing stops feeling like a leaky abstraction and starts feeling like what it is: a remarkable feat of distributed coordination, where a 1990s collision-avoidance radio protocol has been quietly turned into a planetary sensor network.

The ships have been broadcasting all along. Someone just had to listen.

Top comments (0)