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": "…" }| Field | Rule |
|---|---|
| nodeId | [A-Za-z0-9_-]{3,64}, must match the token's provisioned node |
| readings | 1–500 entries per POST |
| readings[].ts | ISO-8601 UTC, device-side time. Accepted window: 90 days past → 5 min future |
| tanks[].tank | 1-based probe index on the RS-485 bus (1–32) |
| dispensers[].totalizerPulses | Cumulative, never reset: the server derives deltas |
| network.activeSlot | 1 = 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
| Status | Meaning | Firmware action |
|---|---|---|
| 200 | Accepted: check accepted / rejected counts | Erase delivered buffer entries |
| 400 | Validation failed / no valid timestamps | Log fault, don't hot-loop retry |
| 401 | Missing or unknown X-Device-Token | Config fault: needs re-provisioning |
| 403 | Token ↔ nodeId mismatch | Config fault |
| 429 / 5xx / timeout | Server or network trouble | Keep 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/:idVolume 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/:idWater layer height inside the tank: early warning before it reaches the engine.
waterHeightMm
Fuel temperature
GET /tanks/:idContinuous 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/nodesSignal 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.