Skip to content

Public API reference

The IoT Pulse public API is a stable, versioned REST surface for building your own integrations, automation, and tooling against your tenant’s data. It is separate from the admin web app’s internal API: it is versioned (/api/v1), scope-gated, rate limited, and documented by a machine-readable OpenAPI spec.

The complete, always-current OpenAPI 3.1 document is served by the API:

https://api.iotpulse.io/openapi/public.json

Import that URL into any OpenAPI tool — Scalar, Postman, Insomnia, or an openapi-generator client — to get an interactive reference and generated client code. The spec is generated from the running API, so it never drifts from the deployed surface.

Every request authenticates with a scoped API key in the X-API-Key header. A tenant admin mints keys in the admin app under Settings → API Keys; the secret (format iotp_…) is shown once at creation.

Terminal window
curl https://api.iotpulse.io/api/v1/devices \
-H "X-API-Key: iotp_your_key_here"

Keys are bound to a single tenant. The API resolves your tenant from the key — you never pass a tenant id, and a key can only ever read or write its own tenant’s data.

A key also lasts only as long as the account that created it. It stops working when an admin deactivates or deletes that user, or resets their password, and reactivating the user does not bring it back. Create keys for long-lived integrations from an account that will stay active.

Each key is granted scopes of the form <resource>:<action>, where action is read, write, or * (both). device-certificates is the exception: its actions are read, issue and revoke. An endpoint returns 403 insufficient_scope if your key lacks the required scope.

Resource group Scopes
devices devices:read, devices:write
groups groups:read, groups:write
shifts shifts:read, shifts:write
thresholds thresholds:read, thresholds:write
anomalies anomalies:read, anomalies:write
tickets tickets:read, tickets:write
provisioning provisioning:read, provisioning:write
device-certificates device-certificates:read, device-certificates:issue, device-certificates:revoke

write does not imply read — grant both (or the * wildcard) if a key needs to do both.

provisioning:write mints gateway enrollment claims, which authorise a new device to join your tenant. It has no * wildcard and is never implied by devices:write or any other scope — tick it explicitly when you create the key.

device-certificates is explicit-only for the same reason: device-certificates:issue issues a device certificate and returns its private key. None of its scopes has a * wildcard or is implied by another scope, and issue and revoke are separate so a key can revoke certificates without being able to issue them (for example an MCP or incident-response key).

Requests are rate limited per API key: 120 requests per minute per key. When you exceed the limit the API returns 429 Too Many Requests with a Retry-After header (seconds). Each key has an independent budget, so one key’s traffic never affects another’s.

All routes are prefixed with /api/v1. Reads cover every resource group; v1 ships a curated set of scoped writes (more writes land in later versions).

Method Path Scope
GET /devices devices:read
GET /devices/{id} devices:read
PUT /devices/{id} devices:write
GET /groups groups:read
GET /groups/{id} groups:read
POST /groups groups:write
POST /groups/{groupId}/shifts shifts:write
GET /shifts shifts:read
GET /shifts/{id} shifts:read
GET /thresholds thresholds:read
GET /thresholds/{id} thresholds:read
POST /thresholds thresholds:write
GET /anomalies anomalies:read
GET /anomalies/{id} anomalies:read
POST /anomalies/{id}/acknowledge anomalies:write
GET /tickets tickets:read
GET /tickets/{id} tickets:read
POST /tickets tickets:write
PATCH /tickets/{id} tickets:write
GET /provisioning/claims provisioning:read
GET /provisioning/claims/{id} provisioning:read
POST /provisioning/claims provisioning:write
DELETE /provisioning/claims/{id} provisioning:write
GET /device-certificates device-certificates:read
GET /device-certificates/audit device-certificates:read
POST /device-certificates device-certificates:issue
DELETE /device-certificates/{id} device-certificates:revoke

The /anomalies list accepts optional query parameters: deviceId, status, skip (default 0), and limit (default 50, max 200).

The write endpoints create or update one resource each: POST /thresholds creates a reusable threshold pack, POST /groups creates a notification group, POST /groups/{groupId}/shifts assigns an existing shift to a group, and POST /tickets opens a maintenance ticket. Each takes a JSON request body and requires the …:write scope shown above.

POST /provisioning/claims mints a gateway enrollment claim: { "intendedName", "intendedGroupIds", "expiresInMinutes" (5–1440, default 60), "maxUses" (1–100, default 1) }. intendedGroupIds takes group ids from GET /groups; omit it for a gateway only admins can see. An id that is not a group in your tenant returns 400 invalid_request with the id in detail. Claims last at most 24 hours: an expiresInMinutes above the platform’s cap returns 400 invalid_claim_request, and the message names the cap. The claim code comes back in this response only, in the form PULSE-XXXX-XXXX-XXXX. GET /provisioning/claims accepts status (pending, used, revoked) and limit (default 100, max 500).

For what a device does with that claim code — generating a keypair, redeeming the claim for a signed certificate, connecting to the broker, and renewing — see Connect a device with a certificate.

These endpoints behave like the admin app’s Device certificates screen. They return the same errors, and every request is scoped to your key’s tenant.

  • GET /device-certificates returns one page of certificate metadata, newest first: { "items", "total", "skip", "limit" }. It never returns a private key. Optional query parameters are deviceId, status (all by default; active means not revoked, expired included; revoked), skip (default 0) and limit (default 50, max 200). The response echoes the effective skip and limit. Any other status value returns 400 invalid_request.
  • GET /device-certificates/audit returns the certificate audit trail, newest first, paged the same way. It covers every platform-generated issue (device_cert_minted), every self-enrolled issuance (device_cert_enrolled) and renewal (device_cert_renewed, with previousSerialNumber), and every revocation (device_cert_revoked), and shows the actor. A request made with an API key is recorded as apikey:<key-id>; a device enrolling with a claim as claim:<claim-id>.
  • POST /device-certificates with { "deviceId", "acknowledgeSelfEnrollmentOverride" } issues a platform-generated certificate. It returns 201 with the one-time bundle: privateKeyPem, clientCertPem, clientCertChainPem, serverCaBundlePem, and pkcs12Base64 + pkcs12Passphrase. The private key is not stored and cannot be fetched again, and the response carries Cache-Control: no-store. Errors:
    • 404 device_not_found: no such device in your tenant.
    • 409 device_self_enrolled: the device already holds an active self-enrolled certificate. Re-send with acknowledgeSelfEnrollmentOverride: true to issue anyway.
    • 409 device_cert_cap_reached: the device is at the per-device limit; revoke one first. Besides error, middleware and message, the body carries activeCount and maxActivePlatformCertsPerDevice.
    • 409 client_id_in_use: the device id is the certificate’s MQTT client ID, and a certificate device in another account already holds a valid certificate for it, or a username/password client already connects under it. A device id can’t be changed, so issue for a new device instead. See the client ID rule.
    • 503 catalog_unavailable: retry shortly.
    • 502 issuance_failed: retry shortly.
  • DELETE /device-certificates/{id} revokes a certificate and returns 204. It is idempotent. An id that is not a certificate in your tenant returns 404 certificate_not_found.

Issues and revocations made with an API key also appear in the API audit log. Those entries hold the certificate’s id, serial and fingerprint, never its key.

Errors return a structured JSON body so failures are diagnosable from a single response:

{ "error": "insufficient_scope", "middleware": "RequireScope", "message": "…" }
Status error Meaning
401 invalid_api_key Missing, malformed, unknown, revoked, or expired key, or the user who created it was deactivated, deleted or had their password reset.
403 insufficient_scope Valid key, but it lacks the scope the endpoint requires.
404 not_found The resource id does not exist in your tenant.
429 — Per-key rate limit exceeded; retry after Retry-After seconds.