MainSDK
Search⌘K
Docs

Connected devices

The optional plumbing for any fleet of devices or agents your app manages, from enrolment and identity to telemetry, policy, commands and health, and what MainSDK leaves to you.

Updated 2026-10-28

Say you sell a security agent that runs on your customers' laptops, a sensor that sits in their warehouses, or an AI agent that runs on their servers. Your app is the control plane: it has to know which machines belong to which customer, hear from them, change how they behave and ask them to do things, safely. Every fleet needs the same plumbing, whatever it does. MainSDK gives you that plumbing and nothing else.

Optional, opt-in, no cost when unused

You turn it on in two places: a device section in main.yaml and an import of mainsdk/device in your server code. Until you do both, nothing loads, nothing listens, no table is created and nothing is counted. mainsdk doesn't re-export it, so your bundle, start-up time and tests are the same as before. mainsdk check speaks up only when you do one half without the other.

The program on the device uses mainsdk/device/client. It needs no main.yaml and no other part of MainSDK, so you can drop it into any TypeScript agent. The Rust, Python and Go SDKs will implement the same contract in their own languages.

What's in

PartWhat it does for you
Enrolment and identityA device joins one of your tenants with a one-time token, a code a signed-in person approves, its cloud workload's OIDC token, or attestation evidence. It makes its own key and never shares it. You can rotate keys, renew credentials and revoke a device in one call
AttestationAsk for evidence before you trust a device: none, a software self-report, or hardware and confidential-computing evidence through the TAPS driver. The SDK says plainly what each level proves
Telemetry transportclient.send(stream, record) signs, batches, buffers through outages and delivers each record once, in order, to your handler inside the right tenant, with size limits, backpressure and redaction on the device
Policy syncPublish signed, versioned settings to a tenant or group, roll them out in stages, roll back on failures, see which version each device runs; playbooks can tighten them for a while, never loosen
CommandsAsk a device to run a handler it registered, with an expiry, people's approval for risky ones, a signed receipt and an undo; an agent can ask but never approve
HealthA signed heartbeat with versions, licence state, clock, policy and pressure; online, late, silent and revoked; no false alarms when your own app was the one that was down
TestingSimulated devices on a fake clock in @mainsdk/testing, so the whole flow runs in a unit test
ts
// your server
import { device } from "mainsdk/device";

const { token } = await device.enrolments.create({ group: "warehouse-3", uses: 25 });
device.telemetry.on("sensor.reading", async (records) => { await saveReadings(records); });
ts
// the program on the device
import { deviceClient, fileKeyStore } from "mainsdk/device/client";

const client = deviceClient({ url: "https://app.example.com/mainsdk/device", keys: fileKeyStore("/var/lib/acme") });
await client.enrol({ token: process.env.ENROL_TOKEN! });
await client.start();
client.send("sensor.reading", { celsius: 21.5 });

What's out

MainSDK carries no security content and isn't bent for one kind of product. It ships no detection rules, no threat signatures, no event formats for processes, files or network flows on a device, no malware handling, no scanners and no sensors. Endpoint detection and response, antivirus, IoT monitoring and AI agent supervision are products you build on top: you decide what your device measures, what your policy means and what your commands do, and you describe each with s.

It also doesn't deliver code. There is no remote shell, no "run this script" command and no software update through commands; a device runs only the handlers it registered, and mainsdk check flags a handler that would run its input as code. Updates to your device program go through signed delivery (TUF), like everything else MainSDK delivers.

Identity on TAPS

Every device is a did:key, the same kind of identity TAPS, HubXero's open trust protocol, uses. The built-in local driver signs with Ed25519 or P-256 and needs nothing else. When you need more, the taps driver in @mainsdk/device-taps issues TAPS device and agent credentials that any TAPS verifier can check, publishes revocation as a status list, appraises TPM, Android, Apple and confidential-computing evidence, and can require a chain of delegation from your customer's own people before a device runs a risky command. Switching drivers changes one line of main.yaml.

What an identity proves

It provesIt doesn't prove
The sender holds the key that enrolled, with a proof you accepted, into this tenantThat the device is healthy, unmodified or free of malware
The message wasn't changed and isn't a replayThat what the device reports is true
With hardware evidence: the key lives in hardware on a device that booted cleanlyThat a software key wasn't copied to another machine
With environment evidence: the program matches a build you approvedAnything about the person using the device

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet