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.
OpenAPI specification
Section titled “OpenAPI specification”The complete, always-current OpenAPI 3.1 document is served by the API:
https://api.iotpulse.io/openapi/public.jsonImport 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.
Authentication
Section titled “Authentication”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.
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.
Scopes
Section titled “Scopes”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 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 |
write does not imply read — grant both (or the * wildcard) if a key
needs to do both.
Rate limiting
Section titled “Rate limiting”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.
Endpoints (v1)
Section titled “Endpoints (v1)”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).
| 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 |
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
Section titled “Errors”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. |
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. |