THREVOX PUBLIC API
API reference
The ThreVox public API is the Tier 2/3 surface: everything your systems need to consume fleet telemetry, fault state, predictive-maintenance assessments and OTA rollouts. All traffic enters through one gateway.
https://api.threvox.com
Every request carries a bearer token. The gateway validates it at the edge, strips identity-smuggling headers, attaches a correlation id and applies per-tenant rate limits; the owning service validates the token again and resolves your tenant from it - defense in depth, end to end.
Tenancy. You only ever see your own tenant's data. A resource id that belongs
to another tenant answers 404, never 403, so foreign ids are
indistinguishable from ids that do not exist.
Machine-readable OpenAPI documents are exposed per service at /v3/api-docs
behind the same authentication.
Authentication
Authenticate with OAuth 2.0 client credentials against the ThreVox identity provider. API credentials are issued per tenant when your workspace is provisioned.
curl -s https://id.threvox.com/realms/threvox/protocol/openid-connect/token \
-d grant_type=client_credentials \
-d client_id="$THREVOX_CLIENT_ID" \
-d client_secret="$THREVOX_CLIENT_SECRET"
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 300
}
Send the token on every request:
curl -s https://api.threvox.com/api/v1/fleet \
-H "Authorization: Bearer $TOKEN"
Tokens are short-lived - refresh with a new client-credentials grant before
expires_in elapses. Tokens carry aud: threvox-platform and your
tenant claim; the tenant in the token scopes every query, topic and storage key. There is
no way to select a tenant per request, by design.
Conventions
Versioning
Routes are versioned (/api/v1, /api/aemp/v1) and evolve
additively: new fields are optional, existing fields are never repurposed. A breaking
change ships as a new /api/vN route. Tolerate unknown response fields, and
degrade gracefully on unknown enum values.
Errors
Errors are RFC 7807 problem details with content type
application/problem+json. No stack traces, no internal identifiers.
{
"type": "https://developers.threvox.com/errors/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Tenant publish budget exhausted; retry after the window resets.",
"instance": "/api/v1/rollouts"
}
Rate limits
Budgets are per tenant and apply to the abuse-prone groups (publishing, rollout
creation, authentication). Rejections are 429 problem details.
Idempotency
Publish and rollout-creation endpoints honor an Idempotency-Key header.
Retrying with the same key returns the original result instead of repeating the side
effect. Keys should be UUIDs, generated per logical operation.
Correlation
Send X-Correlation-Id (matching [A-Za-z0-9._-]{1,64}) to
stitch platform traces into your own; the platform echoes it on the response and mints one
when absent.
Timestamps
All timestamps are ISO-8601 in UTC, for example 2026-07-16T13:58:41Z.
Fleet & telemetry DASHBOARDSERVICE
Read models over everything your devices report: per-device summaries, the latest value of every decoded signal, and fleet-wide projections.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/fleet | Device summaries: vin, last seen, active fault count, worst severity |
| GET | /api/v1/fleet/{deviceId}/telemetry | Latest value per SPN, name and unit enriched |
| GET | /api/v1/fleet/{deviceId}/faults | DM1 fault state per source address |
| GET | /api/v1/fleet/{deviceId}/maintenance | Predictive-maintenance assessment for one device |
| GET | /api/v1/fleet/maintenance | One assessment per assessed device |
| GET | /api/v1/fleet/rollouts | Rollout state projections |
/api/v1/fleetcurl -s https://api.threvox.com/api/v1/fleet \
-H "Authorization: Bearer $TOKEN"
[
{
"deviceId": "TVX-0142",
"vin": "3HAMMAAR1PL512345",
"lastSeenAt": "2026-07-16T13:58:41Z",
"activeFaultCount": 1,
"worstSeverity": "WARNING"
},
{
"deviceId": "TVX-0157",
"vin": "3HAMMAAR7PL518220",
"lastSeenAt": "2026-07-16T13:58:12Z",
"activeFaultCount": 0,
"worstSeverity": "NONE"
}
]
/api/v1/fleet/{deviceId}/telemetryThe latest decoded value for every signal the device reports, enriched with the catalog name and unit.
{
"deviceId": "TVX-0142",
"signals": [
{ "spn": 190, "name": "Engine Speed", "value": 1287.5, "unit": "rpm", "sampledAt": "2026-07-16T13:58:40Z" },
{ "spn": 110, "name": "Engine Coolant Temperature", "value": 87, "unit": "deg C", "sampledAt": "2026-07-16T13:58:40Z" },
{ "spn": 96, "name": "Fuel Level", "value": 62.4, "unit": "%", "sampledAt": "2026-07-16T13:58:38Z" }
]
}
Faults DASHBOARDSERVICE
/api/v1/fleet/{deviceId}/faultsActive DM1 fault state per source address, with lamp status and occurrence counts.
Lamp values follow J1939 semantics: STOP, WARNING,
PROTECT, MIL.
| Query parameter | Type | Meaning |
|---|---|---|
includeCleared | boolean | Also return faults that are no longer active (DM2-style history). Default false. |
{
"deviceId": "TVX-0142",
"faults": [
{
"sourceAddress": 0,
"spn": 100,
"fmi": 1,
"occurrenceCount": 3,
"lamp": "WARNING",
"description": "Engine Oil Pressure - data valid but below normal, most severe",
"active": true,
"firstSeenAt": "2026-07-16T09:14:02Z",
"lastSeenAt": "2026-07-16T13:58:41Z"
}
]
}
Predictive maintenance DASHBOARDSERVICE
/api/v1/fleet/{deviceId}/maintenanceThe latest health assessment for a device, computed per tenant from fault history and
signal trends. Answers 404 until the device has been assessed at least once.
GET /api/v1/fleet/maintenance returns one assessment per assessed device.
{
"deviceId": "TVX-0142",
"healthScore": 0.82,
"riskBand": "WATCH",
"topContributors": [
{ "spn": 100, "name": "Engine Oil Pressure", "weight": 0.41 }
],
"modelVersion": "tenant-2026.06",
"assessedAt": "2026-07-16T05:00:00Z"
}
Rollouts & OTA FWUPDATESERVICE
Publish UI layouts and firmware images, then deliver them with staged rollouts. All artifacts are signed; devices verify the ECDSA P-256 signature and SHA-256 digest before a slot is ever committed, and roll back to the last known-good on any failure.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/ui/publish | Publish a UI layout (SUI payload; Idempotency-Key honored) |
| GET | /api/ui/versions | Published UI versions for the tenant |
| GET | /api/ui/versions/{id}/usb-package | A published layout as a signed USB update file (.uf2) |
| POST | /api/fw/publish | Publish a firmware image (octet-stream; headers below) |
| GET | /api/fw/versions | Published firmware versions |
| GET | /api/fw/versions/{id}/usb-package | A published firmware image as a signed USB update file; ?restart=true installs as soon as it verifies |
| GET | /api/fw/signing-key | ECDSA P-256 verification key devices pin |
| POST | /api/v1/rollouts | Create a staged rollout |
| POST | /api/v1/rollouts/{id}/expand | Also: /pause, /resume, /rollback |
| GET | /api/v1/rollouts | Rollout list; /{id} for detail with per-device delivery |
/api/fw/publishBody is the firmware image as application/octet-stream. Metadata travels in
headers: X-Version-Name (required), X-Hw-Compat (optional
hardware compatibility tag), X-Mandatory (optional boolean).
/api/fw/versions/{id}/usb-packageFor clusters with no connectivity. Answers application/x-uf2 with a
Content-Disposition file name; copy the file onto the cluster's
TVX UPDATE USB drive. The package is signed for your tenant and the version's
hardware tag, and the cluster verifies it exactly as it verifies an over-the-air update.
Without restart the image installs at the next power-up. A version outside your
tenant answers 404; a stored artifact that no longer matches its published digest or
signature answers 409. The layout form, /api/ui/versions/{id}/usb-package, takes
no parameters.
/api/v1/rollouts| Field | Type | Meaning |
|---|---|---|
artifactKind | string | UI or FIRMWARE |
versionName | string | A published version to deliver |
deviceIds | string[] | Target devices, all within your tenant |
canaryCount | integer | Devices served first; expand promotes the rest |
curl -s -X POST https://api.threvox.com/api/v1/rollouts \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 4b8ee1a6-3d0f-4a2e-9d5c-6f2a71c0f9d3" \
-H "Content-Type: application/json" \
-d '{
"artifactKind": "FIRMWARE",
"versionName": "fw-2026.07.1",
"deviceIds": ["TVX-0142", "TVX-0143", "TVX-0157"],
"canaryCount": 1
}'
Rollouts move through expand, pause, resume and
rollback as sub-resources. A device acknowledging DOWNLOADED
means verified-and-armed; the swap happens on the device's next safe boot.
AEMP export DASHBOARDSERVICE
/api/aemp/v1/Fleet/{page}An AEMP 2.0 / ISO 15143-3 XML snapshot of the fleet, for mixed-fleet tools that already speak the standard. Paged; page numbers start at 1.
<Fleet xmlns="http://standards.iso.org/iso/15143/-3">
<Equipment>
<EquipmentHeader>
<OEMName>THREVOX</OEMName>
<Model>TVX-C43C</Model>
<EquipmentID>TVX-0142</EquipmentID>
<SerialNumber>3HAMMAAR1PL512345</SerialNumber>
</EquipmentHeader>
<CumulativeOperatingHours>
<Hour>1287.4</Hour>
<DateTime>2026-07-16T13:58:41Z</DateTime>
</CumulativeOperatingHours>
</Equipment>
</Fleet>
Billing BILLINGSERVICE
/api/v1/billing/entitlementsYour tenant's tier, subscription status and feature list. Answers 404
without an active subscription.
{
"tier": "TIER_3",
"status": "ACTIVE",
"features": ["TELEMETRY_API", "LIVE_STREAMS", "OTA_ROLLOUTS", "DASHBOARD", "PREDICTIVE_MAINTENANCE"],
"currentPeriodEnd": "2026-08-01T00:00:00Z"
}
POST /webhooks/stripe exists on this service but is signature-authenticated
for the payment provider - it is not a client endpoint.
User administration USERMANAGEMENTSERVICE
Tenant admins (owner or fleet-admin role) manage their own
users. Everything is scoped to the caller's tenant; a user id from another tenant answers
404.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/users | Users in the tenant (roles, enabled, self flag) |
| GET | /api/v1/users/assignable-roles | Roles the caller may assign |
| POST | /api/v1/users | Invite a user; returns a one-time temporary password |
| PUT | /api/v1/users/{id}/roles | Replace a user's roles |
| POST | /api/v1/users/{id}/activate | Also: /deactivate |
| POST | /api/v1/users/{id}/reset-password | Issue a new one-time temporary password |
curl -s -X POST https://api.threvox.com/api/v1/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "operator@customer.example", "roles": ["operator"] }'
{
"id": "8c2f1e0a-77b4-4f4e-9b6d-2f9d3a51c007",
"email": "operator@customer.example",
"roles": ["operator"],
"enabled": true,
"temporaryPassword": "returned-once-then-never-again"
}
Policy is enforced server-side: an admin can never grant a role above their own level,
act on a higher-privileged user, change their own roles, deactivate themselves, or remove
the tenant's last active owner (409). The internal platform-admin
role is never assignable (400).
Reference catalog J1939 CATALOG
/api/catalog/pgnsThe J1939 PGN/SPN reference rows the platform decodes with - useful for labeling and unit handling on your side of the API.
{
"pgn": 61444,
"label": "EEC1",
"name": "Electronic Engine Controller 1",
"spns": [
{ "spn": 190, "name": "Engine Speed", "unit": "rpm", "resolution": 0.125, "offset": 0, "lengthBits": 16 }
]
}
Live streams (SSE) DASHBOARDSERVICE
/api/v1/fleet/stream/telemetry/api/v1/fleet/stream/faultsServer-sent events, tenant-scoped, kept alive with heartbeats. Reconnect with backoff; after a reconnect, fetch the REST read model once to resynchronize, then follow the stream.
curl -N https://api.threvox.com/api/v1/fleet/stream/telemetry \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: text/event-stream"
event: telemetry
data: {"deviceId":"TVX-0142","spn":190,"name":"Engine Speed","value":1287.5,"unit":"rpm","at":"2026-07-16T13:58:40Z"}
event: telemetry
data: {"deviceId":"TVX-0142","spn":110,"name":"Engine Coolant Temperature","value":87,"unit":"deg C","at":"2026-07-16T13:58:41Z"}
event: heartbeat
data: {"at":"2026-07-16T13:58:55Z"}
MQTT device link DEVICE PLANE
Devices talk to the platform over MQTT with QoS 1 and per-device mutual TLS. The topic namespace is per-tenant, and a device's credentials only permit its own topics - the namespace is a hard isolation boundary, not a convention.
| Topic | Direction | Payload |
|---|---|---|
tenants/{tenantId}/devices/{deviceId}/telemetry | device to cloud | Telemetry frames, sequence-numbered |
tenants/{tenantId}/devices/{deviceId}/faults | device to cloud | DM1/DM2 fault transitions |
tenants/{tenantId}/devices/{deviceId}/ota | cloud to device | Signed artifact notifications (S3 URL + digest) |
Devices buffer locally while offline and replay with sequence numbers on reconnect; the platform deduplicates, so at-least-once delivery is safe. A Last Will and Testament marks dropout. Device credentials are provisioned with the unit and revoked on decommission - the device plane is not a general-purpose publish API.
Schemas & fixtures
Message schemas (device link, Kafka processing topics, the SUI layout binary and the BLE companion protocol) are versioned contracts, tested on both producer and consumer sides in CI. Payloads carry a schema version; consumers must tolerate unknown fields and reject missing required ones.
Per-service OpenAPI documents live at /v3/api-docs behind the same
authentication. If you are building a deep integration and want the fixture bundle,
write to us.