Developers / DocsSearch docs ⌘K
Documentation

Docs

Rootd is a multi-tenant battery-intelligence layer. Send it BMS telemetry from any vendor; read back a normalized digital twin, forecasts, and recommendations. This is the place to start.

What Rootd is

Rootd sits on top of the swap networks other people run. It ingests raw Battery Management System (BMS) telemetry, normalizes it into a vendor-neutral digital twin, and turns that into operational decisions for three readers: operators, riders, and financiers.

Rootd owns no hardware, operates no fleets, and does not replace your BMS or swap app. Everything below is read APIs and projections your own systems consume.

Vendor-neutral. Rootd never favors a battery brand. The twin is derived from whatever each station already runs.

Core concepts

The digital twin

A twin is intelligence derived from telemetry, not a second device and nothing you install. It exists because the data does, and carries state-of-health, connectivity, cycles, and a derived estimate of charge.

Tenancy

Every object belongs to a tenant. Every query is tenant-scoped at the boundary: there is no supported path that reads across tenants. You pass your tenant context on every call.

Connectivity states

Connectivity is modeled as online, stale, or offline, never an error. When a feed drops, the twin holds last-known state and surfaces its freshness.

Estimated state-of-charge

SoC is computed on read from state-of-health and the latest reading, never stored as an independent peer of SoH. The twin list returns raw socRaw alongside soh so you derive the estimate yourself; the rider view returns an already-computed soc.

Don't treat SoC as stored truth. It is an estimate that moves with the latest reading. Use it as guidance, not a ledger value.

Quickstart

Three steps get you from a raw device to a usable twin. The base URL is https://api.rootd.cc.

1. Get an ingest token. Authenticate as an operator and mint a token that carries the ingest role, scoped to your tenant. (Full auth flow in the ingest reference.)

mint an ingest token
01# Mint an ingest token from your operator session (or Settings → API tokens).
02curl -X POST https://api.rootd.cc/v1/settings/tokens \
03 -H "Authorization: Bearer $SESSION_TOKEN" \
04 -H "Content-Type: application/json" \
05 -d '{"scopes":[],"ttlHours":720}'
06# → { "token": "<INGEST_TOKEN>" }

2. Send a reading. POST a vendor-neutral generic_json body: voltage in volts, current in amps, temperature in °C. Rootd normalizes timing and derives the twin.

ingest a reading
01# Tenant comes from the token, never the body.
02curl -X POST https://api.rootd.cc/v1/ingest/generic_json \
03 -H "Authorization: Bearer $INGEST_TOKEN" \
04 -H "Content-Type: application/json" \
05 -d '{"device":"BIKE-001","timestamp":"2026-06-24T08:14:02Z",
06 "v":53.12,"i":-8.1,"temp":31,"soc":0.64}'
07# → 202 { "acceptedReadings": 1, "acceptedLocations": 0 }
Already speak a device protocol? Send JT/T 808, GT06, Teltonika, GB/T 32960, Modbus, CAN, JBD, Daly, a lithium BMS board, or OCPP in its native wire format to the same endpoint, no translation layer. See the protocol reference.

3. Read the twin. Once normalized, the pack is a twin you can query, tenant-scoped by your token, with derived health and the latest reading.

read the twin
01GET /v1/twins // tenant-scoped by your token
02{ "twins": [ {
03 "batteryId": "BIKE-001",
04 "connectivity": "online", // modeled state
05 "soh": 90.4, // derived state-of-health
06 "latest": { "voltage": 53.12, "socRaw": 0.64, "ts": "2026-06-24T08:14:02Z" }
07} ] }

Send commands back

Rootd is bidirectional. The same endpoint that accepts telemetry can deliver platform→device commands — remote reboot, unlock, an OCPP RemoteStartTransaction, a signed firmware update — on whichever transport the device speaks (HTTP, persistent TCP, MQTT broker, OCPP WebSocket). The operator side stays one shape regardless of the wire.

1. Enqueue. An operator POSTs a normalized kind + payload; the per-protocol encoder dry-runs at request time so an unsupported command 422s before it ever queues.

enqueue a command
01# Operator enqueues a reboot for one device.
02curl -X POST https://api.rootd.cc/v1/devices/BIKE-001/commands \
03 -H "Authorization: Bearer $OPERATOR_TOKEN" \
04 -H "Content-Type: application/json" \
05 -d '{"protocol":"jt808","kind":"reboot","expires_in_s":300}'
06# → 201 { "id": "<CMD>", "status": "pending" }

2. Deliver. The next ingest response from the same device carries the encoded wire bytes plus a delivery token. Devices on a persistent socket (JT808 TCP, MQTT, OCPP-WS) get the bytes pushed as soon as they connect.

device receives the command on next ingest
01# Device POSTs telemetry; the 202 carries any pending commands.
02POST /v1/ingest/jt808
03→ 202
04{ "acceptedReadings": 1,
05 "commands": [ {
06 "id": "<CMD>",
07 "kind": "reboot",
08 "encoded_base64": "fghQ...AHs=", // wire bytes
09 "delivery_token": "<TOKEN>" // echo on ack
10 } ] }

3. Ack. The device PATCHes the delivery token back; replays of a stale token 404. A command for an offline device sits in pending — connectivity loss is a first-class state, not an error — until it expires.

Locked down per device. Per-device ACLs let you say "operator role can reboot any pack, but only this specific user can push a firmware_update." Devices with no ACL rows stay unrestricted. Firmware artifacts are sha256-+ Ed25519-verified at finalize; devices pull chunks and re-verify before flashing.

Where to next

  • Protocols: send data from any OEM in its native format (telematics, BMS/bus, charging) via one endpoint.
  • Ingest reference: transports, payload schema, and freshness rules.
  • Twin API: read twins, list packs, forecasts, and rider projections.
  • Bidirectional commands: enqueue reboots, unlocks, OCPP control, and signed firmware updates across every supported protocol.
  • Security: tenant isolation, per-device ACLs, and firmware signing.
  • Glossary: canonical definitions of every domain term.
Need access? Talk to us to provision a tenant and a sandbox feed.