Skip to content

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:

FieldPurpose
Profile referenceThe device profile new devices inherit (tags, config, anchoring policy, telemetry schema, …)
Allowed hardware_id patternRestricts which hardware IDs may claim with this key
ExpiryUTC instant after which the key is rejected (no expiry if unset)
One-time flagIf set, the key is marked used after a successful claim
Credential typeWhich credential new devices receive (API key, PSK, or cert)
Default groupsGroups 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.

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.1
Host: api.kronoxdata.com
Content-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:

Terminal window
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"
}'

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.

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.

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