Skip to content

CLI

iotpulse is a scriptable command-line client for the IoT Pulse public API. It is a thin wrapper: every command maps to one public API endpoint, authenticates with a scoped API key, and can emit raw JSON for piping into jq or a CI pipeline. It holds no logic of its own — tenant and scope are resolved server-side from your key.

Install the CLI globally from npm:

Terminal window
npm install -g @iotpulse/cli

This puts the iotpulse binary on your PATH. Verify it:

Terminal window
iotpulse --version
iotpulse --help

You need Node.js 20 or newer.

To work on the CLI itself, build it from the platform repository instead of the published package:

Terminal window
cd src/cli
npm install
npm run build
npm link

npm link puts your local build of the iotpulse binary on your PATH.

Mint a scoped API key in the admin app under Settings → API Keys (the secret, format iotp_…, is shown once). The CLI reads the key from the first of these it finds:

  1. the --api-key flag,
  2. the IOTPULSE_API_KEY environment variable,
  3. the config file ~/.config/iotpulse/config.json.

Store it once with the config command:

Terminal window
iotpulse config set apiKey iotp_your_key_here
iotpulse config get # shows the URL and a masked key
iotpulse config path # prints the config file location

The key is sent as the X-API-Key header on every request. You never pass a tenant id — the key is bound to a single tenant, and the API resolves it for you.

The base URL defaults to https://api.iotpulse.io. Override it with the --api-url flag, the IOTPULSE_API_URL environment variable, or iotpulse config set apiUrl <url>. The request timeout defaults to 30s (IOTPULSE_API_TIMEOUT_MS).

By default, list commands print a compact table and single-resource commands print indented JSON. Pass the global --json flag to print the API’s exact response body instead — that is the scriptable contract:

Terminal window
iotpulse devices list # table
iotpulse devices list --json | jq '.[].id' # raw JSON, piped to jq

On an API error, the CLI writes a one-line diagnostic to stderr and exits with a non-zero status, so it composes cleanly in scripts and CI.

Each command requires the matching scope on your key (see Scopes).

Terminal window
iotpulse devices list
iotpulse devices get <device-id>
iotpulse devices update <device-id> --data '{"name":"Boiler A"}' # needs devices:write

Device groups, thresholds, shifts (read-only)

Section titled “Device groups, thresholds, shifts (read-only)”
Terminal window
iotpulse groups list
iotpulse groups get <group-id>
iotpulse thresholds list
iotpulse thresholds get <threshold-id>
iotpulse shifts list
iotpulse shifts get <shift-id>

These resources are read-only in the public API v1, so the CLI exposes only list and get for them.

Terminal window
iotpulse anomalies list --device-id <id> --status open --skip 0 --limit 100
iotpulse anomalies get <anomaly-id>
iotpulse anomalies acknowledge <anomaly-id> # needs anomalies:write
Terminal window
iotpulse tickets list
iotpulse tickets get <ticket-id>
iotpulse tickets update <ticket-id> --data '{"status":"resolved"}' # needs tickets:write

Mint an enrollment claim for a gateway and choose the groups it lands in. --group takes a group id or an exact group name. You can repeat it, and you can leave it out for a gateway only admins can see. A name is looked up in your own groups, which needs groups:read.

Terminal window
iotpulse provisioning claims create --name "Dock 3" --group "Dock crew" --expires-in 60 # needs provisioning:write
iotpulse provisioning claims list --status pending # needs provisioning:read
iotpulse provisioning claims revoke <claim-id> # needs provisioning:write

The claim code is printed once, on create. provisioning:write must be granted explicitly: no wildcard or other scope implies it.

iotpulse enroll turns the machine it runs on into a certificate-authenticated device. It is a reference client for self-enrollment. These commands take no API key: the claim code is the credential for enrolling, and the device certificate is the credential for renewing.

Terminal window
IOTPULSE_CLAIM_CODE=PULSE-XXXX-XXXX-XXXX \
iotpulse enroll --hardware-model rpi4 --out /etc/iotpulse # key + CSR, redeem the claim
iotpulse enroll check --dir /etc/iotpulse # MQTT connect over mTLS, client id = deviceId
iotpulse enroll renew --dir /etc/iotpulse # no-op until renewAfterUtc; --force renews now

enroll generates an ECDSA P-256 key locally and never sends it. It writes the key and its credentials to --out:

  • device.key (mode 0600)
  • device.crt (the leaf certificate)
  • chain.pem (the issuing intermediate; cat device.crt chain.pem is the full chain an MQTT client presents)
  • server-ca.pem (verifies the broker’s certificate)
  • enrollment.json (deviceId, tenantId, brokerUrl, topicPrefix, certExpiresAt, renewAfterUtc)

--fingerprint defaults to an id derived from /etc/machine-id (Linux; pass it explicitly on macOS and Windows). On Windows the file mode is not enforced, so restrict the --out directory’s ACL yourself. Re-enrolling into a directory that already holds an enrollment needs --force. The current certificate is then presented as proof of possession.

renew replaces the leaf and its chain together on every renewal. It refuses to store a renewal that did not return a usable chain. A successful renewal revokes the certificate it was made with, so if a renewal times out, don’t assume it failed: retry with --force.

List, issue, revoke and audit device client certificates. The device-certificates:read, device-certificates:issue and device-certificates:revoke scopes must each be granted explicitly; none implies another.

Terminal window
iotpulse certs list --device-id gw-1 --status active # needs device-certificates:read
iotpulse certs issue gw-1 --out ./certs # needs device-certificates:issue
iotpulse certs issue gw-1 --out ./certs --p12 # also writes gw-1.p12 + gw-1.p12.pass
iotpulse certs revoke <certificate-id> # needs device-certificates:revoke
iotpulse certs audit # needs device-certificates:read

certs issue gets the device’s private key exactly once. It writes <device>.key.pem, .crt.pem, .chain.pem and .server-ca-bundle.pem into --out. The server CA bundle lets the device verify the broker (messaging.iotpulse.io:8883). Don’t confuse it with .chain.pem, which is your client certificate’s chain. The files are owner-only (0600), and the directory is created 0700 if it doesn’t exist. On Windows those modes aren’t enforced, and the files inherit the directory’s ACL, so pick a directory only you can read.

The command creates every file, empty, before it issues anything. So if a file already exists, or --out isn’t writable, the command stops before a certificate is minted, and you never lose a key or use up one of the device’s certificate slots. Existing files are never overwritten. If the request fails, the empty files are removed again.

The key is never printed. That includes --json, which returns the bundle without the key, PKCS#12 or passphrase. If you want the whole bundle on stdout (to pipe into a secret store, say), pass --stdout. It then goes wherever your terminal or CI logs keep output. With both --out and --stdout, a failed save still prints the bundle.

If the device already holds a self-enrolled certificate, the API answers 409 device_self_enrolled. Add --acknowledge-self-enrollment-override to issue anyway. If the device is at its certificate limit (409 device_cert_cap_reached), the error tells you how to list the active certificates and revoke one.

If the connection drops or times out mid-request, the certificate may still have been issued. Check iotpulse certs list --device-id <id> --status active and revoke any certificate you don’t hold a key for.

Write commands (devices update, tickets update) take a --data / -d flag with the JSON body. It accepts a raw string, a file, or stdin:

Terminal window
iotpulse devices update dev_1 --data '{"name":"Boiler A"}'
iotpulse devices update dev_1 --data @device.json
cat device.json | iotpulse devices update dev_1 --data -

The body is forwarded verbatim — the API validates it and returns a structured error if it is malformed.

Code Meaning
0 Success.
1 API returned an error (4xx/5xx) or the request failed (network/timeout).
2 Usage error, including no API key configured.
Terminal window
# Acknowledge every open anomaly for a device.
iotpulse anomalies list --device-id dev_123 --status open --json \
| jq -r '.[].id' \
| while read -r id; do iotpulse anomalies acknowledge "$id"; done