One endpoint, one JSON contract,
one header.

If your edge node or gateway can make an HTTPS POST, it can integrate with KINGSHIP Telemetry. This is the live device-ingestion contract our own edge nodes use in production, for firmware engineers, hardware integrators, and third-party gateways.

Device Ingestion API

Documented below. A single HTTPS endpoint that accepts telemetry from an edge node or third-party gateway, authenticated with a per-device token. This is what every KINGSHIP edge node uses in the field today.

Client Data API

Pulling your own tank and delivery data into your fleet system, ERP, or BI tool, documented below. Your API credential is issued by our team at go-live (not a self-serve signup, since it's scoped to your organization's data), then it's standard HTTPS calls with a bearer token from there.

Field probe(s) → Edge node → HTTPS POST → dashboard

1. Provisioning

Every node needs a device token before it can send data. KINGSHIP issues one per node: request credentials for your integration and we'll provision it. The token is bound to a single nodeId; a payload claiming a different node is rejected with 403.

2. Connectivity check

GET /api/v1/ingest/ping        (no auth)
→ { "ok": true, "serverTime": "2026-08-23T10:15:00.000Z" }

Use during bring-up and after SIM failover to confirm the data path before flushing buffered readings.

3. Telemetry

POST /api/v1/ingest
Content-Type: application/json
X-Device-Token: <deviceToken>
{
  "nodeId": "KS-KLA-0001",
  "firmwareVersion": "1.5.0",
  "network": { "activeSlot": 1, "operator": "MTN-UG", "rssiDbm": -71 },
  "readings": [
    {
      "ts": "2026-08-23T10:15:00Z",
      "tanks": [
        {
          "tank": 1,
          "volumeLiters": 8214.5,
          "fuelHeightMm": 1971.5,
          "waterHeightMm": 4.0,
          "temperatureCelsius": 26.4
        }
      ],
      "dispensers": [
        { "dispenser": 1, "totalizerPulses": 894210, "flowRateLpm": 34.2 }
      ],
      "generator": { "running": false, "runtimeHours": 1204.5 }
    }
  ]
}

Response (200):

{ "ok": true, "nodeId": "KS-KLA-0001", "accepted": 1, "rejected": 0, "serverTime": "…" }
FieldRule
nodeId[A-Za-z0-9_-]{3,64}, must match the token's provisioned node
readings1–500 entries per POST
readings[].tsISO-8601 UTC, device-side time. Accepted window: 90 days past → 5 min future
tanks[].tank1-based probe index on the RS-485 bus (1–32)
dispensers[].totalizerPulsesCumulative, never reset: the server derives deltas
network.activeSlot1 = primary SIM, 2 = secondary

Unknown fields are stripped, out-of-range values fail validation with a 400 and per-field messages. A reading with an unparseable or out-of-window timestamp is skipped and counted in rejected: the rest of the batch still lands.

Cadence: one reading every 60 seconds, one readings entry per POST in normal operation.

4. Offline buffering & backfill

When connectivity is down, keep sampling and store readings locally. On reconnect: ping to confirm the path, flush the buffer oldest-first (up to 500 per POST) with each entry carrying its original ts and "buffered": true, and only erase locally after a 200 with a matching accepted count.

Retry rule: treat only HTTP 200 as delivered. On 5xx/timeout, keep the data and retry with exponential backoff. On 400/401/403, don't retry the same payload in a loop: flag a configuration fault.

5. Heartbeat & offline alerts

The 60-second POST is the heartbeat. If the server hears nothing for the configured window (5 minutes by default), the node is flagged offline and a critical alert is raised on the dashboard: reconnection raises the matching online alert automatically.

6. Transport notes

  • HTTPS only in production: standard TLS 1.2+ (OpenSSL via curl / requests / axios, etc.)
  • Keep JSON compact; a single-tank reading is ~250 bytes, ~11 MB/month at 60 s cadence
  • Set Content-Length correctly; chunked encoding is not required
  • On modem/SIM failover, re-run the ping check before resuming POSTs

7. Error reference

StatusMeaningFirmware action
200Accepted: check accepted / rejected countsErase delivered buffer entries
400Validation failed / no valid timestampsLog fault, don't hot-loop retry
401Missing or unknown X-Device-TokenConfig fault: needs re-provisioning
403Token ↔ nodeId mismatchConfig fault
429 / 5xx / timeoutServer or network troubleKeep data, retry with exponential backoff

Everything your install tracks, fetched through one API.

At go-live we issue your organization a JWT credential. Every item below is fetched by calling the matching HTTPS endpoint with that credential in an Authorization: Bearer header: nothing here is emailed, exported by hand, or read off a dashboard screen. The one exception is theft/loss alerts, which are pushed to you by SMS the moment they fire rather than something you poll for.

Live fuel level

GET /tanks/:id

Volume in litres and % filled, refreshed every 60 seconds.

currentVolumeLiters, percentageFilled

Consumption & refuel history

GET /tanks/:id/history?startTs=&endTs=

Full time-series per tank: reconcile fuel bought against fuel burned.

volumeLiters, temperatureCelsius (per reading)

Water contamination

GET /tanks/:id

Water layer height inside the tank: early warning before it reaches the engine.

waterHeightMm

Fuel temperature

GET /tanks/:id

Continuous reading, used to keep volume figures accurate.

temperatureCelsius

Theft & loss alerts

SMS (automatic push)

A drop outside normal engine draw is detected automatically and pushed straight to your phone, not something you poll for.

no endpoint: delivered, not fetched

Sensor & connectivity health

GET /v1/ingest/nodes

Signal strength and last-seen status for every node.

status, lastSeen, network

Authentication

Every request carries the JWT credential we issue at go-live:

Authorization: Bearer <your-credential>

Access is scoped to your organization's own stations and tanks: a request for a tank outside your organization is rejected, not just filtered.

Live tank reading

GET /tanks/:id
Authorization: Bearer <your-credential>
{
  "id": "tank-ferry-01",
  "stationId": "station-kacyber-01",
  "name": "Main Fuel Tank",
  "fuelType": "AGO",
  "capacityLiters": 4000,
  "currentVolumeLiters": 2814.5,
  "percentageFilled": 70.4,
  "temperatureCelsius": 26.4,
  "fuelHeightMm": 1971.5,
  "waterHeightMm": 4.0,
  "sensorStatus": "online",
  "lastReading": "2026-08-23T10:15:00.000Z"
}

Consumption & refuel history

GET /tanks/:id/history?startTs=1755878400000&endTs=1755964800000
Authorization: Bearer <your-credential>
[
  {
    "tankId": "tank-ferry-01",
    "timestamp": "2026-08-23T09:00:00.000Z",
    "volumeLiters": 2940.0,
    "percentageFilled": 73.5,
    "temperatureCelsius": 26.1,
    "fuelHeightMm": 2010.2,
    "waterHeightMm": 3.5
  },
  { "…": "one entry per hour across the requested window" }
]

Reconcile fuel bought against fuel burned by comparing readings around each delivery: 1-hour buckets by default.

Sensor & connectivity health

GET /v1/ingest/nodes
Authorization: Bearer <your-credential>
[
  {
    "nodeId": "KS-KACYBER-0001",
    "stationId": "station-kacyber-01",
    "status": "online",
    "lastSeen": "2026-08-23T10:15:00.000Z",
    "firmwareVersion": "1.5.0",
    "network": { "activeSlot": 1, "operator": "MTN-UG", "rssiDbm": -71 },
    "totalReadings": 48210,
    "bufferedReadings": 0
  }
]

status: "offline" means the node itself has gone quiet (dead battery, out of signal range, powered down): the API keeps returning the last confirmed reading elsewhere, clearly timestamped, rather than going blank.

Theft & loss alerts

Not a GET endpoint: a fuel drop well outside normal engine draw is detected automatically and sent straight to your phone by SMS, the same channel as every other alert. There's nothing to poll for this one; it's pushed the moment it fires.

Integrating a device or a data feed?

Email us the device or use case and we'll provision credentials, or scope a data integration for your ERP or BI system.

WhatsAppBook Demo