Developer Documentation API v1
Integrate live vessel tracking into your own apps — a simple HTTP API for reading fleet data and managing share links, plus an embeddable live map.
The API uses bearer tokens. Generate one in Dhuveli under Settings → API Access. The token is shown once — copy it then. Every request is scoped to your company; you only ever see your own vessels.
Send the token in the Authorization header on every request:
Authorization: Bearer YOUR_TOKEN_HERE
Accept: application/json
https://dhuveli.com/api/v1
60 requests per minute per token. Over the limit returns 429 Too Many Requests. For a live map, poll no faster than every ~10 seconds.
GET/vessels
All your active vessels, each with its latest position.
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
https://dhuveli.com/api/v1/vessels
Response
{
"data": [
{
"id": 12,
"name": "Ocean Queen",
"type": "Dhoani",
"capacity": 40,
"state": { "key": "moving", "label": "Moving" },
"position": {
"lat": 3.947643,
"lng": 73.486898,
"fixTime": "2026-06-17T16:34:18+00:00",
"speed": { "knots": 9.2, "kmh": 17.0 },
"course": { "degrees": 163, "cardinal": "SSE" },
"power": { "volts": 12.2, "on": true, "hasData": true }
}
}
]
}
position is null and state.key is "unknown" when a vessel has no recorded fixes.
GET/vessels/{id}/track
The most recent activity for one vessel, newest first: up to the last 30 positions and the last 10 events (each with its time). No query parameters.
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
"https://dhuveli.com/api/v1/vessels/12/track"
Response
{
"vessel": { "id": 12, "name": "Ocean Queen" },
"positions": [
{
"lat": 3.947643,
"lng": 73.486898,
"speed": { "knots": 9.2, "kmh": 17.0 },
"course": { "degrees": 163, "cardinal": "SSE" },
"fixTime": "2026-06-17T16:34:18+00:00"
}
],
"events": [
{
"type": "deviceMoving",
"time": "2026-06-17T16:30:02+00:00",
"attributes": null
}
]
}
Control which vessels appear on a public tracking link (/l/<code>).
Reading links works with any token; creating a temporary link or
changing a link's vessels requires a token created with the
“Allow managing share links” option.
GET/links
Your share links and the vessels currently on each.
{
"data": [
{
"code": "sunset-ferry",
"url": "https://dhuveli.com/l/sunset-ferry",
"active": true,
"expires_at": null,
"vessels": [ { "id": 12, "name": "Ocean Queen" } ]
}
]
}
POST/links
Create a temporary share link. An expiry is required — links
created through the API always expire. Requires the manage-links ability.
| Body field | Description |
|---|---|
vessel_ids | Array of vessel ids to show on the link (must belong to you and have a tracker). |
expires_at | Required ISO 8601 datetime in the future — when the link stops working. |
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"vessel_ids": [12, 15], "expires_at": "2026-06-18T17:00:00Z"}' \
https://dhuveli.com/api/v1/links
Response (201 Created)
{
"code": "a1b2c3d4",
"url": "https://dhuveli.com/l/a1b2c3d4",
"active": true,
"expires_at": "2026-06-18T17:00:00+00:00",
"vessels": [
{ "id": 12, "name": "Ocean Queen" },
{ "id": 15, "name": "Reef Runner" }
]
}
PUT/links/{code}/vessels
Replace which vessels are shown on a link. {code} is the link's slug (the part after /l/). Requires the manage-links ability.
curl -X PUT \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"vessel_ids": [12, 15]}' \
https://dhuveli.com/api/v1/links/sunset-ferry/vessels
Response
{
"code": "sunset-ferry",
"url": "https://dhuveli.com/l/sunset-ferry",
"vessels": [
{ "id": 12, "name": "Ocean Queen" },
{ "id": 15, "name": "Reef Runner" }
]
}
Create the link itself (and its {code}) once in the dashboard under Shared Links; this endpoint then drives which vessels it shows.
You can embed a share link's live map directly in your own site with an
<iframe>. For security, a link only loads in a frame on
domains you've whitelisted under Settings → API Access → iFrame
embedding — every other site is refused by the browser.
<iframe
src="https://dhuveli.com/l/sunset-ferry"
width="100%" height="500" style="border:0"
allowfullscreen></iframe>
src must be https, and its domain must be on the
link's company whitelist. A link with no vessels, or an expired one, shows a
friendly status page inside the frame rather than failing.
Add ?t=iframe to the URL to turn on a postMessage feed:
clicking a vessel no longer opens the in-map popup — instead the vessel's details
are sent to your page, and the selected vessel keeps streaming updates on every refresh.
This lets you render the data in your own UI.
<iframe src="https://dhuveli.com/l/sunset-ferry?t=iframe" …></iframe>
<script>
window.addEventListener("message", (event) => {
// 1. Only trust messages from Dhuveli
if (event.origin !== "https://dhuveli.com") return;
if (event.data?.source !== "dhuveli") return;
if (event.data.type === "vessel.selected") {
// user tapped a vessel
showVessel(event.data.vessel);
} else if (event.data.type === "vessel.update") {
// same vessel, fresh data (~every 10s)
updateVessel(event.data.vessel);
}
});
</script>
Message shape (both vessel.selected and vessel.update):
{
"source": "dhuveli",
"type": "vessel.selected",
"version": 1,
"timestamp": "2026-06-17T11:34:20.512Z",
"vessel": {
"name": "Ocean Queen",
"descriptor": "Dhoani",
"status": "online",
"state": { "key": "moving", "label": "Moving" },
"position": { "lat": 3.947643, "lng": 73.486898, "fixTime": "…", "fixAgeLabel": "Just now" },
"speed": { "knots": 9.2, "kmh": 17.0 },
"course": { "degrees": 163, "cardinal": "SSE" },
"power": { "volts": 12.2, "on": true, "hasData": true },
"track": { "distanceKm": 1.36, "fixCount": 30 }
}
}
The vessel object uses the same fields as the API responses below
(no internal device id). Always verify event.origin before trusting a message.
| Field | Meaning |
|---|---|
id | Stable vessel identifier (use this to track a vessel across calls). |
state.key | moving · idle · stopped · unknown |
position.speed | Speed over ground, in knots and kmh. |
position.course | Heading in degrees (0–359) and a 16-point cardinal label. |
position.power | Battery/supply voltage. on is true above 6 V; hasData false if the device reports none. |
fixTime | When the device recorded the fix (ISO 8601, UTC). |
| Status | Meaning |
|---|---|
401 | Missing or invalid token. |
403 | Token lacks the required ability (e.g. manage-links). |
404 | Vessel or link not found in your company. |
422 | Invalid query parameters. |
429 | Rate limit exceeded. |