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.

Each key is granted scopes of the form <resource>:<action>, where action is read, write, or * (both). An endpoint returns 403 insufficient_scope if your key lacks the required scope.

Resource groupScopes
devicesdevices:read, devices:write
groupsgroups:read, groups:write
shiftsshifts:read, shifts:write
thresholdsthresholds:read, thresholds:write
anomaliesanomalies:read, anomalies:write
ticketstickets:read, tickets:write

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

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 all six resource groups; v1 ships a curated set of scoped writes (more writes land in later versions).

MethodPathScope
GET/devicesdevices:read
GET/devices/{id}devices:read
PUT/devices/{id}devices:write
GET/groupsgroups:read
GET/groups/{id}groups:read
POST/groupsgroups:write
POST/groups/{groupId}/shiftsshifts:write
GET/shiftsshifts:read
GET/shifts/{id}shifts:read
GET/thresholdsthresholds:read
GET/thresholds/{id}thresholds:read
POST/thresholdsthresholds:write
GET/anomaliesanomalies:read
GET/anomalies/{id}anomalies:read
POST/anomalies/{id}/acknowledgeanomalies:write
GET/ticketstickets:read
GET/tickets/{id}tickets:read
POST/ticketstickets:write
PATCH/tickets/{id}tickets:write

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.

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

{ "error": "insufficient_scope", "middleware": "RequireScope", "message": "" }
StatuserrorMeaning
401invalid_api_keyMissing, malformed, unknown, revoked, or expired key.
403insufficient_scopeValid key, but it lacks the scope the endpoint requires.
404not_foundThe resource id does not exist in your tenant.
429Per-key rate limit exceeded; retry after Retry-After seconds.