Developer Documentation API v1

Build with Dhuveli

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.

REST · JSON Bearer-token auth 60 req / min Embeddable map

Authentication

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
Keep tokens secret — call the API from your server, not from browser JavaScript, so the token isn't exposed. Tokens are read-only and can be revoked at any time.

Base URL

https://dhuveli.com/api/v1

Rate limit

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.

Endpoints

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
    }
  ]
}

Share links

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 fieldDescription
vessel_idsArray of vessel ids to show on the link (must belong to you and have a tracker).
expires_atRequired 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.

Embedding the live map

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>
The 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.

Live data feed (optional)

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 reference

FieldMeaning
idStable vessel identifier (use this to track a vessel across calls).
state.keymoving · idle · stopped · unknown
position.speedSpeed over ground, in knots and kmh.
position.courseHeading in degrees (0–359) and a 16-point cardinal label.
position.powerBattery/supply voltage. on is true above 6 V; hasData false if the device reports none.
fixTimeWhen the device recorded the fix (ISO 8601, UTC).

Errors

StatusMeaning
401Missing or invalid token.
403Token lacks the required ability (e.g. manage-links).
404Vessel or link not found in your company.
422Invalid query parameters.
429Rate limit exceeded.