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.
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.
Scopes
Section titled “Scopes”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).
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 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.
Device certificates
Section titled “Device certificates”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-certificatesreturns one page of certificate metadata, newest first:{ "items", "total", "skip", "limit" }. It never returns a private key. Optional query parameters aredeviceId,status(allby default;activemeans not revoked, expired included;revoked),skip(default0) andlimit(default50, max200). The response echoes the effectiveskipandlimit. Any otherstatusvalue returns400 invalid_request.GET /device-certificates/auditreturns 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, withpreviousSerialNumber), and every revocation (device_cert_revoked), and shows the actor. A request made with an API key is recorded asapikey:<key-id>; a device enrolling with a claim asclaim:<claim-id>.POST /device-certificateswith{ "deviceId", "acknowledgeSelfEnrollmentOverride" }issues a platform-generated certificate. It returns201with the one-time bundle:privateKeyPem,clientCertPem,clientCertChainPem,serverCaBundlePem, andpkcs12Base64+pkcs12Passphrase. The private key is not stored and cannot be fetched again, and the response carriesCache-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 withacknowledgeSelfEnrollmentOverride: trueto issue anyway.409 device_cert_cap_reached: the device is at the per-device limit; revoke one first. Besideserror,middlewareandmessage, the body carriesactiveCountandmaxActivePlatformCertsPerDevice.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 returns204. It is idempotent. An id that is not a certificate in your tenant returns404 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
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, 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. |