Provisioning by key claim
Key claim is the flow for self-provisioning at scale. Instead of registering
each device by hand, you (or your contract manufacturer) burn a shared
provisioning key into a batch of devices. On first boot, each device calls
POST /api/v1/provision with that key, and the platform creates the device and
returns its credentials. No operator is in the loop at claim time.
This is the right flow when devices come off a line and must come online on their own. For one-off devices, manual registration is simpler.
Step 1 — Create a provisioning template / key
Section titled “Step 1 — Create a provisioning template / key”A tenant admin creates a provisioning template (and the key it issues) ahead of time. The template scopes what the key may do:
| Field | Purpose |
|---|---|
| Profile reference | The device profile new devices inherit (tags, config, anchoring policy, telemetry schema, …) |
Allowed hardware_id pattern | Restricts which hardware IDs may claim with this key |
| Expiry | UTC instant after which the key is rejected (no expiry if unset) |
| One-time flag | If set, the key is marked used after a successful claim |
| Credential type | Which credential new devices receive (API key, PSK, or cert) |
| Default groups | Groups every device claimed with this key joins |
The key is bound to one tenant. The provisioned device’s tenant is always derived from the key — never from anything the device sends.
Step 2 — The device claims its identity
Section titled “Step 2 — The device claims its identity”On first boot the device sends its raw provisioning key, a human-readable name, and
its stable hardware_id to POST /api/v1/provision.
POST /api/v1/provision HTTP/1.1Host: api.kronoxdata.comContent-Type: application/json{ "provision_key": "pk_factory_7Qx2mZr8K...redacted...", "device_name": "Cold Store Sensor 014", "hardware_id": "AA:BB:CC:00:11:22"}The same call over curl:
curl -sS -X POST https://api.kronoxdata.com/api/v1/provision \ -H "Content-Type: application/json" \ -d '{ "provision_key": "pk_factory_7Qx2mZr8K...redacted...", "device_name": "Cold Store Sensor 014", "hardware_id": "AA:BB:CC:00:11:22" }'Successful first claim
Section titled “Successful first claim”A brand-new device gets back its platform device_id and a freshly minted
api_key:
{ "device_id": "8f1c0b7e-3d2a-4f56-9a10-2c4e6f8b1d33", "api_key": "sk_live_Zr8K2mQx...redacted..."}The device now drops the provisioning key and authenticates all future requests
with sk_live_…. See credentials for
the auth headers.
Idempotent re-claim
Section titled “Idempotent re-claim”If a device with the same hardware_id already exists in the tenant, the call is
idempotent: it returns the existing device_id and an empty api_key.
{ "device_id": "8f1c0b7e-3d2a-4f56-9a10-2c4e6f8b1d33", "api_key": ""}An empty api_key is not an error — it is the platform saying “I already know this
device; keep using the key you were issued before.” No duplicate device is created,
no second key is minted, and no device.provisioned event is emitted. Devices must
therefore store their first-issued API key durably and not depend on getting a
new one on re-boot.
The full claim flow
Section titled “The full claim flow”sequenceDiagram
autonumber
participant Dev as Device
participant GW as Gateway / device-link
participant Reg as Device registry
participant Auth as Auth service
Dev->>GW: POST /api/v1/provision<br/>{provision_key, device_name, hardware_id}
GW->>GW: SHA-256(provision_key)
GW->>GW: Look up key by hash,<br/>constant-time compare,<br/>check status + expiry
Note over GW: Invalid / disabled / expired →<br/>PermissionDenied (uniform)
GW->>Reg: CreateDevice(tenant_from_key, name, hardware_id)
alt New hardware_id
Reg-->>GW: device_id (created)
GW->>Auth: MintAPIKey(tenant, device_id)
Auth-->>GW: sk_live_… (raw, once)
GW-->>Dev: {device_id, api_key}
GW--)Reg: publish device.provisioned event
else Existing hardware_id (idempotent)
Reg-->>GW: device_id (existing)
GW-->>Dev: {device_id, api_key: ""}
end